在Unity使用Verlet积分实现逼真的绳索(仅3行核心代码)
效果背景与原理
此种绳索的实现方式是使用韦尔莱(Verlet)积分方法实现的。它最初被2000年的游戏《杀手:代号47》应用于布娃娃物理并推广,后在电子游戏行业中流行,这种方式帮助《杀手:代号47》成为首个实现高级布娃娃物理的电子游戏。
Verlet绳索物理背后的原理是使用众多的粒子来表示整条绳索,每组粒子之间拥有持续的距离约束。每个粒子受重力作用表现出物理特性,在每一步积分中,都会更新位置,其它粒子根据相连的粒子进行距离约束。
两端的粒子是固定的不受重力影响。中间的自由粒子受重力影响下坠,相连的粒子受距离约束。
实现步骤
1.数据的建模
首先我们应该建立粒子与约束,粒子简单的说就是一个位置,约束简单的来说就是一对儿粒子,我们形象的称呼它为木棍stick,每个木棍的两端各有一个粒子,这里的数据表示如下:
public class Particle
{
public Vector2 position;
public Vector2 oldPosition;
public bool locked;
}
public class Stick
{
public Particle particleA;
public Particle particleB;
public float length;
public Stick(Particle a, Particle b)
{
particleA = a;
particleB = b;
length = (a.position - b.position).magnitude;
}
}
2.Verlet积分与欧拉积分
在之前的原理解释时我们已经知道实现绳索物理的方式是使用Verlet积分,在我们说到积分时,各位同学不要恐惧,它并不是什么令人难以接近的高深数学,我们在这里只是简单的使用它,利用它为我们的游戏效果服务。所以我会在此用简单的语言解释它。
积分,它并不是什么罕见的理论,在游戏开发中,无时无刻都会用到积分。我们在模拟物理时,以离散的时间步长来预测新的位置,把预测的位置应用到物理物体身上,这些在随时间推移的一系列预测,就可以把它视为积分。在刚体动力学当中,我们会用如下的式子模拟重力与加速度。
a=\frac{F}{m}
v_{n+1}=v_{n}+a\cdot\Delta t
p_{n+1}=p_{n}+v\cdot\Delta t
于是我们会得到与Rigidbody一致的重力效果:
红块为欧拉积分实现,白块为Rigidbody组件实现。效果是完全一致的。
这种方式就是我们日常使用的最常见的方式,也被称为欧拉积分。它的特点是我们要时刻的追踪速度与加速度,然后将速度应用到物理物体。这在刚体物理等情况下工作的很好,但是在像绳索,软体,流体等呈粒子状的物理模拟中会变的很复杂和不稳定,因为我们要时刻关注大量粒子之间的相互关系,所以我们就要寻求一种更加简单的积分法,也就是Verlet积分。
Verlet积分不同于欧拉积分要时刻追踪速度,它连速度都不需要追踪,就可以完成与欧拉积分相同的事情,这是真的。在Verlet积分中,我们唯一要记录的东西只有这个粒子的上一个位置,仅此而已。这种简单的方式极大的切合此类众多粒子相互作用的物理模拟,而无需处理大量复杂的数学问题。Verlet积分公式如下:
p_{n+1}=p_{n}+(p_{n}-p_{n-1})+\Delta t \cdot\Delta t\cdot a
p_{n-1}=p_{n}
就是如此简单,不同于欧拉积分,这是一种无速度表示,速度是隐含的,所以速度和位置很难不同步,它之所以有效是因为 p_{n}+(p_{n}-p_{n-1}) 是当前速度的近似值,也就是上一个时间步长所经过的距离。落实到代码上如下:
//每个时间步都遍历所有粒子
for (int i = 0; i < particles.Count; i++)
{
Particle p = particles;
if (p.locked == false)
{
Vector2 temp = p.position;
//Verlet积分真正发挥作用的地方
p.position = p.position + (p.position - p.oldPosition) + Time.fixedDeltaTime * Time.fixedDeltaTime * new Vector2(0, -gravity);
//存储旧位置
p.oldPosition = temp;
}
}
3.进行约束
此刻没经过约束时的粒子是完全自由的,受重力影响后,也无法形成绳索形态。我们要做的就是让一组粒子时刻都在拉伸与收缩,超过原始长度时要收缩,小于原始长度时要拉伸,拉伸与收缩靠迭代次数控制,也就是说,迭代次数越多,在视觉上,绳索刚性越强,迭代次数越少,绳索的弹性越强。落实到代码上如下:
//迭代次数,控制绳索的刚性
for (int i = 0; i < stiffness; i++)
{
for (int j = 0; j < sticks.Count; j++)
{
Stick stick = sticks;
//进行约束
Vector2 delta = stick.particleB.position - stick.particleA.position;
float deltaLength = delta.magnitude;
float diff = (deltaLength - stick.length) / deltaLength;
if (stick.particleA.locked == false)
stick.particleA.position += 0.5f * diff * delta;
if (stick.particleB.locked == false)
stick.particleB.position -= 0.5f * diff * delta;
}
}
4.最终渲染
最后,我们用LineRender进行简单的初步渲染,来查看绳索的效果。
private void Rendering()
{
for (int i = 0; i < particles.Count; i++)
{
lineRenderer.SetPosition(i, particles.position);
}
}
源码
其它
此处代码是为了观看而不是为了性能,之后可以依据性能需求自己更改,例如对于距离的平方根运算,建立面向数据的平稳的粒子结构以实现更好的CPU缓存与预测等。
我们只使用了LineRenderer进行了初步简陋的渲染,如果想了解如何真正的渲染绳索,可以借鉴Unreal的绳索文档。
在之前,我还写过另一篇绳索模拟的文章,那是一个更为简单的模拟,不同于本篇的物理方法,那篇文章完全是用数学方法模拟的TopDown视角的无重力绳索。
Verlet积分的使用,绳索模拟只是较为初步的,我们还可以应用到,布料,毛发,液体,软体等实现上。可以关注专栏观看后续的更新!
效果不错
页:
[1]