Create a grid of points.
Use a coroutine to analyze their placement.
Define a surface with triangles.
Automatically generate normals.
Add texture coordinates and tangents.
[ 1. Rendering Things ]
Mesh
3D Model 툴로 만들어지거나 직접 프로그램으로 점진적으로 만들어짐
그래픽 하드웨어에 의해서 그려지는 복잡한 모형
3차원 공간 상에서 최소 정점 데이터를 가지고 있으며, 추가적으로는 폴리곤(삼각형)과 같은 정보
Wire frame : 폴리곤으로 이루어진 오브젝트의 표면적인 모양
3D 모델을 보여 주기위해 필요한 2가지 Component
Mesh filter : 모양을 가진 mesh의 주소 참조 값
Mesh Renderer : Mesh가 렌더링 되는 설정 ( Material, 그림자 처리 등 )
Material이 배열로 표현되어 있는 이유는 여러 개의 Material을 사용할 수 있기 때문 (submesh)
Material 생성
유니티에서 제공하는 기본 Material은 흰색 모양을 띈 Material
Asset >> Create >> material
Albedo : 메시가 가지가 되는 기본적인 색깔( 여기에 Texture를 넣을 수도 있음)
Texure를 넣기 위해서는 u,v 좌표계를 사용함 (uv모두 0 ~ 1값을 가짐)
[ 2. Creating a Grid of Vertices ]
Grid를 활용하여 나만의 mesh로 사용할 정점 만들기
Grid 게임 오브젝트 생성(Empty Object)
Menu : GameObject >> Create Empty
Name : Grid
MyGrid.cs 생성
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class MyGrid : MonoBehaviour {
public int xSize, ySize;
}
MeshFilter와 MeshRenderer를 필요로 하고 있음 (자동 추가됨)
Grid.cs >> (to) Grid(게임 오브젝트)
Grid public 변수 세팅
X Size : 10, Y Size : 5
UV 메시 >> (to) Grid(게임 오브젝트)
Texture를 끌어다 놓으면 자동적으로 기본(Standard) 셰이더를 사용하는 Material만들어지고 Albedo에 Texture Map을 넣음
또한 Grid의 Mesh Renderer 콤퍼넌트의 Materials에 생성된 Material을 대입함
실행과 동시에 메시가 만들어질 수 있도록 하기
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class MyGrid : MonoBehaviour {
public int xSize, ySize;
private Vector3[] vertices;
private void Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
}
}
}
// 실행과 동시에 메시를 만들어 내기 위 Awake() 이용
private void Awake () {
Generate();
}
private void OnDrawGizmos () {
if (vertices == null) {
return;
}
Gizmos.color = Color.black;
for (int i = 0; i < vertices.Length; i++) {
Gizmos.DrawSphere(vertices[i], 0.1f); // 0.1 크기의 구를 그림
}
}
}
[ Gizmo ]
에디터에서 특정한 정보를 시각적인 방식으로 표현을 하는 것을 의미함
게임 씬에서는 보여지지는 않지만, 씬을 구성하는데 있어서 유저가 시각적으로 정보를 얻을 수 있도록 해줌
정점들이 (시각적으로) 점진적으로 만들어지게 하기
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class MyGrid : MonoBehaviour {
public int xSize, ySize;
private Vector3[] vertices;
private IEnumerator Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(0.05f);
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
yield return wait;
}
}
}
// 실행과 동시에 메시를 만들어 내기 위 Awake() 이용
private void Awake () {
//Generate();
StartCoroutine(Generate());
}
private void OnDrawGizmos () {
if (vertices == null) {
return;
}
Gizmos.color = Color.black;
for (int i = 0; i < vertices.Length; i++) {
Gizmos.DrawSphere(vertices[i], 0.1f); // 0.1 크기의 구를 그림
}
}
}
[ 3. Creating the Mesh ]
Mesh Filer에 들어갈 Mesh 생성하기
private Mesh mesh;
private IEnumerator Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(0.05f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid"; // 메시 이름
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
yield return wait;
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
}
삼각형 하나 그리기
private IEnumerator Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(0.05f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid"; // 메시 이름
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
yield return wait;
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
int[] triangles = new int[3];
triangles[0] = 0;
triangles[1] = 1;
triangles [2] = xSize + 1; // 두번째 줄의 첫번째 정점
mesh.triangles = triangles;
}
Clockwise 삼각형 그리기(z축 기준)
clockwise : 앞면 / counter-clockwise : 뒷면
triangles [0] = 0;
triangles [1] = xSize + 1; // 두번째 줄의 첫번째 정점
triangles [2] = 1;
clockwise로 바꾼 인덱스
두 개의 삼각형으로 사각형 그리기
int[] triangles = new int[6];
triangles[0] = 0;
triangles[1] = xSize + 1;
triangles[2] = 1;
triangles[3] = 1;
triangles[4] = xSize + 1;
triangles[5] = xSize + 2;
//triangles[0] = 0;
//triangles[3] = triangles[2] = 1;
//triangles[4] = triangles[1] = xSize + 1;
//triangles[5] = xSize + 2;
한줄 메시 만들기
private IEnumerator Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(1.0f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid"; // 메시 이름
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
int[] triangles = new int[xSize * 6];
for (int ti = 0, vi = 0, x = 0; x < xSize; x++, ti += 6, vi++) {
triangles[ti] = vi;
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
triangles[ti + 5] = vi + xSize + 2;
mesh.triangles = triangles;
yield return wait;
}
//mesh.triangles = triangles;
//yield return wait;
}
전체 만들기
int[] triangles = new int[xSize * ySize * 6];
for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) {
for (int x = 0; x < xSize; x++, ti += 6, vi++) {
triangles [ti] = vi;
triangles [ti + 3] = triangles [ti + 2] = vi + 1;
triangles [ti + 4] = triangles [ti + 1] = vi + xSize + 1;
triangles [ti + 5] = vi + xSize + 2;
mesh.triangles = triangles;
yield return wait;
}
}
Coroutine을 삭제해서 Delay 시간 없애기
private void Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(0.1f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid"; // 메시 이름
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
int[] triangles = new int[xSize * ySize * 6];
for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) {
for (int x = 0; x < xSize; x++, ti += 6, vi++) {
triangles [ti] = vi;
triangles [ti + 3] = triangles [ti + 2] = vi + 1;
triangles [ti + 4] = triangles [ti + 1] = vi + xSize + 1;
triangles [ti + 5] = vi + xSize + 2;
}
}
mesh.triangles = triangles;
}
// 실행과 동시에 메시를 만들어 내기 위 Awake() 이용
private void Awake () {
Generate();
}
[ 4. Generating Additional Vertex Data ]
[ Normal (법선) ]
표면에 수직인 단위 벡터 (내부가 아닌 바깥 표면을 인지할 수 있도록 함)
빛이 표면에 닿는 각도를 결정하는데 사용됨
법선은 정점별로 정의됨 (폴리곤의 정점들에 위치한 법선들을 가지고 보간법을 이용하면 부드럽게 곡선을 그릴 수 있음)
private void Generate () { // 10 X 5의 평면을 만들기 위서는 (10+1) X (5+1)의 정점 필요
WaitForSeconds wait = new WaitForSeconds(0.1f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid"; // 메시 이름
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
int[] triangles = new int[xSize * ySize * 6];
for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) {
for (int x = 0; x < xSize; x++, ti += 6, vi++) {
triangles [ti] = vi;
triangles [ti + 3] = triangles [ti + 2] = vi + 1;
triangles [ti + 4] = triangles [ti + 1] = vi + xSize + 1;
triangles [ti + 5] = vi + xSize + 2;
}
}
mesh.triangles = triangles;
mesh.RecalculateNormals(); // Normal Vector를 재생성하는 함수
Mesh.RecalculateNormal() :다른 정점들과 연결된 폴리곤(삼각형)들의 Normal을 구하고 이들을 평균하여 그 값을 구하고 다시 정규화 한다.
Texture 입히기
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
Vector2[] uv = new Vector2[vertices.Length];
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices [i] = new Vector3 (x, y);
uv[i] = new Vector2(x / xSize, y / ySize);
}
}
mesh.vertices = vertices; // 메시에 필요한 정점 데이터 대입
mesh.uv = uv;
맨 마지막의 사각형을 제외한 모든 정점의 uv가 0이라서 이렇게 표현됨
clamp와 wrap 모드 설정
Texture Inspector의 "Wrap Mode" : Repeat / Clamp
uv 값을 실수화 하여 정상화 하자
uv[i] = new Vector2((float) x / xSize, (float) y / ySize);
Normal Map 적용하기
기존 Texture는 없애고, Metallic을 강조하고 있음
Tangent Vector 적용하기(접선 공간)
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
Vector2[] uv = new Vector2[vertices.Length];
Vector4[] tangents = new Vector4[vertices.Length];
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
for (int i = 0, y = 0; y <= ySize; y++) {
for (int x = 0; x <= xSize; x++, i++) {
vertices[i] = new Vector3(x, y);
uv[i] = new Vector2((float)x / xSize, (float)y / ySize);
tangents[i] = tangent;
}
}
mesh.vertices = vertices;
mesh.uv = uv;
mesh.tangents = tangents;
기본 모델링은 평면의 정점으로 이루어져 있으므로, 간단하게 new Vector4(1f, 0f, 0f, -1f)를 설정