# 髮片模型_進階 ###### tags: `髮片model` ### 髮片模型生成研究步驟 (一)髮片模型構想 (二)Unity Mesh實作(架構) (三)三種髮片樣式演算公式 (四)髮片模型生成與VR手把 ### (一)髮片模型構想 預期結果,(一)頭髮模型風格,以VRoid的動漫式風格為目標,(二) 髮片模型有三種樣式,分別是直髮、波浪捲髮及螺旋捲髮,髮片會依不同長度、寬度、厚度等參數挑整呈現不一樣的效果,(三)模型即時更新計算,讓使用者可即時看到效果,以利創作。 ### (二)Unity Mesh實作 **Mesh設定步驟** **step1:** 建立MeshFilter以及MeshRenderer組件,並將mesh網格加入進MeshFilter中 ``` GetComponent<MeshFilter>().mesh = mesh = new Mesh(); GetComponent<MeshRenderer>().material = hairColor; //加入material ``` **step2:** 設定vertice、uv、nomal、tangent、triangle **mesh.vertice陣列:** 將在PosGenerate.cs程式中收集的座標給到mesh.vertice ```C# Vector3[] vertice; for (int i = 0,j=0;i<GetPointPos.Count;i++,j++) { vertice[i] = GetPointPos[j]; } mesh.vertices = vertice; ``` **mesh.uv陣列:** 貼圖設定(Vector2座標,範圍在0~1) ```C# uv = new Vector2[GetPointPos.Count]; int len = GetPointPos.Count / 4; float TexWidth = 0.8f; for (int i = 0, x = 0; i < len; i++) { for (int j = 1; j <= 4; j++) { uv[x] = new Vector2(TexWidth / 4 * j, 1.0f / len * i); x++; } } mesh.uv = uv; ``` **mesh.tangent陣列:** ```C# tangents = new Vector4[GetPointPos.Count]; for (int i = 0,j=0;i<GetPointPos.Count;i++,j++) { tangents[i] = new Vector4(1f, 0f, 0f, -1f); } mesh.tangents = tangents; ``` **mesh.triangle陣列:** 以int型態指定存取vertice[某index]中的資料(座標) ```C# int point = GetPointPos.Count - 2; triangle = new int[point * 6]; int t = 0; for (int i = 1, vi = 0; i <= point - 2; i++, vi++) { if (i % 4 != 0) t = SetQuad(triangle, t, vi, vi + 1, vi + 4, vi + 5); else t = SetQuad(triangle, t, vi, vi - 3, vi + 4, vi + 1); } int vii = 0; t = SetQuad(triangle, t, vii + 2, vii + 1, vii + 3, vii); vii = GetPointPos.Count - 1; t = SetQuad(triangle, t, vii - 1, vii, vii - 2, vii - 3); mesh.triangles = triangle; ``` **mesh.nomal用mesh.RecalculateNormals()函式自動計算代替** 完整 MeshGenerate.cs 程式碼 ```C# using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class MeshGenerate : MonoBehaviour { private Mesh mesh; public static Material GethairColor; Shader HairShader; Vector3[] vertice; Vector2[] uv; Vector4[] tangents; int[] triangle; public void GenerateMesh(List<Vector3> GetPointPos,int Getwidth) { GethairColor = GetComponent<Renderer>().material; HairShader = Shader.Find("Diffuse Fast"); GethairColor.shader = HairShader; GethairColor.color = new Color(222f/255,184f/255,135f/255); GetComponent<MeshFilter>().mesh = mesh = new Mesh(); GetComponent<MeshRenderer>().material = GethairColor; mesh.name = "HairModel"; vertice = new Vector3[GetPointPos.Count]; uv = new Vector2[GetPointPos.Count]; tangents = new Vector4[GetPointPos.Count]; for (int i = 0,j=0;i<GetPointPos.Count;i++,j++) { vertice[i] = GetPointPos[j]; tangents[i] = new Vector4(1f, 0f, 0f, -1f); } //加厚度 int len = GetPointPos.Count / 4; float TexWidth = 0.8f; for (int i = 0, x = 0; i < len; i++) { for (int j = 1; j <= 4; j++) { uv[x] = new Vector2(TexWidth / 4 * j, 1.0f / len * i); x++; } } mesh.vertices = vertice; mesh.uv = uv; mesh.tangents = tangents; int point = GetPointPos.Count - 2; triangle = new int[point * 6]; int t = 0; for (int i = 1, vi = 0; i <= point - 2; i++, vi++) { if (i % 4 != 0) t = SetQuad(triangle, t, vi, vi + 1, vi + 4, vi + 5); else t = SetQuad(triangle, t, vi, vi - 3, vi + 4, vi + 1); } int vii = 0; t = SetQuad(triangle, t, vii + 2, vii + 1, vii + 3, vii); vii = GetPointPos.Count - 1; t = SetQuad(triangle, t, vii - 1, vii, vii - 2, vii - 3); mesh.triangles = triangle; mesh.RecalculateBounds(); mesh.RecalculateNormals(); } 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; } } ``` ### (三)三種髮片樣式演算公式 **三種髮片樣式:直髮、波浪捲髮、螺旋捲髮** 目的:設計三種髮片風格演算公式,來調配整個Mesh.vertice頂點位置 ### 實作步驟: **(一)抓取髮片生成座標,配合刷子物件以及VR手把** 1. 抓取刷具(UI道具)座標點,為生成點座標值。 2. 每隔length=0.05f的距離抓一次點 3. 以正規化糾正移動速度造成的誤差距離 **以下是抓取座標的程式架構** ```C# if (TriggerDown == 0 && Gather1.icon == 1) //沒被按下 { if (TriggerClick.GetStateDown(Pose.inputSource)) //偵測被按下的瞬間 { OldPos = NewPos = HairPos.transform.position; PosCreater.VectorCross(HairPos.transform.up, HairPos.transform.forward, HairPos.transform.right); PointPos.Add(OldPos); TriggerDown = 1; } } if (TriggerDown == 1) //被按下 { NewPos = HairPos.transform.position; float dist = Vector3.Distance(OldPos, NewPos); //計算舊點到新點,位置的距離 if (dist > length) //距離大於設定的長度 { //正規化 Vector3 NormaizelVec = NewPos - OldPos; NormaizelVec = Vector3.Normalize(NormaizelVec); NormaizelVec = new Vector3(NormaizelVec.x * length, NormaizelVec.y * length, NormaizelVec.z * length); NewPos = NormaizelVec + OldPos; PointPos.Add(NewPos); '''' OldPos = NewPos; } '''' if (TriggerClick.GetStateUp(Pose.inputSource)) //放開 { '''' PointPos.Clear(); direction.Clear(); Gather1.GridState = false; TriggerDown = 0; } ``` **(二)配合VR手把,處理髮片生成方向性問題** 在3D空間中分別有世界座標的X、Y、Z三個方向,而每個物件本身也有自己LocalPosition的X、Y、Z軸,我們利用外積公式抓出物件X軸與Z軸在世界座標的位置。並將每個座標的方向性座標存入List保存,用於之後的變形計算。 ```C# public Vector3 cross1, cross2; public List<Vector3> directionA = CreateHair.direction; public void VectorCross(Vector3 up, Vector3 forward, Vector3 right) { cross1 = Vector3.Cross(up, forward);//x cross1.Normalize(); cross2 = Vector3.Cross(up, right);//z cross2.Normalize(); directionA.Add(cross1); //每個座標的方向性座標存入List directionA.Add(cross2); } ``` **(三)髮片即時變形程式架構** 設定三個List資料 GetPointPos → 在CreateHair抓取物件座標 TempPoint → 在變形計算過程中的暫存結構 GetUpdatePointPos → 儲存變形後的座標,並傳遞給MeshGenerate.cs中的Mesh.vertice 直髮參數:髮尾平、髮尾尖、寬度、厚度 波浪捲髮參數:髮尾平、髮尾尖、寬度、捲度、厚度 螺旋捲髮參數:髮尾平、髮尾尖、寬度、捲度、厚度