Mesh 만들기
오늘은 메쉬mesh를 스크립트로 제어및 생성해볼것이다
기존에 내가 알던방식과
이번에 새로 알게된 재밌는 다른 방식 해서 총 2가지의 방식이다
유니티로 하였다 모두
첫번째로 기존에 내가 알면서 해왔던 방식
게임씬에 빈 게임오브젝트를 만들고
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SimpleProceduralMesh : MonoBehaviour
{
private void OnEnable()
{
Mesh mesh = new Mesh { name = "Procedural Mesh" };
1.정점의 위치정보
mesh.vertices = new Vector3[]
{
Vector3.zero, Vector3.right, Vector3.up
,new Vector3(1f,1f)
사각형 모양을 만든다
};
2. 정점배열의 인덱스번호로 , 삼각형그리기
mesh.triangles = new int[]
{
// 시계방향으로 정점을 읽을시, 유니티 z축 방향으로 그려짐
0,2,1,1,2,3
};
3. 정점의 노말방향, 즉 면이 바라볼 정면의 방향
mesh.normals = new Vector3[]
{
// z축 방향으로 바라볼시 메쉬가 보이기에, 노말(표면이 나아가는 방향은)은 back으로 맞출수있음
Vector3.back,Vector3.back,Vector3.back,
Vector3.back
};
4. 탄젠트는 사실 아직도 완벽히 이해못하였다, 셰이더를 할때도 거의 사용을 아직 안해보았다
mesh.tangents = new Vector4[]
{
new Vector4(1f,0f,0f,-1f),
new Vector4(1f,0f,0f,-1f),
new Vector4(1f,0f,0f,-1f),
new Vector4(1f,0f,0f,-1f),
};
5. uv이다, 정점에게 텍스처의 어디부터 어디까지를 보일지, 유니티와 OPENGL은 왼쪽 하단부터가 0,0으로 시작한다고 한다
mesh.uv = new Vector2[]
// uv 의 xy를 정해서 출력가능
{ Vector2.zero, Vector2.right, Vector2.up,
Vector2.one
};
6. 마지막으로 메쉬필터의 메쉬로 만들어둔 메쉬 를 넣어주기
GetComponent<MeshFilter>().mesh = mesh;
}
}
위까지는 내가 알고있던 방식이다,
그러나 재밋는 글을 보았고 , 출처를 남길수잇는지 좀 더 확인후 남겨보도록하겠다
패트론에서 1달러로 후원자가 되며 다양한 셰이더관련 튜토리얼 글을 읽을수있었다
https://catlikecoding.com/unity/tutorials/procedural-meshes/square-grid/
두번쨰로 이번에 알게된 새로운방식
using Unity.Mathematics;
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
public class AdvancedSingleStreamProceduralMesh : MonoBehaviour // 버전 2020부터
// 유니티에서 실행하였음
1. 버텍스가 가질 요소를 정의한다, 구조체로
{
[StructLayout(LayoutKind.Sequential)] // using System.Runtime.InteropServeices; 필수!
struct Vertex
{ // float -> half -> fixed 순으로 바이트가 적어진다, float 32비트 half 16비트 fixed 8비트 셰이더 데이터 개념으로 알고있다
public float3 position, normal; 포지션과 노말
public half4 tangent; 탄젠트
public half2 texCoord0; 텍스처
}
2. 활성화상태에서 바로 재생
private on Enable()
{
int vertexAttributeCount = 4; 속성의 갯수는 4개
int vertexCount = 4;
int triangleIndexCount = 6; 정점4개와 이어서 만들어줄 삼각형에 드는 정점 6개(2개는 중복)
메쉬데이터배열은 2020버전부터 쓸수있는듯하였다, 2018.LTS에서 사용하려니 인식을 못하여 버전업 하였다
말그대로 메쉬가 가질 데이터배열을 의미하는데 , 추가로 내가 직접 작성하여 의미가 많아질듯하다,
WritableMeshData(인자)에서 에디터와 씬뷰에서 만들어질 메쉬의 갯수를 의미한다
Mesh.MeshDataArray meshDataArray = Mesh.AllocateToWritableMeshData(1);
Mesh.MeshData meshData = meshDataArray[0];
버텍스 요소들을 구조체로 정의하고 그걸 직접 버텍스속성에 적용하려한다 Attribute : 속성
var vertexAttributes = new NativeArray<VertexAttributeDescriptor>
(vertexAttributeCount, Allocator.Temp , NativeArrayOptions.UninitializedMemory);
애초에 정점(버텍스)의 개수를 4개로 정하였기에 자연스레 배열의 크기도 4개 (0으로 시작해 3까지의 길이)
순서대로 하면 [0] 포지션 float3, [1] 노말 flaot3, [2] 탄젠트 float4, [4] 텍스처 flaot2
허나 버텍스속성포맷으로 그저 반복되면서 수정될일이 없는 (대표적으로 탄젠트)는 flaot32->16으로 압축을 한거같다
vertexAttributes[0] = new VertexAttributeDescriptor(dimension:3);
vertexAttributes[1] = new VertexAttributeDescriptor
(VertexAttribute.Normal , dimension:3);
//노말 float3 라서 삼차원 ,스트림은 재생순서 우리는 포지션 노말 탄젠트 텍스쳐순으로 재생
vertexAttributes[2] = new VertexAttributeDescriptor
(VertexAttribute.Tangent, VertexAttributeFormat.Float16,4);
vertexAttributes[3] = new VertexAttributeDescriptor
(VertexAttribute.TexCoord0, VertexAttributeFormat.Float16, 2);
위처럼 버텍스의 각 속성들을 버텍스속성배열에 정의하였고
이제 버텍스구간에 파라미터를 이용하여 버텍스 위치등을 실제로 배치한다
위에서 속성들을 선언한 구조체를 사용한다
인자로 버텍스 갯수와, 배열로 각각 지정해둔 버텍스속성배열이다
meshData.SetVertexBufferParams(vertexCount, vertexAttributes);
vertexAttributes.Dispose();
// 실제 에디터상에서 정점들의 정보를 담아줄 vertices배열
NativeArray<Vertex> vertices = meshData.GetVertexData<Vertex>():
half h0 = half(0f), h1 = half(1f); 로 하프를 쓸수있게
var vertex = new Vertex // 모두 같은 값을 지니는 노말과 탄젠트는 묶어버려 한번에 해결
{
normal = back(),
tangent = half4(h1, h0, h0, half(-1))
};
각 버텍스(소문자) 들의 배열요소들의 각 위치등의 속성정보들을 수정
vertex.position = 0f;
vertex.texCoord0 = h0;
vertices[0] = vertex;
vertex.position = right();
vertex.texCoord0 = half2(h1,h0);
vertices[1] = vertex;
vertex.position = up();
vertex.texCoord0 = half2(h0,h1);
vertices[2] = vertex;
vertex.position = float3(1f,1f,0f);
vertex.texCoord0 = h1;
vertices[3] = vertex;
정점들을 완성하였으면, 삼각형을 만들어주자
정점에서는 셋버텍스파라미터 함수를 썻다면
이번엔 셋인덱스버퍼파마리터이다
meshData.SetIndexBufferParams(triangleIndexCount, IndexFormat.UInt16);
겟인덱스 데이타는 버텍스버퍼에 담긴 각 인덱스버퍼의 정보를 끄집어 사용
NativeArray<ushort> triangleIndicies = meshData.GetIndexData<ushort>();
triangleIndicies[0] = 0;
triangleIndicies[1] = 2;
triangleIndicies[2] = 1;
triangleIndicies[3] = 1;
triangleIndicies[4] = 2;
triangleIndicies[5] = 3;
바운딩작업
var bounds = new Bounds(new Vector3(0.5f, 0.5f), new Vector3(1f,1f)) ;
meshData.subMeshCount = 1;
meshData.SetSubMesh(0,
new SubMeshDescriptor(0,triangleIndexCount){ bounds = bounds, vertexCount = vertexCount }
, MeshUpdateFlags.DontRecalculateBounds);
Mesh mesh = new Mesh {bounds = bounds, name = "Advanced Procedural Mesh"};
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh);
생성한 메쉬를 넣어주기
GetComponent<MeshFilter>().mesh = mesh;
}
결과적으로 첫번쨰 방식과 두번째 방식 모두 같은 그림을 그린다
사진은 2번쨰 방식만 올리지만 결과는 어차피 동일하다
float -> half로 줄인 탄젠트와 UV(텍스처) 절반으로 줄어든게 보인다
마찬가지로 정점또한 6개가 아닌 4개로(2개는 재활용) 삼각형을 2개 만들고 쿼드를 만들며
텍스처또한 잘 붙은걸로 보인다