找回密码
 立即注册
查看: 380|回复: 1

[笔记] 在Unity使用Verlet积分实现逼真的绳索(仅3行核心代码)

[复制链接]
发表于 2022-11-15 11:39 | 显示全部楼层 |阅读模式
效果



背景与原理

此种绳索的实现方式是使用韦尔莱(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[j];
        //进行约束
        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积分的使用,绳索模拟只是较为初步的,我们还可以应用到,布料,毛发,液体,软体等实现上。可以关注专栏观看后续的更新!



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-11-15 11:46 | 显示全部楼层
效果不错
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-21 20:53 , Processed in 0.092510 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表