什么是Mesh?
Mesh是指的模型的網格,3D模型是由多邊形拼接而成,而多邊形實際上又是由多個三角形拼接而成的。即一個3D模型的表面其實是由多個彼此相連的三角面構成。三維空間中,構成這些三角形的點和邊的集合就是Mesh。
原理
即動態創建一個Mesh,設置三角形和頂點數據,然后賦值給MeshFilter(增加mesh屬性),通過MeshRenderer(增加材質並渲染出Mesh)繪制出來
理論基礎:
1、左手坐標系和右手坐標系
我們的三維坐標系,在3dmax里是右手坐標系,而在Unity里是左手坐標系。
左手坐標系和右手坐標系的區別 http://www.cnblogs.com/mythou/p/3327046.html
2、三邊面如何組成四邊面
如圖,左邊是Unity里的左手坐標系,右邊是在此坐標系里生成的一個面以及它的各個點坐標。
012和230這兩個三邊面就組成了一個四邊面。
如果我問這個四邊面有幾個頂點,想必大家都會回答4個,實際上是6個,012和230這是6個頂點,不同面的頂點不公用。
要組成2個三邊面可以有很多種順序,例如012和320、012和032、023和012等等等
但是我們一般都是按照4個點的順序來畫2個三邊面組成四邊面,所以可選的只有【012和230、230和012】,以及【032和210、210和032】這兩大類
這兩類畫法有什么區別呢?細心的童鞋應該已經發現,這兩種方式前者是逆時針,后者是順時針。
這種循環的方向會導致面的法線方向不同,而這個法線方向會決定這個面的朝向。
我們要確定這個法線方向其實很簡單,上面說了,Unity里是左手坐標系,拿出左手,伸直,拇指與其他四個指頭垂直,然后四指彎曲,指尖朝向循環的方向,拇指就指向法線的方向。
由此我們得出結論,要想生成正確的面(法線指向我們),我們只能用【032和210、210和032】
這里需要注意的一點是,我們確定4個點的循環方向,和生成三邊面時的循環方向無關,只要生成三邊面時,用到的前4個點的index順序沒錯就行了。
Mesh的組成部分
1.vertices(頂點數據數組Vector3[])
2.triangles(三角形頂點索引數組,int[])
3.normals(法線向量數組,Vector3[])
4.uv(紋理坐標數組,Vector2[])
頂點坐標:頂點坐標數組存放Mesh的每個頂點的空間坐標,假設某mesh有n個頂點,則vertex的size為n
法線:法線數組存放mesh每個頂點的法線,大小與頂點坐標對應,normal[i]對應頂點vertex[i]的法線
法線詳解:
法線就是垂直於面的一條線,它有方向,沒有大小。
法線的方向就是面朝外的方向。比如我們現在盯着顯示器看,從顯示器的正中心會有一條法線垂直於屏幕指向我們。
法線向外的面就是正面,相反的就是背面,一般來講,從正面看才能看到面,背面看面是看不到的。
紋理坐標:它定義了圖片上每個點的位置的信息. 這些點與3D模型是相互聯系的, 以決定表面紋理貼圖的位置. UV就是將圖像上每一個點精確對應到模型物體的表面. uv[i]對應vertex[i]
三角形序列:每個mesh都由多個三角面組成,而三角面的三個點就是頂點坐標里的點,三角形的數組的size = 三角形個數 * 3
三邊面和四邊面:
三邊面就是三條邊組成的面,四邊面就是四條邊組成的面。
三邊面在三維空間中是不可扭曲的,而四邊面在三維空間中可以扭曲。所以Unity里只支持三邊面。其他支持四邊面的軟件例如3dmax在導出fbx的時候,會把四邊面轉換成三邊面。
創建一個立方體
1.定頂點坐標
一般我們會以立方體幾何中心為坐標原點。
代碼:
1 //頂點數組 2 Vector3[] _vertices = 3 { 4 // front 5 new Vector3(-5.0f, 10.0f, -5.0f), 6 new Vector3(-5.0f, 0.0f, -5.0f), 7 new Vector3(5.0f, 0.0f, -5.0f), 8 new Vector3(5.0f, 10.0f, -5.0f), 9 10 11 // left 12 new Vector3(-5.0f, 10.0f, -5.0f), 13 new Vector3(-5.0f, 0.0f, -5.0f), 14 new Vector3(-5.0f, 0.0f, 5.0f),// 15 new Vector3(-5.0f, 10.0f, 5.0f), 16 17 // back 18 new Vector3(-5.0f, 10.0f, 5.0f), 19 new Vector3(-5.0f, 0.0f, 5.0f), 20 new Vector3(5.0f, 0.0f, 5.0f), 21 new Vector3(5.0f, 10.0f, 5.0f), 22 23 24 // right 25 new Vector3(5.0f, 10.0f, 5.0f), 26 new Vector3(5.0f, 0.0f, 5.0f), 27 new Vector3(5.0f, 0.0f, -5.0f), 28 new Vector3(5.0f, 10.0f, -5.0f), 29 30 31 // Top 32 new Vector3(-5.0f, 10.0f, 5.0f), 33 new Vector3(5.0f, 10.0f, 5.0f), 34 new Vector3(5.0f, 10.0f, -5.0f), 35 new Vector3(-5.0f, 10.0f, -5.0f), 36 37 // Bottom 38 new Vector3(-5.0f, 0.0f, 5.0f), 39 new Vector3(5.0f, 0.0f, 5.0f), 40 new Vector3(5.0f, 0.0f, -5.0f), 41 new Vector3(-5.0f, 0.0f, -5.0f), 42 43 };
這里有人會有疑問,正方體6個面,每個面由2個三角形組成,所以共需要36個三角形頂點索引。但是正方體只有8個頂點,為什么需要24個頂點坐標數據呢?
答案是:Unity3D的Mesh.triangles是三角形索引數組,不僅依靠這個索引值索引三角形頂點坐標,而且索引紋理坐標,索引法線向量。即正方體的每個頂點都參與了3個平面,而這3個平面的法線向量是不同的,該頂點在渲染這3個平面的時候需要索引到不同的法線向量。而由於頂點坐標和法線向量是由同一個索引值triangles[Index]取得的,例如,有三個點在vertices中索引到的頂點都為(0,0,0),但是在normals中索引到的法向量值各不相同。這就決定了在正方體中一個頂點,需要有3份存儲。(如果你需要創建其它模型,需要根據實際情況決定頂點坐標的冗余度。實質上頂點坐標的冗余正是方便了法線坐標、紋理坐標的存取。),一般不共點。還有就是Unity中是左手坐標系,一定記好,因為在繪制三角面時很重要。
2.三角面索引
//索引數組 int[] _triangles = { //front 2,1,0, 0,3,2, //left 4,5,6, 4,6,7, //back 9,11,8, 9,10,11, //right 12,13,14, 12,14,15, ////up //16,17,18, //16,18,19, ////buttom //21,23,22, //21,20,23, //不可跳躍設置索引值(否則會提示一些索引超出邊界頂點 15直接20不可,要連續15-16) 17,19,18, 17,16,19, };
這里設置的原則時外面被渲染里面剔除掉,順時針構建(注意里外面的區別),還要注意的一個點,如上所寫,比如我想生成5個面,那你的索引值也要是連續的,不可16直接蹦到20。這里立法體面的繪制順序是(即繪制三角面的面與上面頂點順序要一致)設置頂點的順序
3.UV坐標
代碼:
1 //UV數組 2 Vector2[] uvs = 3 { 4 // Front 5 new Vector2(1.0f, 0.0f), 6 new Vector2(1.0f, 1.0f), 7 new Vector2(1.0f, 0.0f), 8 new Vector2(0.0f, 0.0f), 9 10 11 // Left 12 new Vector2(1.0f, 1.0f), 13 new Vector2(0.0f, 1.0f), 14 new Vector2(0.0f, 0.0f), 15 new Vector2(1.0f, 0.0f), 16 17 18 // Back 19 new Vector2(1.0f, 0.0f), 20 new Vector2(1.0f, 1.0f), 21 new Vector2(1.0f, 0.0f), 22 new Vector2(0.0f, 0.0f), 23 24 25 // Right 26 new Vector2(1.0f, 1.0f), 27 new Vector2(0.0f, 1.0f), 28 new Vector2(0.0f, 0.0f), 29 new Vector2(1.0f, 0.0f), 30 31 //// Top 32 //new Vector2(0.0f, 0.0f), 33 //new Vector2(1.0f, 0.0f), 34 //new Vector2(1.0f, 1.0f), 35 //new Vector2(0.0f, 1.0f), 36 37 38 // Bottom 39 new Vector2(0.0f, 0.0f), 40 new Vector2(1.0f, 0.0f), 41 new Vector2(1.0f, 1.0f), 42 new Vector2(0.0f, 1.0f), 43 44 };
UV坐標從左上角開始(想象攝像機在立方體內部去判斷),

1 Mesh mesh = new Mesh() 2 { 3 vertices = _vertices, 4 uv = uvs, 5 triangles = _triangles, 6 }; 7 8 //重新計算網格的法線 9 //在修改完頂點后,通常會更新法線來反映新的變化。法線是根據共享的頂點計算出來的。 10 //導入到網格有時不共享所有的頂點。例如:一個頂點在一個紋理坐標的接縫處將會被分成兩個頂點。 11 //因此這個RecalculateNormals函數將會在紋理坐標接縫處創建一個不光滑的法線。 12 //RecalculateNormals不會自動產生切線,因此bumpmap着色器在調用RecalculateNormals之后不會工作。然而你可以提取你自己的切線。 13 mesh.RecalculateNormals();
給mesh屬性賦值。
5.增加MeshFilter組件,網格過濾。以及增加MeshRenderer組件添加材質實現渲染。OK!!!到這基本已經繪制完了,Mesh已經出來了。
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class ShaderBase : MonoBehaviour 6 { 7 8 void Start() 9 { 10 GameObject gameObject = new GameObject("Cube"); 11 gameObject.transform.position = Vector3.zero; 12 13 //頂點數組 14 Vector3[] _vertices = 15 { 16 // front 17 new Vector3(-5.0f, 10.0f, -5.0f), 18 new Vector3(-5.0f, 0.0f, -5.0f), 19 new Vector3(5.0f, 0.0f, -5.0f), 20 new Vector3(5.0f, 10.0f, -5.0f), 21 22 23 // left 24 new Vector3(-5.0f, 10.0f, -5.0f), 25 new Vector3(-5.0f, 0.0f, -5.0f), 26 new Vector3(-5.0f, 0.0f, 5.0f),// 27 new Vector3(-5.0f, 10.0f, 5.0f), 28 29 // back 30 new Vector3(-5.0f, 10.0f, 5.0f), 31 new Vector3(-5.0f, 0.0f, 5.0f), 32 new Vector3(5.0f, 0.0f, 5.0f), 33 new Vector3(5.0f, 10.0f, 5.0f), 34 35 36 // right 37 new Vector3(5.0f, 10.0f, 5.0f), 38 new Vector3(5.0f, 0.0f, 5.0f), 39 new Vector3(5.0f, 0.0f, -5.0f), 40 new Vector3(5.0f, 10.0f, -5.0f), 41 42 43 // Top 44 new Vector3(-5.0f, 10.0f, 5.0f), 45 new Vector3(5.0f, 10.0f, 5.0f), 46 new Vector3(5.0f, 10.0f, -5.0f), 47 new Vector3(-5.0f, 10.0f, -5.0f), 48 49 // Bottom 50 new Vector3(-5.0f, 0.0f, 5.0f), 51 new Vector3(5.0f, 0.0f, 5.0f), 52 new Vector3(5.0f, 0.0f, -5.0f), 53 new Vector3(-5.0f, 0.0f, -5.0f), 54 55 }; 56 //索引數組 57 int[] _triangles = 58 { 59 //front 60 2,1,0, 61 0,3,2, 62 //left 63 4,5,6, 64 4,6,7, 65 //back 66 9,11,8, 67 9,10,11, 68 //right 69 12,13,14, 70 12,14,15, 71 ////up 72 //16,17,18, 73 //16,18,19, 74 ////buttom 75 //21,23,22, 76 //21,20,23, 77 78 //不可跳躍設置索引值(否則會提示一些索引超出邊界頂點 15直接20不可,要連續15-16) 79 17,19,18, 80 17,16,19, 81 }; 82 83 //UV數組 84 Vector2[] uvs = 85 { 86 // Front 87 new Vector2(1.0f, 0.0f), 88 new Vector2(1.0f, 1.0f), 89 new Vector2(1.0f, 0.0f), 90 new Vector2(0.0f, 0.0f), 91 92 93 // Left 94 new Vector2(1.0f, 1.0f), 95 new Vector2(0.0f, 1.0f), 96 new Vector2(0.0f, 0.0f), 97 new Vector2(1.0f, 0.0f), 98 99 100 // Back 101 new Vector2(1.0f, 0.0f), 102 new Vector2(1.0f, 1.0f), 103 new Vector2(1.0f, 0.0f), 104 new Vector2(0.0f, 0.0f), 105 106 107 // Right 108 new Vector2(1.0f, 1.0f), 109 new Vector2(0.0f, 1.0f), 110 new Vector2(0.0f, 0.0f), 111 new Vector2(1.0f, 0.0f), 112 113 //// Top 114 //new Vector2(0.0f, 0.0f), 115 //new Vector2(1.0f, 0.0f), 116 //new Vector2(1.0f, 1.0f), 117 //new Vector2(0.0f, 1.0f), 118 119 120 // Bottom 121 new Vector2(0.0f, 0.0f), 122 new Vector2(1.0f, 0.0f), 123 new Vector2(1.0f, 1.0f), 124 new Vector2(0.0f, 1.0f), 125 126 }; 127 128 Mesh mesh = new Mesh() 129 { 130 vertices = _vertices, 131 uv = uvs, 132 triangles = _triangles, 133 }; 134 135 //重新計算網格的法線 136 //在修改完頂點后,通常會更新法線來反映新的變化。法線是根據共享的頂點計算出來的。 137 //導入到網格有時不共享所有的頂點。例如:一個頂點在一個紋理坐標的接縫處將會被分成兩個頂點。 138 //因此這個RecalculateNormals函數將會在紋理坐標接縫處創建一個不光滑的法線。 139 //RecalculateNormals不會自動產生切線,因此bumpmap着色器在調用RecalculateNormals之后不會工作。然而你可以提取你自己的切線。 140 mesh.RecalculateNormals(); 141 gameObject.AddComponent<MeshFilter>().mesh=mesh; 142 //Material/New Material 1 143 gameObject.AddComponent<MeshRenderer>().material = Resources.Load<Material>("Material/New Material"); 144 145 } 146 147 }
這不是上述代碼的結果圖片,這是動態創建外圍盒的圖片,做法一樣。
最新:這個立方體,我想底面和側面貼不同貼圖,如何實現?
使用 mesh.subMeshCount = X;即subMesh,子網格,具體使用如下:
1 Vector3 contralPos = (maxPos + minPos) / 2; 2 float boxHight = Mathf.Abs(maxPos.y - minPos.y); 3 float boxLength = Mathf.Abs(maxPos.x - minPos.x); 4 float boxWidth = Mathf.Abs(maxPos.z - minPos.z); 5 vertexPosArray = AddVertexPos(1.2f * boxLength, 1.2f * boxWidth, 1.4f * boxHight); 6 vertexIndexList = AddVertexIndex(); 7 uvArr = SetUVPos(GetIntValue(boxLength / (textureSizeL * uvNorm)), GetIntValue(boxWidth / (textureSizeL * uvNorm)), GetIntValue(boxHight / (textureSizeW * uvNorm))); 8 Mesh mesh = new Mesh() 9 { 10 vertices = vertexPosArray, 11 uv = uvArr, 12 }; 13 mesh.subMeshCount = 2; 14 mesh.SetTriangles(vertexIndexList[0], 0); 15 mesh.SetTriangles(vertexIndexList[1], 1); 16 mesh.RecalculateNormals(); 17 GameObject Box = new GameObject(name); 18 // Box.transform.localPosition = contralPos; 19 Box.transform.localPosition = new Vector3(contralPos.x, minPos.y, contralPos.z); 20 Box.AddComponent<MeshFilter>().mesh = mesh; 21 Material[] materials = new Material[2]; 22 materials[0] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Side")); 23 materials[1] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Buttom")); 24 Box.AddComponent<MeshRenderer>().materials = materials;
mesh.subMeshCount = 2;
mesh.SetTriangles(vertexIndexList[0], 0); mesh.SetTriangles(vertexIndexList[1], 1);
這是指定子網格對應的索引集合,在設置索引時,應該這樣分開存儲:

1 /// <summary> 2 /// 添加索引 3 /// </summary> 4 private List<int[]> AddVertexIndex() 5 { 6 List<int[]> indexList = new List<int[]>(); 7 int[] sideIndexArray = 8 { 9 //front 10 2,1,0, 11 2,0,3, 12 13 //back 14 4,5,6, 15 4,6,7, 16 17 //left 18 8,10,11, 19 8,9,10, 20 21 //right 22 13,15,14, 23 13,12,15, 24 }; 25 int[] buttomFaceIndexArray = 26 { 27 //buttom 28 17,16,19, 29 17,19,18 30 }; 31 indexList.Add(sideIndexArray); 32 indexList.Add(buttomFaceIndexArray); 33 34 return indexList; 35 }
即這樣完成分開了Mesh,分別使用不同的材質。