在游戏引擎场景中的3D物体是由一定数量的点、面组成的,如下图:
要使这些物体变形就是改变3D物体每个顶点状态。
1.首先在Unity场景中增加一个球体,如下图
3D组件默认拥有MeshFilter、meshRenderer、Collider组件,分别用来获取Mesh顶点、渲染物体、返回射线碰撞位置信息
新建物体形变脚本MeshDeformer,并在游戏开始时缓存形变的网格和顶点信息,新建完成后,将脚本挂载到要形变的物体上。
public class MeshDeformer : MonoBehaviour
{//需要变形的mesh网格Mesh deformingMesh;//顶点原始位置,移动后的顶点位置Vector3[] originalVertices, displacedVertices;void Start(){//获取变形网格deformingMesh = GetComponent<MeshFilter>().mesh;//获取变形网格的所有顶点位置originalVertices = deformingMesh.vertices;displacedVertices = new Vector3[originalVertices.Length];for (int i = 0; i < originalVertices.Length; i++){displacedVertices[i] = originalVertices[i];}}
}
新建输入脚本MeshFormerInput,并在Update函数中检测输入
public class MeshDeformerInput : MonoBehaviour
{void Update(){if (Input.GetMouseButton(0)){HandleInput();}}void HandleInput(){//获得从相机位置往鼠标点击屏幕点方向的射线Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(inputRay, out hit)){MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();if (deformer){Debug.Log(deformer);}}}
}
如果一切顺利则,在场景中点击物体就会在Unity控制台中打印获取到的组件信息
在MeshDeformer脚本中增加施加力的作用效果的方法,
/// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){for (int i = 0; i < displacedVertices.Length; i++){//施加力到顶点AddForceToVertex(i, point, force);}}/// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){}
网格变形是因为对其每个顶点施加了力。当顶点被推时,它们会获得速度。随着时间的推移,顶点都会改变它们的位置。如果所有顶点都受到完全相同的力,则整个对象将移动而不改变其形状,所以我们需要知道每个顶点的变形力的方向和距离,两者都可以从指向力点到顶点位置的向量中导出。使用平方反比定律找到衰减的力,只需将原始力除以距离的平方,就可以得到衰减的力。
/// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){//计算施加的力Vector3 pointToVertex = displacedVertices[i] - point;//实际上,如果只用F/d*d,再d=0时,衰减的力会变成无穷大,所以除以 1 加上距离的平方,保证了当距离为零时力处于全强度状态。float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);float velocity = attenuatedForce * Time.deltaTime;//a = F/m,忽略每个质点的质量,将质量都设为1,则dv = FdtvertexVelocities[i] += pointToVertex.normalized * velocity;}
计算了每个顶点的速度接下来,在MeshDeformer脚本的Update方法中移动顶点
void Update(){for (int i = 0; i < displacedVertices.Length; i++){UpdateVertex(i);}deformingMesh.vertices = displacedVertices;deformingMesh.RecalculateNormals();}/// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];displacedVertices[i] += velocity * Time.deltaTime;}
一切顺利会得到以下效果:
增加弹力和阻尼:在MeshFormer.cs中的UpdateVertex中增加弹力和阻尼的计算
/// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];//胡克定律 F = -kx,k是常数,是物体的劲度系数(倔强系数)(弹性系数)x是弹簧的伸长量(或压缩量)//x = displacedVertices[i] - originalVertices[i]//F = -springForce * displacement; Vector3 displacement = displacedVertices[i] - originalVertices[i]; velocity -= displacement * springForce * Time.deltaTime;vertexVelocities[i] = velocity;//通过不断减慢顶点的速度来防止这种永恒的振荡。此阻尼效果可替代阻力、阻力、惯性等//阻尼越高,对象的弹性就越小,看起来越迟缓。//v = velocity(1-damping)velocity *= 1f - damping * Time.deltaTime;displacedVertices[i] += velocity * Time.deltaTime;}
最后处理:
现在的变形体是放在原点的,而变形体的顶点坐标都是模型坐标系的本地坐标,我们通过射线碰碰撞得到的着力点是在世界坐标系下,因此我们需要将二者变换到同一坐标系下进行力的计算。
/// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){point = transform.InverseTransformPoint(point);Debug.DrawLine(Camera.main.transform.position, point);for (int i = 0; i < displacedVertices.Length; i++){AddForceToVertex(i, point, force);}}
物体放缩后,顶点之间的距离会相应的变大或者缩小,如下图:一个球体没有放大和放大两倍的时候的顶点位置。
由上在变形体放缩后需要调整一下每两个顶点之间的作用力,否则用平方反比的计算出来的力,在不同的放缩下,大小会有不同,因此,需要变化三个地方,一个是施加在顶点上的力需要放缩,一个是相互作用力的距离计算时需要放缩,最后一个是顶点移动的距离需要放缩。
最终效果:
完整代码:
public class MeshDeformerInput : MonoBehaviour
{//施加的力public float force = 10f;//力的偏移public float forceOffset = 0.1f;void Update(){if (Input.GetMouseButton(0)){HandleInput();}}void HandleInput(){//获得从相机位置往鼠标点击屏幕点方向的射线Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(inputRay, out hit)){MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();Debug.Log(deformer);if (deformer){Vector3 point = hit.point;point += hit.normal * forceOffset;deformer.AddDeformingForce(point, force);}}}
}
public class MeshDeformer : MonoBehaviour
{//需要变形的mesh网格Mesh deformingMesh;//顶点原始位置,移动后的顶点位置Vector3[] originalVertices, displacedVertices;//顶点的速度Vector3[] vertexVelocities;//弹力public float springForce = 20f;//阻尼public float damping = 5f;//放缩比例float uniformScale = 1f;void Start(){//获取变形网格deformingMesh = GetComponent<MeshFilter>().mesh;//获取变形网格的所有顶点位置originalVertices = deformingMesh.vertices;displacedVertices = new Vector3[originalVertices.Length];for (int i = 0; i < originalVertices.Length; i++){displacedVertices[i] = originalVertices[i];}vertexVelocities = new Vector3[originalVertices.Length];}/// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){point = transform.InverseTransformPoint(point);Debug.DrawLine(Camera.main.transform.position, point);for (int i = 0; i < displacedVertices.Length; i++){AddForceToVertex(i, point, force);}}/// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){//计算施加力的方向Vector3 pointToVertex = displacedVertices[i] - point;pointToVertex *= uniformScale;//实际上,如果只用F/d*d,再d=0时,衰减的力会变成无穷大,所以除以 1 加上距离的平方,保证了当距离为零时力处于全强度状态。float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);float velocity = attenuatedForce * Time.deltaTime;//a = F/m,忽略每个质点的质量,将质量都设为1,则dv = FdtvertexVelocities[i] += pointToVertex.normalized * velocity;}void Update(){uniformScale = this.transform.localScale.x;for (int i = 0; i < displacedVertices.Length; i++){UpdateVertex(i);}deformingMesh.vertices = displacedVertices;deformingMesh.RecalculateNormals();}/// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];//胡克定律 F = -kx,k是常数,是物体的劲度系数(倔强系数)(弹性系数)x是弹簧的伸长量(或压缩量)//x = displacedVertices[i] - originalVertices[i]//F = -springForce * displacement; Vector3 displacement = displacedVertices[i] - originalVertices[i];displacement *= uniformScale;velocity -= displacement * springForce * Time.deltaTime;vertexVelocities[i] = velocity;//通过不断减慢顶点的速度来防止这种永恒的振荡。此阻尼效果可替代阻力、阻力、惯性等//阻尼越高,对象的弹性就越小,看起来越迟缓。//v = velocity(1-damping)velocity *= 1f - damping * Time.deltaTime;displacedVertices[i] += velocity * Time.deltaTime / uniformScale;}
}
参考链接:
网格变形,Unity C# 教程 (catlikecoding.com)