如何制作一個小球,能擠壓變形,並有彈力恢復原形?
通過修改mesh的頂點位置來做,應該算是頂點動畫的范圍了吧(*/ω\*)。
代碼不會很復雜,主要是理解原理。
我這個實現是參考:
https://catlikecoding.com/unity/tutorials/mesh-deformation/
先上效果,網格圖是側視:
建議往下閱讀前,先看一下文檔中關於mesh和頂點的相關概念(Procedural Mesh Geometry下面三個子主題),還有理解向量的概念:
https://docs.unity3d.com/Manual/GeneratingMeshGeometryProcedurally.html
擠壓小球的時候,主要受力區域會凹陷,然后迫使其他部位順着力的方向變形。
首先定義一下要變形mesh:
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和頂點方面的計算,如果發現有什么錯漏,請指出。
歡迎交流。
轉載注明出處。