如何製做一個小球,能擠壓變形,並有彈力恢復原形?php
經過修改mesh的頂點位置來作,應該算是頂點動畫的範圍了吧(*/ω\*)。html
代碼不會很複雜,主要是理解原理。算法
我這個實現是參考:spring
https://catlikecoding.com/unity/tutorials/mesh-deformation/數組
先上效果,網格圖是側視:函數
建議往下閱讀前,先看一下文檔中關於mesh和頂點的相關概念(Procedural Mesh Geometry下面三個子主題),還有理解向量的概念:工具
https://docs.unity3d.com/Manual/GeneratingMeshGeometryProcedurally.html動畫
擠壓小球的時候,主要受力區域會凹陷,而後迫使其餘部位順着力的方向變形。spa
首先定義一下要變形mesh:3d
public MeshFilter targetMeshFilter; private Mesh targetMesh;
並在start中獲取mesh:
void Start() { targetMesh = targetMeshFilter.mesh; }
觸摸操做用射線來實現,先定義從哪一個相機射出射線:
public Camera mainCamera;
再在update寫下射線代碼:
void Update() { if (Input.GetMouseButton(0)) { if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out RaycastHit hitInfo)) { } } }
定義一些用到的數組:
private Vector3[] originalVertices, displacedVertices, vertexVelocities; private int verticesCount;
在start中初始化,從上往下,分別含義是這個mesh頂點數量,初始的頂點位置,頂點下一步的位置,頂點移動速度:
void Start() {
... verticesCount = targetMesh.vertices.Length; originalVertices = targetMesh.vertices; displacedVertices = targetMesh.vertices; vertexVelocities = new Vector3[verticesCount]; }
在觸摸到小球時,先定義一下要用到的觸摸力度,發力點偏移量:
public float force = 10; public float forceOffset = 0.1f;
擠壓小球並回彈,因此小球須要有觸摸力度表示變形程度,發力點偏移量表示做用點的位置,0偏移就是在球體表面,值越高越偏離球表面。
在射線觸碰成功的代碼裏面添加如下:
Vector3 actingForcePoint = targetMeshFilter.transform.InverseTransformPoint(hitInfo.point + hitInfo.normal * forceOffset);//發力點指向球的本地座標向量 for (int i = 0; i < verticesCount; i++) { Vector3 pointToVertex = displacedVertices[i] - actingForcePoint;//做用力點指向當前頂點位置的向量 float actingForce = force / (1f + pointToVertex.sqrMagnitude);//做用力大小 vertexVelocities[i] += pointToVertex.normalized * actingForce * Time.deltaTime;//頂點速度向量 }
解釋一下,就是頂點的座標位置都是相對於這個模型的座標不是世界座標,因此觸摸的時候,發力點座標要轉換成相對座標。
hitInfo.normal也就是觸摸點的法線,是垂直於觸摸點並指向外面的,forceOffset值表示做用點離表面有多高,1表示法線長度那麼高,0表示在表面,以下圖,棕色表示觸摸點,紅色表示法線。
還有做用力actingForce,球的各個頂點受力,若是是一致的,那麼球就是平行飛出去,而不是變形了, 觸摸點受力最大,而後輻射出去逐漸衰減。這裏用了一條函數來計算,y=force/(1+x^2),當force值爲10,爲5,爲1時函數圖像以下,能夠根據本身的狀況調整衰減力度,這裏用的函數圖像繪製工具地址是:https://zh.numberempire.com/graphingcalculator.php ,百度隨便找的。
有了做用力,還要有回彈以恢復形狀,和阻力來消除做用力和彈力,還有從新把頂底從新賦值,並從新計算法線(影響光照):
定義一下:
public float springForce = 20f; public float damping = 5f;
而後在update中:
for (int i = 0; i < verticesCount; i++) { vertexVelocities[i] += (originalVertices[i] - displacedVertices[i]) * springForce * Time.deltaTime;//加上+頂點當前位置指向頂點初始位置的速度向量==回彈力 vertexVelocities[i] *= 1f - damping * Time.deltaTime;//乘上阻力 displacedVertices[i] += vertexVelocities[i] * Time.deltaTime;//算出頂點的下一個位置 } targetMesh.vertices = displacedVertices; targetMesh.RecalculateNormals();
到此,就完成了,下面是完整代碼:
1 using UnityEngine; 2 3 public class DeformationToucher : MonoBehaviour 4 { 5 public MeshFilter targetMeshFilter; 6 private Mesh targetMesh; 7 8 public Camera mainCamera; 9 10 private Vector3[] originalVertices, displacedVertices, vertexVelocities; 11 12 private int verticesCount; 13 14 public float force = 10; 15 public float forceOffset = 0.1f; 16 public float springForce = 20f; 17 public float damping = 5f; 18 19 void Start() 20 { 21 targetMesh = targetMeshFilter.mesh; 22 23 verticesCount = targetMesh.vertices.Length; 24 25 originalVertices = targetMesh.vertices; 26 displacedVertices = targetMesh.vertices; 27 vertexVelocities = new Vector3[verticesCount]; 28 } 29 30 void Update() 31 { 32 if (Input.GetMouseButton(0)) 33 { 34 if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out RaycastHit hitInfo)) 35 { 36 Vector3 actingForcePoint = targetMeshFilter.transform.InverseTransformPoint(hitInfo.point + hitInfo.normal * forceOffset);//發力點指向球的本地座標向量 37 38 for (int i = 0; i < verticesCount; i++) 39 { 40 Vector3 pointToVertex = displacedVertices[i] - actingForcePoint;//做用力點指向當前頂點位置的向量 41 42 float actingForce = force / (1f + pointToVertex.sqrMagnitude);//做用力大小 43 vertexVelocities[i] += pointToVertex.normalized * actingForce * Time.deltaTime;//頂點速度向量 44 } 45 } 46 } 47 48 for (int i = 0; i < verticesCount; i++) 49 { 50 vertexVelocities[i] += (originalVertices[i] - displacedVertices[i]) * springForce * Time.deltaTime;//加上+頂點當前位置指向頂點初始位置的速度向量==回彈力 51 vertexVelocities[i] *= 1f - damping * Time.deltaTime;//乘上阻力 52 displacedVertices[i] += vertexVelocities[i] * Time.deltaTime;//算出頂點的下一個位置 53 } 54 55 targetMesh.vertices = displacedVertices; 56 targetMesh.RecalculateNormals(); 57 } 58 }
這是我第一次接觸mesh和頂點方面的計算,若是發現有什麼錯漏,請指出。
歡迎交流。
轉載註明出處。