# 實作Mesh髮片(滑鼠模擬) ###### tags: `髮片model` ## (一)以滑鼠抓取座標(主程式) ### Step1: 想法 按下滑鼠拖曳畫完線段放開滑鼠,中間每間格1cm抓取一次座標。為了抓取正確座標我們需要兩個變數newPos與oldPos不斷計算新座標與舊座標的距離,取到正確座標後將它加入 List< Vector3> PointPos資料。 --- ### Step2: 計算座標方法 設定如下圖的基本網格。圖中紅線為我們拖曳出的滑鼠路徑,我們需要算出上下兩段與紅線座標(1)平行的座標(0)與(2)。 抓完座標將座標記入List< Vector3> PointPos資料 ![](https://ppt.cc/fEmDFx@.jpg) 如以下程式碼: ```c# Vector3 Vec0 = pos1 - pos2; //pos1新座標 pos2舊座標 Vector3 Vec1 = new Vector3(Vec0.y, -Vec0.x, 0.0f); Vector3 Vec2 = new Vector3(-Vec0.y, Vec0.x, 0.0f); Vector3 AddPos1 = new Vector3(pos1.x + Vec1.x, pos1.y + Vec1.y, 0.0f);//座標(0) Vector3 AddPos2 = new Vector3(pos1.x + Vec2.x, pos1.y + Vec2.y, 0.0f);//座標(2) ``` --- ### step3: 寬度調整方法 增加髮片寬度,我們沿用上一步計算座標方法並增加調整彈性。 下圖藍色區塊為基本寬度1,加入紫色區塊為寬度2,以此類推往上下兩端延伸。 ![](https://ppt.cc/fWsD6x@.jpg) 函式PosGenerate()簡說: 首先 for迴圈跑 thickness1記入向下延伸的座標,並記入List PointPos< Vector3> 將中心點座標記入List PointPos< Vector3> 再走 for迴圈跑 thickness2記入往上延伸的座標,並記入List PointPos< Vector3> 如以下程式碼: ```c# int width = 2; void PosGenerate(Vector3 pos1, Vector3 pos2)//計算點座標 (1)主線段點(2)右左兩個延伸點座標計算 { //上下兩個延伸點座標矩陣 thickness1 = new Vector3[width]; thickness2 = new Vector3[width]; //算兩點向量差 Vector3 Vec0 = pos1 - pos2;//兩點移動方向向量 for (int i = 0, j = thickness1.Length; i < thickness1.Length; i++, j--)//widthAdd1 { Vector3 Vec1 = new Vector3((Vec0.y) * j, (-Vec0.x) * j, Vec0.z * j); thickness1[i] = new Vector3(pos1.x + Vec1.x, pos1.y + Vec1.y, pos1.z + Vec1.z); PointPos.Add(thickness1[i]); } PointPos.Add(NewPos); for (int i = 0, j = 1; i < thickness2.Length; i++, j++)//widthAdd { Vector3 Vec2 = new Vector3((-Vec0.y) * j, (Vec0.x) * j, (-Vec0.z) * j); thickness2[i] = new Vector3(pos1.x + Vec2.x, pos1.y + Vec2.y, pos1.z + Vec2.z); PointPos.Add(thickness2[i]); } } ``` ## (二)生成mesh(副程式) 首先建立MeshFilter組件,再將mesh網格加入進MeshFilter中。 ` GetComponent<MeshFilter>().mesh = mesh = new Mesh();` mesh網格中有幾項數組(array)需設定,包含vertice、uv、triangle、normal、tangent...等。 基本設定: ```C# public void meshGenerate(int count,int Getwidth) { hairColor = GetComponent<Renderer>().material; hairColor.color = Color.red; GetComponent<MeshFilter>().mesh = mesh = new Mesh(); GetComponent<MeshRenderer>().material = hairColor;//加入material mesh.name = "Hair Grid";//網格名字 ``` } ``` --- ### step1:設定vertice、uv及tangent 將在第一部分PointPos收集的座標繼承在GetPointPos,並指給vertice以及uv 1. 需先給定陣列大小,陣列大小包含舊的髮片座標加上新的髮片座標的總和 2. 若有之前生成過的髮片,需要再跑迴圈備份一次 3. 跑迴圈將GetPointPos中的座標指給vertice及uv 4. 將vertice、uv、tangent(array名稱自訂無規範)的array分別指給mesh.vertice、mesh.uv及mesh.tangent ```c# public void meshGenerate(int count,int Getwidth) { ``` int Voldlen = verticeBox[count];//目前的total vertice個數 Vector3 vertice = new Vector3[GetPointPos.Count + Voldlen]; Vector2 uv = new Vector2[GetPointPos.Count + Voldlen];//texture Vector4 tangents = new Vector4[GetPointPos.Count + Voldlen]; Vector4 tangent = new Vector4(1f, 0f, 0f, -1f); //舊座標備分 for (int i = 0; i < Voldlen; i++) { vertice[i] = oldVerticePos[i]; uv[i].x = oldVerticePos[i].x; uv[i].y = oldVerticePos[i].y; tangents[i] = tangent; } //新座標 for (int i = Voldlen,j = 0; i < GetPointPos.Count + Voldlen; i++,j++) { vertice[i] = GetPointPos[j]; uv[i].x = GetPointPos[j].x;//Vector3轉Vector2 uv[i].y = GetPointPos[j].y; tangents[i] = tangent; } mesh.vertices = vertice; mesh.uv = uv; mesh.tangents = tangents; ``` } ``` --- ### step2:設定triangle mesh.triangle陣列負責生成三角形,一塊正方形網格是由兩片三角形組成,而我們的髮片是由多塊正方形網格所組合而成的。mesh.triangle資料型態為int,triangle抓座標方式為找到對應的mesh.vertice陣列指標,繼承對應的vertice陣列指標中的座標位置。 下圖中,黑色數字為vertice陣列指標,紅色數字部分為triangle陣列指標,triangle陣列指標需正確找到自己對應的vertice陣列指標,才能完成我們想要的Mesh網格。 ![](https://ppt.cc/f0A1hx@.jpg) 1.int point為計算有多少個正方形網格 2.計算triangle陣列需開多大 3.若有之前生成的髮片,需先做備份 4.for迴圈配合SstQuad函式,計算triangle。 ``` public void meshGenerate(int count,int Getwidth) { ``` int point = ((GetPointPos.Count / (3 + (Getwidth - 1) * 2) - 1)) * 2 * Getwidth;//計算網格數 triangles = new int[point * 6 + triangleBox[count]];//計算需要多少三角形點座標 //備份三角形 for (int i = 0; i < triangleBox[count]; i++) { triangles[i] = oldTrianglePos[i]; } int t = triangleBox[count];//初始三角形陣列指標 int k = 0;//累加 for (int vi = verticeBox[count], x = 1; x <= point; x++, vi += k) { t = SetQuad(triangles, t, vi, vi + 1, vi + 3 + (2 * (Getwidth - 1)), vi + 4 + (2 * (Getwidth - 1))); if (x % (Getwidth * 2) != point % (Getwidth * 2)) k = 1; //在同一行時 else k = 2; //累加 (換行時) } mesh.triangles = triangles; ``` } private static int SetQuad(int[] triangles, int i, int v0, int v1, int v2, int v3) { triangles[i] = v0; triangles[i + 1] = v1; triangles[i + 2] = v2; triangles[i + 3] = v2; triangles[i + 4] = v1; triangles[i + 5] = v3; return i + 6; } ``` ### step3:生成多段髮片 指導老師建議: 所有的髮片共用一個mesh,將mesh想成一個陣列,每加上一片髮片我們需將陣列擴大。如下圖: ![](https://ppt.cc/fAyRMx@.jpg) 1. 新增兩個變數分別為oldVertice及oldTriangle紀錄mesh.vertice及triangle的長度。 新增兩個List資料為verticeBox及triangleBox紀錄每次mesh.vertice及triangle的長度。 新增兩個List資料為 oldVerticePos及oldTrianglePos 存放mesh.vertice的輩分座標及mesh.triangle的輩分指標位置 2. 在進行輩分以及生成新的值時,須注意起始的陣列指標以及對應的座標 ```c# public void meshGenerate(int count,int Getwidth) { if (down == 0)//讓list有值 { verticeBox.Add(0); triangleBox.Add(0); down = 1; } ``` Voldlen = verticeBox[count];//目前的total vertice個數 vertice = new Vector3[GetPointPos.Count + Voldlen]; uv = new Vector2[GetPointPos.Count + Voldlen];//texture tangents = new Vector4[GetPointPos.Count + Voldlen]; Vector4 tangent = new Vector4(1f, 0f, 0f, -1f); //舊座標備分 for (int i = 0; i < Voldlen; i++) { vertice[i] = oldVerticePos[i]; uv[i].x = oldVerticePos[i].x; uv[i].y = oldVerticePos[i].y; tangents[i] = tangent; } //新的座標 for (int i = Voldlen,j = 0; i < GetPointPos.Count + Voldlen; i++,j++) { vertice[i] = GetPointPos[j]; uv[i].x = GetPointPos[j].x;//Vector3轉Vector2 uv[i].y = GetPointPos[j].y; tangents[i] = tangent; } mesh.vertices = vertice;//mesh網格點生成 mesh.uv = uv; mesh.tangents = tangents; int point = ((GetPointPos.Count / (3 + (Getwidth - 1) * 2) - 1)) * 2 * Getwidth;//計算網格數 triangles = new int[point * 6 + triangleBox[count]];//計算需要多少三角形點座標 //備份三角形 for (int i = 0; i < triangleBox[count]; i++) { triangles[i] = oldTrianglePos[i]; } int t = triangleBox[count];//初始三角形 int k = 0;//累加 for (int vi = verticeBox[count], x = 1; x <= point; x++, vi += k) { t = SetQuad(triangles, t, vi, vi + 1, vi + 3 + (2 * (Getwidth - 1)), vi + 4 + (2 * (Getwidth - 1))); if (x % (Getwidth * 2) != point % (Getwidth * 2)) k = 1; //在同一行 else k = 2; //對vi的累加 (需換行時) } mesh.triangles = triangles; mesh.RecalculateBounds(); mesh.RecalculateNormals(); oldVertice = vertice.Length; oldTriangle = triangles.Length; //收集長度&舊的位置 RecordValue(oldVertice, oldTriangle,mesh.vertices,mesh.triangles); } ``` 3. RecordValue()函式,負責收集長度以及需輩分座標位置。當偵測到滑鼠放開時,先將之前的輩分內容先清空,在加入新的輩分內容進去(AddRange())以及新長度(Add())。 ```c# //紀錄 vertice & triangle長度的矩陣 public List<int> verticeBox = new List<int>(); public List<int> triangleBox = new List<int>(); //輩分座標 public List<Vector3> oldVerticePos = new List<Vector3>(); public List<int> oldTrianglePos = new List<int>(); public void RecordValue(int verticeLength,int triangleLength,Vector3[] verticePos,int[] trianglePos) { if (Input.GetMouseButtonUp(0)) { oldVerticePos.Clear();//需先清空原有的座標 oldTrianglePos.Clear(); verticeBox.Add(verticeLength); triangleBox.Add(triangleLength); oldVerticePos.AddRange(verticePos);//重新新增上去 oldTrianglePos.AddRange(trianglePos); } } ```