# 髮片模型_進階
###### 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
直髮參數:髮尾平、髮尾尖、寬度、厚度
波浪捲髮參數:髮尾平、髮尾尖、寬度、捲度、厚度
螺旋捲髮參數:髮尾平、髮尾尖、寬度、捲度、厚度