实时物理模拟讲解 · 写给图形程序、技术美术和想自己动手的人
从 PBD
到 GNN + XPBD
布料、绳子、软体,本质上都是一堆点和一堆规则:这两点之间的距离要保持,这几点围成的体积要守恒。这门课从“为什么硬的东西难模拟”讲起,推出 PBD 和 XPBD,讲完约束、碰撞、子步和并行,带你写一个自己的 XPBD 布料;然后引入图神经网络,讲清楚网络和求解器怎样分工。
00比喻
这片珠帘每一帧在做什么
封面那片珠帘是 17 根绳子、204 个质点。它没有任何关于“绳子”的知识,只有两条规则:每个质点受重力往下掉;相邻两点之间的距离要保持静止长度(打开弯曲约束时,隔一个点的两点之间也要保持距离)。
每一帧,程序做三件事。第一,预测:假装没有任何边,每个点按自己的速度和重力往前走一步。第二,修正:挨个检查每一段,被拉长了就把两端往里拉,被压短了就往外推,碰到球就推出球外。第三,反推速度:用这一帧的实际位移除以时间,作为下一帧的速度。
这就是基于位置的动力学(Position Based Dynamics,PBD)和它的改进版 XPBD 的全部骨架。整门课都在回答这三步里藏着的问题:为什么不直接算力?修正量怎么分给两端?“硬”和“软”怎么控制?修正的顺序有没有关系?一帧算不完怎么办?
你在封面上调的两个参数,正好对应后面两个关键概念:柔度(第 3 章)决定一段绳子有多容易被拉长;子步(第 4 章)决定一帧被切成几小段来算。
预测、按约束修正、用位移算速度:XPBD 的一帧就这三步,其余都是细节。
要点
预测、按约束修正、用位移算速度:XPBD 的一帧就这三步,其余都是细节。
00速查
术语表:这门课会反复出现的词
| 词 | 一句话 | 在哪一章展开 |
|---|---|---|
| 质点 | 有位置、速度、质量的点。布料、绳子、软体都被离散成质点 | 2.1 节 |
| 逆质量 w | 1/m。固定点的 w = 0,修正量按 w 分配 | 2.3 节 |
| 约束 C(x) | 一个标量函数,等于 0 表示规则被满足,比如 |x₁ − x₂| − L | 2.2 节、第 5 章 |
| 投影 | 把位置挪到满足约束的地方,挪动量尽量小、按质量分配 | 2.2 节 |
| 迭代 | 把所有约束挨个投影一遍叫一次迭代 | 2.4 节 |
| 柔度 α | 刚度的倒数,单位 m/N(距离约束)。α = 0 表示完全不可伸长 | 3.1 节 |
| 拉格朗日乘子 λ | 每个约束在这一步里累积的“力 × 时间²”,XPBD 靠它记住已经施加了多少力 | 3.2 节 |
| 子步 | 把一帧切成 n 段,每段完整地预测、投影、更新速度 | 第 4 章 |
| 残差 | 当前解离方程的解还差多少,用来判断收敛 | 3.6 节 |
| 高斯-赛德尔 / 雅可比 | 逐个投影并立即使用新位置 / 全部算完再一起应用 | 2.4 节、7.1 节 |
| 图神经网络(GNN) | 在图上运行的网络:节点沿边交换消息,更新自己的状态 | 第 8 章 |
| 滚动(rollout) | 把网络的输出当作下一帧的输入,连续推很多帧 | 8.5 节 |
表里的公式符号不需要现在记住。每个词第一次正式出场时,都会先用一句白话说清楚它是什么。
看到不认识的词,回到这张表;看到“x.y 节”,点目录就能跳过去。
要点
看到不认识的词,回到这张表;看到“x.y 节”,点目录就能跳过去。
00路线
怎么读:三条路线
全课 7 个页签,前 5 个讲 XPBD,后 2 个讲 GNN 和组合。按你的目标选一条路线:
只想弄懂原理:页签 1 → 2 → 3,读完第 3 章就能讲清楚 XPBD 比 PBD 好在哪里。交互图都动手拖一拖,比读公式快。
要自己写一个:按顺序读完页签 1 到 5,然后照着附录 A 的八个里程碑写。每个里程碑都有完成标准,附录末尾有坑表和验证清单。
关心学习方法:至少读完第 2、3 章和第 7.1 节(理解迭代和并行),再读页签 6、7。第 9 章的每一种组合,都默认你知道 XPBD 的投影和 λ 是什么。
每节末尾有一句加粗的收束句。只想快速复习,可以只看这些句子。公式可以用右上角的“公式”按钮隐藏,白话解释始终保留。
先 XPBD 后 GNN:不懂求解器在保证什么,就分不清网络该负责什么。
只想弄懂原理
:页签 1 → 2 → 3,读完第 3 章就能讲清楚 XPBD 比 PBD 好在哪里。
要自己写一个
:按顺序读完页签 1 到 5,然后照着附录 A 的八个里程碑写。
关心学习方法
:至少读完第 2、3 章和第 7.1 节(理解迭代和并行),再读页签 6、7。
要点
先 XPBD 后 GNN:不懂求解器在保证什么,就分不清网络该负责什么。
01
为什么难:刚度和时间步
硬的东西难模拟,不是因为算得慢,而是因为显式积分在大刚度、长步长下会发散。
开场那片珠帘里,每一段绳子都想保持自己的长度。最直接的写法是把每条边当成弹簧:拉长了就产生拉力,按牛顿定律算加速度,再一步步积分。
这一章说明这种写法为什么在游戏里走不通:越硬的弹簧需要越小的步长,而游戏一帧只有 16 毫秒。然后引出另一种思路:不算力,直接改位置。
1.1交互图
一根弹簧怎样把模拟炸掉
最直观的模拟写法:把每条边当成弹簧,拉长了产生拉力 F = −k·Δx,按 a = F/m 算加速度,然后积分。最简单的积分叫显式欧拉:用这一刻的速度更新位置,用这一刻的加速度更新速度。
下面这个交互图只有一根弹簧、一个质点,初始拉开 1 个单位放手。真实的答案是永远以振幅 1 来回振动。三种积分方法各算 3 秒:
显式欧拉不管刚度多小都会慢慢放大振幅,因为它每一步都在用“过时”的信息往前冲,能量只增不减。半隐式欧拉(先更新速度,再用新速度更新位置)好得多:只要 ω·h < 2 就稳定,其中 ω = √(k/m) 是弹簧的固有角频率,h 是步长。
问题在于游戏里的布料。一块布的质点只有几克重,边长几厘米,要“几乎拉不长”,k 得非常大。算一下:m = 0.01 kg,k = 10⁴ N/m,ω = 1000 rad/s。h = 1/60 秒时 ω·h ≈ 17,远超 2。要稳定,步长得小于 2 毫秒,一帧至少要切九次;k 再大十倍,就要切二十七次以上。
弹簧越硬、步长越长,显式积分越容易发散:硬度和帧率直接冲突。
数值积分的稳定性条件为标准结论;交互图为本页的数值实验
1.2推导
隐式积分:稳定,但每步要解一个大方程
经典的解法是隐式欧拉:用这一步结束时的力来更新速度,而不是开始时的力。
下一步的速度 = 这一步的速度 + 步长 × 下一步位置上的加速度;下一步的位置 = 这一步的位置 + 步长 × 下一步的速度。
vn+1 = vn + h · M−1 f(xn+1) xn+1 = xn + h · vn+1
- M
- 质量矩阵(对角线上是每个质点的质量)
- f(x)
- 位置 x 上的总力,未知量 xn+1 出现在等号两边
未知量出现在等号两边,没法直接算,要解一个方程组。Baraff 和 Witkin 1998 年把它用在布料上,从此大步长布料模拟成为可能。它的优点是无条件稳定:步长再大也不会炸。
代价有两个。第一,每一步都要解一个规模等于自由度数量的线性方程组(非线性还要牛顿迭代),实现复杂,求解成本高。第二,隐式欧拉有数值阻尼:步长越大,能量损失越多,布料看起来像在糖浆里。你在 1.1 节的交互图里已经看到了:XPBD 那条线稳定但振幅在衰减,因为 XPBD 本质上就是在近似解隐式欧拉。
游戏需要的是:稳定、实现简单、每一步的成本可控,最好还能并行。这就引出了基于位置的思路。
隐式积分用解方程换来稳定;游戏需要更便宜、更简单的稳定方法。
Baraff & Witkin, Large Steps in Cloth Simulation, SIGGRAPH 1998;隐式欧拉的性质为标准结论
vn+1 = vn + h · M−1 f(xn+1) xn+1 = xn + h · vn+1
下一步的速度 = 这一步的速度 + 步长 × 下一步位置上的加速度;下一步的位置 = 这一步的位置 + 步长 × 下一步的速度。
要点
隐式积分用解方程换来稳定;游戏需要更便宜、更简单的稳定方法。
1.3比喻
换个问题:不算力,直接改位置
回到那块布。我们其实并不关心每条边上的力具体是多少牛顿,我们关心的是结果:每条边的长度别偏离静止长度太多。
Müller 等人 2007 年的 PBD 论文就是从这个观察出发的:既然要的是“位置满足约束”,那就不经过“力 → 加速度 → 速度 → 位置”这条链,直接把位置挪到满足约束的地方,然后用位置的变化反推速度。
这样做有三个直接的好处。一,无条件稳定:投影只会把点往约束允许的方向挪,挪动量有界,不会像弹簧力那样越积越大。二,可控:想让某个点跟着骨骼走,直接把它的位置设过去,逆质量设为 0 就行。三,简单:每个约束独立处理,不需要组装和求解大矩阵。
代价也很明确,后面会一个个讲:硬度取决于迭代次数(2.5 节)、修正的顺序会影响结果(2.4 节)、速度是“反推”出来的,所以修正过的位移会变成速度(9.3 节会再遇到这个问题)。
PBD 跳过力,直接把位置投影回约束:稳定、可控、简单,代价是“硬度”变得不那么物理。
Müller et al., Position Based Dynamics, 2007
要点
PBD 跳过力,直接把位置投影回约束:稳定、可控、简单,代价是“硬度”变得不那么物理。
页签 2 / 7
PBD:先预测,再投影
PBD 的三步循环、单个约束的投影推导、质量的作用、高斯-赛德尔,以及它的硬伤:硬度随迭代次数变。
02
PBD:先预测,再投影
PBD 的一帧只有三步:按惯性预测位置、把位置投影回约束、用位移反推速度。
上一章的结论是:弹簧力加显式积分,硬了就炸。PBD 换了一个问题:既然我们最终关心的是位置满足约束,那就直接去改位置。
这一章把 PBD 的循环拆开:预测、投影、速度更新;一个约束怎么投影、质量怎么参与、许多约束怎么一个个解。最后指出 PBD 的硬伤:它的“硬度”不是材料属性。
2.1流程
PBD 的一帧:预测、投影、更新速度
PBD 的主循环一共十来行。先看伪代码,再逐行解释:
for each particle i:
v[i] += h * gravity // 外力只影响速度
p[i] = x[i] + h * v[i] // 预测位置:假装没有约束
for iter in 1..N: // 求解器迭代
for each constraint c:
project(c, p) // 把 p 挪到满足 c 的地方
for each particle i:
v[i] = (p[i] - x[i]) / h // 用实际位移反推速度
x[i] = p[i]第一段:预测。每个质点按速度和外力(重力、风)往前走一步,得到预测位置 p。这一步完全不考虑约束,相当于半隐式欧拉。
第二段:投影。对每个约束,把相关质点的预测位置挪到满足约束的地方。所有约束挨个处理一遍叫一次迭代,一般做几次到几十次。
第三段:更新速度。新速度等于这一步的实际位移除以步长。约束把点往回拉的那部分位移,自动变成了速度的变化,相当于约束力的冲量。这是 PBD 不需要显式计算力的关键。
注意速度更新放在投影之后,并且用的是投影后的位置。如果把它放到投影前,约束就不会影响速度,绳子会被“拉住”却保留着下坠的速度,下一帧又冲出去。
外力进速度,速度进预测,约束改位置,位移回速度:这个顺序不能乱。
Müller et al., Position Based Dynamics, 2007(算法 1)
第一段:预测
每个质点按速度和外力(重力、风)往前走一步,得到预测位置 p。
第二段:投影
对每个约束,把相关质点的预测位置挪到满足约束的地方。
第三段:更新速度
新速度等于这一步的实际位移除以步长。
要点
外力进速度,速度进预测,约束改位置,位移回速度:这个顺序不能乱。
2.2推导
一个约束怎么投影
约束用一个标量函数 C(x) 表示,C = 0 就是满足。距离约束是 C = |x₁ − x₂| − L。投影要回答:把相关的点挪多少、往哪挪,才能让 C 变成 0?
PBD 的回答有两条。第一,沿梯度方向挪:∇C 指向 C 增长最快的方向,沿它(的反方向)挪,单位挪动量对 C 的改变最大,也就是用最小的挪动修好约束。第二,按逆质量加权(下一节讲为什么)。在一阶近似下解出来:
把约束误差除以“各点梯度长度的平方 × 逆质量”之和,得到一个统一的步长;每个点沿自己的梯度方向、按自己的逆质量挪这个步长。
Δλ = −C(x) / Σj wj |∇jC|² Δxi = wi ∇iC · Δλ
- wj
- 质点 j 的逆质量 1/mj
- ∇iC
- C 对质点 i 位置的梯度(一个向量)
代入距离约束:∇₁C = n,∇₂C = −n,n 是从 x₂ 指向 x₁ 的单位向量,梯度长度都是 1。于是 Δλ = −C/(w₁ + w₂),x₁ 挪 w₁·Δλ·n,x₂ 挪 −w₂·Δλ·n。拉长了(C > 0),两点相向移动;压短了,两点相背移动。
这个公式对任何约束都成立,只要给出 C 和 ∇C。这一点很重要:第 5 章的弯曲、体积、形状约束,全部套用同一个公式,区别只在 C 和 ∇C 怎么写。
投影 = 沿梯度、按逆质量,挪到让约束一阶近似为零的地方。
Müller et al., Position Based Dynamics, 2007(第 3.3 节)
2.3推导
质量为什么按逆质量分
为什么不是两端各挪一半?因为那样会凭空产生动量。
设想一个 1 kg 的小球和一个 100 kg 的大球用绳子连着,绳子被拉长了 10 厘米。如果各挪 5 厘米,相当于小球获得了 5 kg·cm 的“动量变化”,大球获得了反方向的 500 kg·cm,两者不抵消,系统的质心被约束推着走了。现实里绳子的拉力是内力,不会移动质心。
按逆质量分配:小球挪 100/101 × 10 ≈ 9.9 厘米,大球挪 1/101 × 10 ≈ 0.1 厘米。m₁Δx₁ + m₂Δx₂ = 0,质心不动,动量守恒。沿两点连线方向挪,还保证了角动量守恒。
逆质量还带来一个很方便的副作用:w = 0 就是钉死。被钉在角色身上的布料顶点、绳子的固定端,只需要把逆质量设成 0,投影时修正量自然全部落到另一端,不需要任何特殊分支。动画驱动的点(比如跟着骨骼走的衣领)也是这样处理:每一帧把它设到动画位置,逆质量为 0。
反过来,质量比很悬殊的系统很难收敛:重物几乎不动,所有修正量都压在轻的点上,一条长链上的误差只能一段段往上挪。7.1 节的交互图会直接看到这一点。
按逆质量分配修正量,守住动量;逆质量为 0,就是钉住。
Müller et al., 2007(动量守恒的论证)
要点
按逆质量分配修正量,守住动量;逆质量为 0,就是钉住。
2.4算法科普
许多约束:一个接一个地解
一个约束好解,难的是成千上万个约束互相牵制:把这条边修好,可能把相邻的边拉坏。PBD 的做法很朴素:一个一个修,修完立刻更新位置,下一个约束用的就是新位置。所有约束修一遍叫一次迭代,重复若干次,误差逐渐变小。
这种“立即使用最新结果”的做法叫高斯-赛德尔(Gauss-Seidel)式迭代。它的对手是雅可比(Jacobi)式:所有约束都基于同一份旧位置算出修正量,最后一起应用。
两者各有长短。高斯-赛德尔收敛快:在一条链上自上而下修一遍,顶端的修正可以一次传到底端。但它天生串行,第 k 个约束必须等第 k−1 个修完。雅可比可以完全并行,适合 GPU,但每次迭代信息只能传一跳,收敛慢;而且直接相加会“修过头”(两个约束都把同一个点往同一方向推),需要把修正量按约束个数平均,再乘一个松弛系数。
还有一个容易忽视的问题:顺序会影响结果。高斯-赛德尔修到最后的那个约束总是满足得最好,前面的会被后面的拉坏一点。如果每一帧都按同样的顺序,误差会有固定的偏向,比如布料总是往一侧歪。常见的缓解办法是交替正反顺序,或者每帧随机打乱。
7.1 节有一张交互图,直接比较这几种顺序的收敛速度。
逐个修、立即用新位置,收敛快但串行;全部基于旧位置一起修,能并行但慢。
高斯-赛德尔与雅可比为数值线性代数的标准方法;PBD 默认采用高斯-赛德尔式的逐个投影
要点
逐个修、立即用新位置,收敛快但串行;全部基于旧位置一起修,能并行但慢。
2.5交互图
PBD 的硬伤:硬度跟着迭代次数变
想要一根有弹性的绳子,PBD 的做法是给投影乘一个系数 k ∈ [0, 1]:每次只修掉误差的 k 倍。k = 1 是“尽量拉不长”,k 越小越软。
问题是,同一个 k,迭代 4 次和迭代 40 次,效果完全不同:每次迭代都修掉剩余误差的 k 倍,迭代越多,修得越干净,绳子越硬。即使 k = 1,迭代次数不够时绳子也会被重物拉得很长,因为高斯-赛德尔还没收敛。
原论文给了一个补救办法:用 k′ = 1 − (1 − k)1/n 代替 k,n 是迭代次数,让 n 次迭代后的总效果近似一致。它能让迭代次数变化时的硬度大致稳定,但还有两个依赖没解决:一是时间步,帧率从 60 变成 30,同一个 k 又是另一种材料;二是 k 和真实材料的刚度(N/m)之间没有换算关系,美术只能靠试。
这在游戏里是实打实的麻烦:性能档位降低迭代次数,布料就变软;主机和 PC 帧率不同,同一件衣服手感不同;调好的参数换一个分辨率的网格又要重调。
PBD 的 k 不是材料参数:它和迭代次数、时间步、网格分辨率绑在一起。XPBD 要解决的就是这件事。
Müller et al., 2007(刚度系数与迭代次数的关系);Macklin et al., XPBD, 2016(问题陈述)
页签 3 / 7
XPBD 与子步
从隐式欧拉推出 XPBD:柔度、λ、阻尼和残差;然后是 Small Steps:同样的计算量,切子步比加迭代更准。
03
XPBD:把刚度变回物理量
XPBD 只多了一个变量 λ 和一个参数 α,但收敛时硬度与迭代次数、时间步无关。
PBD 的刚度系数 k 是一个“调出来的数”:换迭代次数、换帧率,同一个 k 就是另一种材料。XPBD(Macklin、Müller、Chentanez,2016)用柔度 α 取代了 k,并为每个约束引入一个累积的拉格朗日乘子 λ。
这一章从隐式欧拉推出 XPBD 的更新公式,给出柔度的单位和常见取值,讲阻尼怎么加,以及怎样用残差判断解有没有收敛。
3.1比喻
柔度:用刚度的倒数描述材料
XPBD 的第一个改动是换参数:不用“每次修掉多少”的 k,而用物理上的柔度 α,即刚度的倒数。对距离约束,刚度 k 的单位是 N/m(拉长 1 米需要多少牛顿),柔度 α = 1/k 的单位是 m/N(每牛顿拉长多少米)。
为什么用倒数?因为游戏里最常见的材料是“几乎不可伸长”的:绳子、布料的经纬方向、骨骼连接。用刚度表示,它们的 k 是无穷大,没法写进浮点数;用柔度表示,α = 0,干干净净。α = 0 时 XPBD 退化成 k = 1 的 PBD,这一点后面推导完会看到。
换了参数,意义也变了:α 是材料的属性,和迭代次数、时间步都没有关系。只要求解器收敛,α = 10⁻⁴ m/N 的绳子挂 1 kg 的重物(约 9.8 N),每段就伸长约 1 毫米,不管你迭代 5 次还是 50 次、帧率 30 还是 120。
代价是每个约束要多存一个数:拉格朗日乘子 λ。下一节解释它是什么、为什么需要它。
柔度 α = 1/k 是材料参数;α = 0 表示不可伸长,这是 XPBD 最常用的设定。
Macklin, Müller, Chentanez, XPBD, MIG 2016
要点
柔度 α = 1/k 是材料参数;α = 0 表示不可伸长,这是 XPBD 最常用的设定。
3.2推导
从隐式欧拉推出 XPBD
这一节给出推导的骨架。看懂它,后面的阻尼、残差、可微 XPBD 都会顺理成章。
第一步:把约束写成势能。柔度为 α 的约束,等价于一根势能为 U = ½·C²/α 的弹簧。它产生的力是 f = −∇U = −∇C · C/α。
第二步:写隐式欧拉。先用外力和速度算出预测位置 x̃ = xⁿ + h·vⁿ + h²·M⁻¹fext(就是 PBD 的预测)。隐式欧拉要求下一步位置 x 满足:
质量 × 偏离预测的距离,必须等于约束力乘以 h² 。
M (x − x̃) = h² f(x) = −h² ∇C(x)ᵀ C(x) / α
- x̃
- 只考虑惯性和外力的预测位置
第三步:引入乘子。定义 λ = −C(x)/α̃,其中 α̃ = α/h²。于是方程拆成两条:
第一条是动量方程:质量 × 偏离预测 = 约束方向 × λ。第二条是约束方程:约束误差 + 修正后的柔度 × λ = 0 。
g(x, λ) = M (x − x̃) − ∇C(x)ᵀ λ = 0 h(x, λ) = C(x) + α̃ λ = 0
- α̃
- α / h²,“按时间步缩放过的柔度”
- λ
- 约束在这一步里施加的力 × h²
第四步:牛顿迭代,加两个近似。对 (x, λ) 做牛顿迭代,忽略约束的二阶导数(几何刚度),并假设 g ≈ 0(每次更新后 x 都沿 ∇C 方向挪,动量方程基本保持)。这样消去 Δx,得到一个只关于 Δλ 的标量方程,下一节就是它的解。
这两个近似意味着 XPBD 不是精确的隐式欧拉,但在实践中误差很小。更重要的是:XPBD 收敛到的,是一个有明确物理意义的方程的解,所以它的硬度才和迭代次数无关。
XPBD = 对隐式欧拉做近似牛顿迭代;λ 是约束力乘 h²,它让求解器记住已经施加了多少力。
Macklin, Müller, Chentanez, XPBD, MIG 2016(第 3 节)
M (x − x̃) = h² f(x) = −h² ∇C(x)ᵀ C(x) / α
质量 × 偏离预测的距离,必须等于约束力乘以 h² 。
g(x, λ) = M (x − x̃) − ∇C(x)ᵀ λ = 0 h(x, λ) = C(x) + α̃ λ = 0
第一条是动量方程:质量 × 偏离预测 = 约束方向 × λ。第二条是约束方程:约束误差 + 修正后的柔度 × λ = 0 。
要点
XPBD = 对隐式欧拉做近似牛顿迭代;λ 是约束力乘 h²,它让求解器记住已经施加了多少力。
3.3交互图
XPBD 的更新公式:比 PBD 多两项
推导的结果只比 PBD 的投影多了两项:
这次要补的乘子 = (负的约束误差 − 修正柔度 × 已经累积的乘子) ÷ (各点梯度平方 × 逆质量之和 + 修正柔度)。然后各点照旧沿梯度、按逆质量挪。
Δλ = ( −C(x) − α̃ λ ) / ( Σj wj |∇jC|² + α̃ ) λ ← λ + Δλ Δxi = wi ∇iC · Δλ
- α̃ = α/h²
- 每个时间步(子步)开始时算一次
- λ
- 每个时间步开始时清零,迭代中累积
和 PBD 对比:分子多了 −α̃λ,分母多了 +α̃。α = 0 时两项都消失,就是 PBD 的 k = 1。α > 0 时,分母变大,每次修得少一点;分子里的 −α̃λ 记录了“已经施加的力”,使得迭代收敛到的不是 C = 0,而是 C = −α̃λ,也就是约束力和形变刚好平衡的位置。
// 每个时间步开始:lambda[c] = 0 void SolveDistance(int c, float h) { int a = con[c].a, b = con[c].b; float3 d = p[a] - p[b]; float len = length(d); if (len < 1e-8f) return; // 两点重合:方向无定义,跳过 float3 n = d / len; float C = len - con[c].rest; float aT = con[c].compliance / (h * h); // α̃ float wsum = w[a] + w[b]; if (wsum + aT == 0) return; // 两端都固定 float dl = (-C - aT * lambda[c]) / (wsum + aT); lambda[c] += dl; p[a] += w[a] * dl * n; p[b] -= w[b] * dl * n; }
交互图里能看到 XPBD 的承诺和它的边界:只要收敛了,伸长率就贴着理论值,迭代 4 次和 40 次没有区别;子步减到 1、或者柔度很小而重物很重时,迭代不够收敛,绳子依然比理论值长。XPBD 消除的是“参数的含义依赖迭代次数”,不是“迭代不够就不准”。下一章的子步就是对付后者的。
XPBD 只多了 −α̃λ 和 +α̃ 两项;λ 每个时间步清零,迭代中累积。
Macklin, Müller, Chentanez, XPBD, MIG 2016(式 18)
3.4速查
柔度怎么取值:单位、材料和网格分辨率
柔度有单位,所以可以从真实材料推出来,而不是靠试。不同约束的柔度单位不同:距离约束是 m/N,弯曲约束是 1/(N·m)(每单位力矩转多少弧度),体积约束是 m⁵/N(单位都由 U = ½·C²/α 推出:C² 的单位除以能量的单位)。
从杨氏模量出发。一根截面积 A、长度 L、杨氏模量 E 的杆,刚度 k = EA/L,所以 α = L/(EA)。几个量级:
| 材料 | 杨氏模量 E(量级) | 1/E(m²/N) | 手感 |
|---|---|---|---|
| 钢 | 200 GPa | 5 × 10⁻¹² | 在游戏里等同于 α = 0 |
| 木材(顺纹) | 10 GPa | 10⁻¹⁰ | 同上 |
| 皮革、肌腱 | 0.1 ~ 1 GPa | 10⁻⁹ ~ 10⁻⁸ | 几乎拉不长 |
| 橡胶 | 1 ~ 100 MPa | 10⁻⁸ ~ 10⁻⁶ | 能看到明显伸长 |
| 肌肉、脂肪 | 1 ~ 100 kPa | 10⁻⁵ ~ 10⁻³ | 果冻感 |
网格分辨率会改变每条边的柔度。一维的绳子切成 n 段,每段长度 L/n,每段的柔度也要变成 1/n,整根绳子的总柔度才不变。如果你给每段设同一个 α,把绳子细分一倍,它就软了一倍。二维的布料有一个巧合:均匀网格上每条边的等效刚度约为 E·t(t 是厚度),与边长无关,所以布料细分时边的柔度大致不用改(剪切和弯曲另算)。
实际项目里,美术很少直接输入 m/N。更常见的是给一个 0~1 的“硬度”滑杆,内部映射到对数刻度的柔度上,例如 α = 10−8 + 6(1−s)。这样滑杆的每一段都有可见的变化,而且换帧率、换迭代次数不会改变手感。7.5 节再讲这层映射怎么设计。
柔度有单位、可以从材料推;但每条边的柔度要随网格分辨率换算,一维绳子尤其如此。
弹性模量为常见量级;柔度与网格分辨率的换算为一维杆和二维膜的标准力学结论;XPBD 原文给出了一张同类的材料柔度表
从杨氏模量出发
一根截面积 A、长度 L、杨氏模量 E 的杆,刚度 k = EA/L,所以 α = L/(EA)。
网格分辨率会改变每条边的柔度
一维的绳子切成 n 段,每段长度 L/n,每段的柔度也要变成 1/n,整根绳子的总柔度才不变。
要点
柔度有单位、可以从材料推;但每条边的柔度要随网格分辨率换算,一维绳子尤其如此。
3.5推导
阻尼:让振动停下来
只有柔度的 XPBD 是一个无阻尼的弹簧系统,橡皮筋拉开会一直弹。现实材料有内摩擦,振动会衰减。XPBD 提供了约束级的阻尼:每个约束有一个阻尼系数 β,阻尼力沿约束方向、正比于约束的变化速度。
多减一项“约束这一步变化了多少 × 阻尼系数”,分母也相应放大。
Δλ = ( −C − α̃λ − γ ∇C·(x − xⁿ) ) / ( (1 + γ) Σ wj|∇jC|² + α̃ ) γ = α̃ β̃ / h,β̃ = h² β
- x − xⁿ
- 这一步到目前为止的位移,∇C·(x − xⁿ) 约等于 C 的变化速度 × h
- β
- 阻尼系数,单位与刚度×时间相当
它只阻尼沿约束方向的相对运动:绳子伸缩的振动被吃掉,但整根绳子的摆动不受影响。这比全局速度阻尼(每帧把所有速度乘 0.99)物理得多,后者会让自由下落的布料也像在水里一样慢。
工程上常见的三种阻尼,按物理程度排序:
| 做法 | 阻尼什么 | 副作用 |
|---|---|---|
| 全局速度衰减 v ← v·(1 − c·h) | 所有运动 | 自由落体也被减速;c 要乘 h,否则依赖帧率 |
| 按边的相对速度阻尼(在速度更新后,沿边方向减掉一部分相对速度) | 边的伸缩振动 | 简单有效,但不在 XPBD 的方程里,和 α 的关系要自己调 |
| XPBD 约束阻尼(上式) | 约束方向的变化速度 | 和柔度在同一套方程里;β 太大时需要更多迭代 |
别忘了隐式积分本身也有数值阻尼(1.2 节)。步长大时,即使 β = 0,振动也会衰减。所以“阻尼调多少”和“子步多少”是一起调的:加子步会让材料显得更“活”,可能需要补一点显式阻尼。UE 的 Chaos Cloth Asset 把拉伸阻尼表示成临界阻尼的倍数,这让同一个数值在不同刚度下有相近的手感。
约束阻尼只吃掉约束方向的振动;全局阻尼一定要乘时间步,否则帧率一变手感就变。
Macklin, Müller, Chentanez, XPBD, MIG 2016(第 3.5 节);UE Chaos Cloth Asset 的 StretchDamping 参数以临界阻尼为单位(UE 5.8 API 文档)
Δλ = ( −C − α̃λ − γ ∇C·(x − xⁿ) ) / ( (1 + γ) Σ wj|∇jC|² + α̃ ) γ = α̃ β̃ / h,β̃ = h² β
多减一项“约束这一步变化了多少 × 阻尼系数”,分母也相应放大。
要点
约束阻尼只吃掉约束方向的振动;全局阻尼一定要乘时间步,否则帧率一变手感就变。
3.6深挖
残差:怎么知道算没算准
迭代式求解器永远要回答一个问题:现在的解离真正的解还差多少?XPBD 的推导给了一个直接的度量:约束方程的残差。
残差 = 约束误差 + 修正柔度 × 累积乘子。等于 0 说明形变和约束力刚好平衡。
rc = Cc(x) + α̃ λc
- rc
- 约束 c 的残差;α = 0 时就是约束误差本身
为什么不直接看 C?因为柔度不为零时,C 本来就不该是 0:挂着重物的橡皮绳理应被拉长。要判断的是“拉长的量是否和它承受的力匹配”,这正是 r 衡量的。
实践中的用法:
调试:每帧记录所有约束残差的最大值和均方根,画成曲线。某个场景下残差突然升高,通常是质量比悬殊(重物挂在轻布上)、约束冲突(两个钉住点之间的距离比绳子还长)或碰撞把点推进了不可能的位置。
自适应迭代:残差低于阈值就提前停止,省下的预算给更难的物体。UE Chaos Cloth 同时有迭代次数和最大迭代次数两个参数,思路相近(官方文档对两者关系的描述有限,这里是推断)。
验收:附录 A 的验证清单里,“柔度不同、迭代次数不同,残差收敛后伸长率是否一致”是 XPBD 实现是否写对的第一个测试。
动量方程 g 的残差一般不监控:XPBD 的近似假设它近似为 0,实践中它也确实很小。
看残差 C + α̃λ,而不是只看 C:有柔度时,“被拉长”本身不是错误。
Macklin, Müller, Chentanez, XPBD, MIG 2016(第 4 节的收敛分析)
rc = Cc(x) + α̃ λc
残差 = 约束误差 + 修正柔度 × 累积乘子。等于 0 说明形变和约束力刚好平衡。
要点
看残差 C + α̃λ,而不是只看 C:有柔度时,“被拉长”本身不是错误。
04
子步:小步胜过多次迭代
同样的计算量,把一帧切成很多子步、每步只迭代一次,通常比少数子步、多次迭代更准。
上一章说 XPBD 的硬度在收敛时与迭代无关。现实是一帧里算不到收敛。Macklin 等人 2019 年的 Small Steps 论文给出了一个出人意料的答案:少迭代,把时间切碎。
4.1推导
为什么切小步比多迭代更好
计算量有限,假设每帧能做 16 次“把所有约束投影一遍”。两种分法:1 个子步迭代 16 次,或者 16 个子步各迭代 1 次(每个子步都完整地预测、投影、更新速度)。直觉上前者更“认真”,Macklin 等人 2019 年的 Small Steps 论文却发现后者通常误差更小、手感更硬。
原因有三层:
一,预测更准。子步越短,按惯性预测的位置离最终答案越近,投影要修的量越小。迭代的作用是修掉预测的误差;如果预测本来就准,一次就够了。
二,方程更好解。看 XPBD 的分母:α̃ = α/h²。h 变成原来的 1/16,α̃ 变成 256 倍。约束之间的耦合相对于对角项变弱,高斯-赛德尔每次迭代能消掉的误差比例更高。对于 α = 0 的硬约束,这一项帮不上忙,但第一条依然成立。
三,数值阻尼更小。隐式欧拉的数值阻尼随步长减小而减小。小步长下,布料的摆动、绳子的回弹更接近真实,不会像在糖浆里。1.1 节的交互图里,加子步之后 XPBD 那条线的振幅回来了,就是这个原因。
论文还指出:每个子步只迭代一次时,XPBD 的 λ 每次都从 0 开始,−α̃λ 那一项不起作用,更新公式就是带柔度的 PBD 投影,实现更简单。
同样的计算量,切成更多子步、每步迭代一次:预测更准、方程更好解、数值阻尼更小。
Macklin et al., Small Steps in Physics Simulation, SCA 2019
一,预测更准
子步越短,按惯性预测的位置离最终答案越近,投影要修的量越小。
二,方程更好解
看 XPBD 的分母:α̃ = α/h²。
三,数值阻尼更小
隐式欧拉的数值阻尼随步长减小而减小。
要点
同样的计算量,切成更多子步、每步迭代一次:预测更准、方程更好解、数值阻尼更小。
4.2交互图
固定预算,挪一挪就知道
下面这张图固定每帧的总计算量(子步数 × 迭代次数),只改变它的分法。末端挂 10 kg 的重物,绳子本身很轻,这是质量比最悬殊、最难收敛的情形。
一个子步、迭代 16 次时,绳子被拉得很长:高斯-赛德尔在长链上收敛慢,16 次远远不够。同样 16 次投影分给 16 个子步,伸长率小得多。
这不意味着迭代完全没用。两种情况下还需要多次迭代:一是约束之间存在强冲突,比如两个钉住点被动画拉开,必须多次迭代才能找到折中;二是碰撞检测很贵,没法每个子步都做(下一节),只能在一个子步里多迭代几次。实际引擎里通常两者混用:几个子步,每个子步少量迭代。UE Chaos Cloth 同时暴露子步数和迭代次数,就是让项目自己找这个平衡。
先加子步,再加迭代;迭代留给约束冲突和碰撞太贵的情况。
Macklin et al., Small Steps in Physics Simulation, SCA 2019(主要结论);交互图为本页的数值实验
4.3清单
子步的代价:碰撞、精度和外部输入
子步不是免费的。它把三类原本“一帧一次”的工作变成了“一子步一次”,每一类都要单独处理:
碰撞检测。完整的碰撞检测(建空间加速结构、找可能接触的三角形对)很贵,每子步做一次往往负担不起。常见做法是分两层:每帧做一次宽相,用这一帧可能移动的最大距离扩大包围盒,找出候选接触对;每个子步只对候选对做便宜的窄相(重新算一次距离和法线)。附录 A 的 M6 就按这个结构写。
外部输入的插值。角色动画、碰撞体的位置通常一帧才更新一次。如果每个子步都用帧末的位置,碰撞体相当于在第一个子步瞬移过去,布料会被“抽”一下。正确做法是在子步之间线性插值碰撞体和钉住点的位置(旋转用球面插值)。
浮点精度。速度更新 v = (x − xprev)/h 是两个很接近的数相减再除以一个很小的数。离世界原点很远时(大地图),单精度的位置只剩几毫米的分辨率,子步一短,算出的速度就会抖。UE Chaos Cloth 提供了“在局部空间模拟”的开关,官方说明就是为了减少远离原点时的精度抖动。自己实现时,建议把布料的模拟空间放在角色的局部坐标系里,只把角色的整体运动作为外力(惯性力)注入。
另外,子步数越多,每一帧的固定开销(启动计算着色器、同步)也越多。GPU 上通常会把多个子步写进同一个调度里,避免每子步一次的调度开销。
子步要配套三件事:分层的碰撞检测、子步间插值外部输入、在局部空间里模拟。
Macklin et al., Small Steps, SCA 2019;UE ChaosClothSharedSimConfig 的 bUseLocalSpaceSimulation 说明(官方 API 文档);碰撞检测的分层做法为常见工程实践
碰撞检测
完整的碰撞检测(建空间加速结构、找可能接触的三角形对)很贵,每子步做一次往往负担不起。
外部输入的插值
角色动画、碰撞体的位置通常一帧才更新一次。
浮点精度
速度更新 v = (x − xprev)/h 是两个很接近的数相减再除以一个很小的数。
要点
子步要配套三件事:分层的碰撞检测、子步间插值外部输入、在局部空间里模拟。
页签 4 / 7
约束库、碰撞与摩擦
距离、弯曲、体积、长程附着、形状匹配;不等式约束、碰撞检测、位置级摩擦和自碰撞。
05
约束库:布、绳、软体和刚体
任何约束都只需要两样东西:一个标量函数 C 和它的梯度;其余的更新公式全部通用。
前面所有内容只用了一种约束:距离。XPBD 的好处是更新公式和约束类型无关:给出 C(x) 和 ∇C,就能套用同一套投影。这一章给出常用约束的定义、梯度和用法,以及它们各自的坑。
5.1清单
写一个新约束的配方
2.2 节和 3.3 节的公式对任何约束都成立。所以“支持一种新约束”只需要回答四个问题:
一,C 是什么?一个标量,满足时等于 0。等式约束(必须等于)和不等式约束(不能小于)要区分开,后者只在 C < 0 时投影(第 6 章)。
二,∇C 是什么?对每个参与的质点求一个梯度向量。可以手推,也可以用自动微分生成,但运行时要算得便宜。
三,退化情况怎么办?梯度为零或无定义时(两点重合、三角形退化成线),分母会趋于 0,Δλ 爆掉。每个约束都要有一个“跳过”分支。
四,柔度用什么单位?由 C 的单位决定(3.4 节)。同一个系统里不同类型约束的柔度不能直接比较。
struct Constraint { int idx[4]; int n; float compliance; float lambda; };
// Eval 返回 C,并把每个质点的梯度写进 grad[];退化时返回 false
bool Eval(const Constraint& c, float& C, float3 grad[4]);
void Solve(Constraint& c, float h) {
float C; float3 grad[4];
if (!Eval(c, C, grad)) return;
float denom = 0;
for (int k = 0; k < c.n; k++) denom += w[c.idx[k]] * dot(grad[k], grad[k]);
float aT = c.compliance / (h * h);
if (denom + aT < 1e-12f) return;
float dl = (-C - aT * c.lambda) / (denom + aT);
c.lambda += dl;
for (int k = 0; k < c.n; k++) p[c.idx[k]] += w[c.idx[k]] * dl * grad[k];
}| 约束 | C | 典型用途 | 节 |
|---|---|---|---|
| 距离 | |x₁ − x₂| − L | 绳子、布料的拉伸和剪切 | 5.2 |
| 弯曲 | 二面角 − θ₀,或等距弯曲能量 | 布料抗折、纸、皮革 | 5.3 |
| 体积 | V − V₀(四面体或整个封闭网格) | 软体、气球、肌肉 | 5.4 |
| 长程附着 | |x − xpin| − Lgeo ≤ 0 | 长布料、头发不被拉长 | 5.5 |
| 形状匹配 | x − (R·r₀ + c) | 几乎刚性的块、可破碎物 | 5.6 |
| 接触 | (x − q)·n − r ≥ 0 | 碰撞 | 6.1 |
新约束 = C + ∇C + 退化处理 + 柔度单位;更新公式一行都不用改。
Müller et al., PBD, 2007(通用投影公式);Macklin et al., XPBD, 2016
一,C 是什么?
一个标量,满足时等于 0。
二,∇C 是什么?
对每个参与的质点求一个梯度向量。
三,退化情况怎么办?
梯度为零或无定义时(两点重合、三角形退化成线),分母会趋于 0,Δλ 爆掉。
四,柔度用什么单位?
由 C 的单位决定(3.4 节)。
要点
新约束 = C + ∇C + 退化处理 + 柔度单位;更新公式一行都不用改。
5.2算例
距离约束:拉伸、剪切与经纬方向
布料最基础的约束是网格每条边上的距离约束。但只有网格边是不够的:一个四边形网格只连横竖边,方格可以被推成菱形,布料会像渔网。加上两条对角线(剪切约束),方格就保持住了。三角形网格本身就有对角边,不需要额外的剪切约束,但对角边只有一个方向,布料的剪切行为会有方向性偏差。
真实布料是各向异性的:经向(warp,沿布匹长度)、纬向(weft,横向)、斜向(bias)的刚度不同。牛仔布经纬几乎拉不长,斜向却能明显伸长,这就是斜裁的裙子更贴身的原因。要表达这一点,每条边按它在版片(2D 纸样)里的方向分配不同的柔度。
UE 5.8 的 Chaos Cloth Asset 正是这样暴露参数的:拉伸配置节点里可以选 PBD 或 XPBD 求解器,可以选各向同性或各向异性,分别设置经向、纬向和斜向的拉伸刚度,还可以选择静止长度来自 3D 披挂后的形状还是 2D 版片。
后一个选项值得多说一句:如果静止长度取自 3D 网格,模型师建模时留下的拉伸会被当成“自然状态”;取自 2D 版片,布料会试图回到裁剪时的尺寸,垂坠感更真实,但模型和版片必须对得上,否则一开始布料就会收缩或膨胀。
只连网格边的布料像渔网:加剪切约束,再按经纬方向分配柔度。
UE 5.8 Chaos Cloth Asset 拉伸配置节点(FChaosClothAssetSimulationStretchConfigNode)官方 API 文档;网格连接方式为常见做法
要点
只连网格边的布料像渔网:加剪切约束,再按经纬方向分配柔度。
5.3推导
弯曲约束:三种写法
没有弯曲约束的布料可以随意对折,看起来像塑料袋。弯曲约束作用在共享一条边的两个三角形上,一共四个顶点。有三种常见写法:
一,跨边距离约束。在两个三角形不共享的那两个顶点之间加一个距离约束。最便宜,代码复用距离约束。缺点是它和拉伸耦合:布料被拉伸时这条“弯曲弹簧”也在抵抗,弯曲刚度和拉伸刚度很难独立调,而且对接近 180° 的平展状态不敏感(距离对角度的变化是二阶的)。
二,二面角约束。C = 二面角 − 初始二面角,PBD 原论文给出了它的梯度。物理意义清楚,但梯度公式复杂,在两个三角形接近共面或退化时数值不稳定,而且有 acos 的导数在 ±1 附近发散的问题。
三,等距弯曲(isometric bending)。Bergou 等人 2006 年指出:对几乎不可伸长的曲面,弯曲能量可以写成顶点位置的二次型 E = ½ Σ Qij xi·xj,Q 是一个只依赖静止形状的 4×4 矩阵,预计算一次即可。梯度是线性的,便宜且稳定。代价是它假设布料不怎么拉伸,对弹性很大的材料不准。
| 写法 | 成本 | 稳定性 | 和拉伸独立 | 适合 |
|---|---|---|---|---|
| 跨边距离 | 最低 | 好 | 否 | 头发、低精度布料、原型 |
| 二面角 | 高 | 共面时要特殊处理 | 是 | 需要设定非平的静止形状(折痕、褶皱) |
| 等距弯曲 | 低(预计算 Q) | 好 | 近似是 | 大多数服装布料 |
弯曲柔度的单位是 1/(N·m)。和拉伸一样,它也随网格分辨率变化,而且变化方式不同:细分网格时,每条边对应的弯曲刚度要按边长和面积重新缩放,否则细网格的布料会比粗网格更软或更硬。
UE 的 Chaos Cloth 配置也给了两种选择:默认是更快的跨边弹簧,打开 Use Bending Elements 换成更准的弯曲单元。它还有一组“屈曲”参数:折叠超过某个比例(Buckling Ratio)后改用另一个刚度(Buckling Stiffness),用来模拟布料一旦折出褶就更容易继续折下去的现象。
原型用跨边距离,产品用等距弯曲或弯曲单元;需要预设折痕时才用二面角。
Müller et al., PBD, 2007(二面角弯曲约束);Bergou et al., A Quadratic Bending Model for Inextensible Surfaces, SGP 2006(等距弯曲);UE UChaosClothConfig 官方 API 文档
一,跨边距离约束
在两个三角形不共享的那两个顶点之间加一个距离约束。
二,二面角约束
C = 二面角 − 初始二面角,PBD 原论文给出了它的梯度。
三,等距弯曲(isometric bending)
Bergou 等人 2006 年指出:对几乎不可伸长的曲面,弯曲能量可以写成顶点位置的二次型 E = ½ Σ Qij xi·xj,Q 是一个只依赖静止形状的 4×4 矩阵,预计算一次即可。
要点
原型用跨边距离,产品用等距弯曲或弯曲单元;需要预设折痕时才用二面角。
5.4推导
体积约束:软体和气球
软体(肌肉、果冻、轮胎)有两种表示:一种是四面体网格,内部填满;一种只有表面网格,靠一个整体体积约束撑起来。
四面体体积约束。对每个四面体,C = V − V₀,其中 6V = (x₂ − x₁) × (x₃ − x₁) · (x₄ − x₁)。梯度有简洁的形式:
每个顶点的梯度,是对面那个三角形的面积向量(除以 6);第一个顶点的梯度是其余三个之和的相反数。
∇₂C = (x₃ − x₁) × (x₄ − x₁) / 6 ∇₃C = (x₄ − x₁) × (x₂ − x₁) / 6 ∇₄C = (x₂ − x₁) × (x₃ − x₁) / 6 ∇₁C = −(∇₂C + ∇₃C + ∇₄C)
只有体积约束和四面体的边长约束,软体会有“锁死”的问题:几乎不可压缩的材料(α 很小),四面体网格的自由度不够,整体变得过硬。工程上要么允许一点可压缩性,要么换更好的本构模型(5.6 节提到的 Neo-Hookean 约束)。
整体体积约束。封闭的表面网格,C = Σ 三角形对体积的贡献 − k·V₀,k 是“充气系数”。所有顶点都参与这一个约束,梯度是每个顶点相邻三角形的面积法向之和。k > 1 时网格会鼓起来,像气球。
整体约束有一个工程问题:它把成百上千个顶点耦合进一个约束,雅可比式并行时会和其他约束激烈冲突;高斯-赛德尔下它的位置也很重要,放在最后修,体积最准,但会把表面的距离约束拉坏一点。
四面体约束做实心软体,整体体积约束做气球;几乎不可压缩的材料要提防锁死。
Müller et al., PBD, 2007(封闭网格的整体体积约束);四面体体积的梯度为标准结论
∇₂C = (x₃ − x₁) × (x₄ − x₁) / 6 ∇₃C = (x₄ − x₁) × (x₂ − x₁) / 6 ∇₄C = (x₂ − x₁) × (x₃ − x₁) / 6 ∇₁C = −(∇₂C + ∇₃C + ∇₄C)
每个顶点的梯度,是对面那个三角形的面积向量(除以 6);第一个顶点的梯度是其余三个之和的相反数。
要点
四面体约束做实心软体,整体体积约束做气球;几乎不可压缩的材料要提防锁死。
5.5深挖
长程附着:长布料为什么会“越拉越长”
一条挂在角色腰上的长披风,顶端钉住,下摆离钉住点有一米多。角色快速转身时,你会发现披风像橡皮筋一样被拉长,过了好几帧才缩回来。即使每条边的柔度都是 0,也是这样。
原因是 7.1 节会讲的收敛速度问题:钉住点的约束信息要沿着几十条边一段段传下去,有限的迭代传不到底。下摆的点在预测时被甩出去,它附近的边只能把它“拉回一点”,整条链的误差累积起来,就是明显的伸长。
Kim、Chentanez、Müller 2012 年的长程附着(Long Range Attachments,LRA)给出了一个几乎免费的解法:给每个自由顶点加一条直接通往钉住点的单边约束:
顶点到钉住点的距离,不允许超过它们沿布料表面的静止距离;短于这个距离时什么也不做。
C = |x − xpin| − Lgeo ≤ 0 (只在 C > 0 时投影,且只移动 x)
- Lgeo
- 静止状态下沿布面的测地距离,预计算;简化做法用直线距离
它是单边的:布料可以自由地靠近钉住点(褶皱、堆积),只是不能被拉得比静止时更远。这条约束一步就把下摆拉回可能的范围内,剩下的局部误差交给普通的边约束。
工程要点:每个顶点只连最近的一个或几个钉住点;钉住点跟着动画走时,Lgeo 不变,xpin 每子步更新;如果钉住点之间被动画拉开了比布料还长的距离,LRA 和边约束会冲突,要允许 LRA 有一点柔度或在 Lgeo 上乘一个大于 1 的系数。很多引擎把这类约束叫 tether。UE 的 Chaos Cloth 配置(UChaosClothConfig)里就有对应的三个参数:Tether Stiffness(附着刚度)、Tether Scale(允许的最大拉伸比例,即上面说的系数),以及是否用测地距离代替直线距离(官方说明:初始化更慢,但更准、更少瑕疵)。
长布料被拉长不是柔度问题,是收敛问题;长程附着一步就能拉回来。
Kim, Chentanez, Müller, Long Range Attachments, SCA 2012;UE UChaosClothConfig 官方 API 文档
C = |x − xpin| − Lgeo ≤ 0 (只在 C > 0 时投影,且只移动 x)
顶点到钉住点的距离,不允许超过它们沿布料表面的静止距离;短于这个距离时什么也不做。
要点
长布料被拉长不是柔度问题,是收敛问题;长程附着一步就能拉回来。
5.6对比
形状匹配、刚体与更好的软体材料
前面的约束都是局部的:两个点、四个点。有三类东西需要更“整体”的约束,这里只给出方向,细节见原论文。
形状匹配(Shape Matching)。一组质点,求一个最佳的刚体变换(旋转 R、平移 c),让它们最接近静止形状;然后把每个点往 R·r₀ + c 拉。只有一个“刚度”参数,适合果冻、可变形的道具。它不是严格的 C(x) 形式,但可以塞进同一个循环里,作为一个特殊的投影步骤。
刚体。Müller 等人 2020 年把 XPBD 推广到刚体:每个刚体有位置和朝向两套自由度,约束(关节、接触)的投影同时修正平移和旋转,“逆质量”换成沿约束方向的广义逆质量(包含惯性张量)。他们的结论之一是:在子步 + 一次迭代的配置下,XPBD 刚体在关节链、高质量比的情况下表现很好。
Neo-Hookean 软体。简单的四面体边长 + 体积约束,在大变形下不够准,还会锁死。Macklin 和 Müller 2021 年把稳定的 Neo-Hookean 材料改写成 XPBD 约束:一个约束管形状(偏量部分),一个约束管体积(静水压部分),柔度直接对应拉梅参数。这样 XPBD 也能用真正的有限元材料参数。
| 需求 | 推荐 | 理由 |
|---|---|---|
| 布料、头发、绳子 | 距离 + 弯曲 + 长程附着 | 本课主线,附录 A 就写这个 |
| 果冻、橡胶道具 | 形状匹配,或四面体 + 体积 | 前者简单,后者可以局部变形 |
| 肌肉、脂肪、角色软体 | Neo-Hookean 约束 | 材料参数有物理意义,大变形稳定 |
| 关节链、载具 | XPBD 刚体 | 和粒子约束在同一个求解循环里 |
局部约束管布料和绳子;形状匹配、XPBD 刚体和 Neo-Hookean 约束把同一个框架扩展到更整体的物体。
Müller et al., Meshless Deformations Based on Shape Matching, SIGGRAPH 2005;Müller et al., Detailed Rigid Body Simulation with XPBD, SCA 2020;Macklin & Müller, A Constraint-based Formulation of Stable Neo-Hookean Materials, MIG 2021
形状匹配(Shape Matching)
一组质点,求一个最佳的刚体变换(旋转 R、平移 c),让它们最接近静止形状;
刚体
Müller 等人 2020 年把 XPBD 推广到刚体:每个刚体有位置和朝向两套自由度,约束(关节、接触)的投影同时修正平移和旋转,“逆质量”换成沿约束方向的广义逆质量(包含惯性张量)。
Neo-Hookean 软体
简单的四面体边长 + 体积约束,在大变形下不够准,还会锁死。
要点
局部约束管布料和绳子;形状匹配、XPBD 刚体和 Neo-Hookean 约束把同一个框架扩展到更整体的物体。
06
碰撞与摩擦
碰撞是只在违反时才生效的约束;摩擦是按法向修正量限制切向位移。
约束分两类:等式约束(距离必须等于 L)和不等式约束(距离不能小于 r)。碰撞属于后者。这一章讲不等式约束的投影、碰撞检测怎么嵌进子步循环、位置级摩擦,以及最难的自碰撞。
6.1推导
不等式约束:只在违反时才推
碰撞约束说的是“不能进去”,而不是“必须贴着”。写成不等式:
质点到碰撞面的有符号距离,减去质点半径,不能小于 0。
C(x) = (x − q) · n − r ≥ 0
- q, n
- 碰撞面上最近的点和外法线
- r
- 质点的碰撞半径(布料的厚度的一半)
投影规则只多一条:只在 C < 0 时投影。在 XPBD 里,还要保证累积的 λ 只能朝“推出去”的方向:如果某次迭代算出的 Δλ 会让累积 λ 变成“拉进来”,就把它截到 0。否则一个已经离开表面的点会被吸回去。
void SolveGround(int i) {
float C = p[i].y - radius; // 地面 y = 0,法线 (0,1,0)
if (C >= 0) return; // 没穿透:什么也不做
p[i].y -= C; // 推出去;只有一个质点,修正量全给它
contactDepth[i] = -C; // 记下法向修正量,摩擦要用(6.3 节)
}和等式约束相比,不等式约束有两个工程特点。第一,集合会变:哪些点在接触,每个子步都可能不一样,所以接触约束通常不预先建好,而是在碰撞检测后临时生成。第二,恢复系数(弹性碰撞)不能在位置层面表达:投影只会把点推到表面上,反弹速度是“位移 / h”自然得到的,近似完全非弹性。需要弹跳的物体,要在速度更新之后再做一个速度层面的处理:把法向速度设为 −e × 碰撞前的法向速度。Müller 等人 2020 年的刚体论文就是这样分两层的。
接触只在穿透时推,λ 只能往外;弹性碰撞要在速度层面补。
Müller et al., PBD, 2007(碰撞约束);Müller et al., Detailed Rigid Body Simulation with XPBD, SCA 2020(λ 的符号约束与速度层面的恢复系数)
C(x) = (x − q) · n − r ≥ 0
质点到碰撞面的有符号距离,减去质点半径,不能小于 0。
要点
接触只在穿透时推,λ 只能往外;弹性碰撞要在速度层面补。
6.2流程
碰撞检测:子步里查什么、查多少次
碰撞约束需要知道 q 和 n:最近的表面点和法线。布料和角色之间的碰撞,最常见的做法是把角色的身体用简单形状近似:球、胶囊、凸包,或者一张有符号距离场。这些形状的“最近点 + 法线”都有解析解或一次查表就能得到,可以每个子步都算。
流程分三层:
每帧一次:宽相。用这一帧布料每个点可能的移动范围(速度 × 帧长 + 余量),和碰撞体的包围盒求交,筛出“可能碰到”的点-碰撞体对。
每子步:窄相 + 投影。对候选对,用当前预测位置重新算最近点和法线,生成接触约束,和其他约束一起投影。
必要时:连续检测。点在一个子步内移动距离超过碰撞体的厚度,就可能直接穿过去(隧穿)。子步本身就是最有效的防隧穿手段:子步越短,每步移动越小。仍然不够时(子弹速度的物体),要做沿运动轨迹的连续碰撞检测。
碰撞半径(厚度)是一个关键参数。UE Chaos Cloth 有“碰撞厚度”参数(官方说明:给碰撞形状额外加的厚度)。厚度太小,布料会贴着身体闪烁、偶尔穿模;厚度太大,衣服看起来浮在身体上方。经验上取一个比每子步最大移动距离略大的值,再按视觉微调。
碰撞体用解析形状或距离场,每子步重算最近点;子步是最便宜的防隧穿手段。
分层碰撞检测与连续碰撞检测为常见工程实践;UE ChaosClothSharedSimConfig 的 CollisionThickness 说明(官方 API 文档)
每帧一次:宽相
用这一帧布料每个点可能的移动范围(速度 × 帧长 + 余量),和碰撞体的包围盒求交,筛出“可能碰到”的点-碰撞体对。
每子步:窄相 + 投影
对候选对,用当前预测位置重新算最近点和法线,生成接触约束,和其他约束一起投影。
必要时:连续检测
点在一个子步内移动距离超过碰撞体的厚度,就可能直接穿过去(隧穿)。
要点
碰撞体用解析形状或距离场,每子步重算最近点;子步是最便宜的防隧穿手段。
6.3交互图
位置级摩擦:按法向修正量限制切向位移
库仑摩擦说:摩擦力不超过 μ × 法向力。在 XPBD 里没有显式的力,但有一个和法向力成正比的量:法向修正量(6.1 节记下的 contactDepth)。法向修正量 d 越大,说明这一子步里接触面“推”得越用力。
于是摩擦可以写成位置层面的规则:
// 法向投影之后调用;d = 这一子步的法向修正量,x_prev = 子步开始时的位置 float3 dx = p[i] - x_prev[i]; float3 dt = dx - dot(dx, n) * n; // 这一子步的切向位移 float len = length(dt); if (len < muStatic * d) // 静摩擦:切向位移完全抵消 p[i] -= dt; else // 动摩擦:最多抵消 muKinetic * d p[i] -= dt * min(muKinetic * d / len, 1.0f);
可以验证它等价于库仑摩擦:静止在斜坡上的物体,每个子步重力让它向下(垂直于坡面)陷入 g·cosθ·h²,沿坡面滑动 g·sinθ·h²。静摩擦条件 g·sinθ·h² < μs·g·cosθ·h² 正好是 tanθ < μs,和子步长度无关。交互图里改子步数,停与不停的临界角不变。
两个常见的坑:一是摩擦必须用本子步的切向位移,而不是整帧的,否则静摩擦会随子步数变化;二是动态碰撞体(角色在走动)的摩擦要用相对位移:布料点的切向位移要减去碰撞体表面在同一点的位移,否则衣服会从走动的身体上滑下来。
静摩擦:切向位移小于 μs × 法向修正量就整个抵消;动摩擦:最多抵消 μk × 法向修正量。
Müller et al., Detailed Rigid Body Simulation with XPBD, SCA 2020(位置层面的静摩擦);Macklin et al., Unified Particle Physics for Real-Time Applications, SIGGRAPH 2014(粒子摩擦)
6.4深挖
自碰撞:最贵也最难的一块
布料和自己碰撞:裙子的前片和后片、袖子和衣身、折叠的围巾。它比和身体碰撞难得多,原因有三个:
候选对数量大。任何两个三角形都可能碰到。需要每帧重建空间加速结构(最常见的是均匀网格的空间哈希),把每个点放进格子,只检查相邻格子里的点和三角形。
两边都会动。和身体碰撞时,碰撞面是“已知”的;自碰撞时,三角形也在被约束推来推去。点-三角形的修正量要按逆质量分给四个点(点本身和三角形的三个顶点),边-边的情况也要单独处理,否则两条边可以交叉穿过。
一旦穿过去就很难回来。如果某一子步布料穿过了自己,下一子步的检测会把它判断为“在另一侧”,碰撞约束反而会把它推得更远,布料就缠在一起了。工程上的对策是:保持足够的厚度和子步,防止穿过;同时做一个“解缠”步骤,检测已经相交的区域(比如用穿过的边环)并把它们往正确的一侧推。
最便宜的近似是质点-质点自碰撞:每个质点是一个小球,球之间不能重叠(PBD 风格的粒子碰撞,Macklin 等人 2014 年的统一粒子框架就是这样做的)。它只要求球的直径大于网格的边长才不漏,所以只适合较粗的网格或者作为第一道防线。UE 的 Chaos Cloth 配置里,三种做法各有一个开关:点-面自碰撞排斥(Use Self Collisions)、以每个顶点为球心的球体自碰撞(Use Self Collision Spheres),以及自相交的解除(Use Self Intersections,也就是上面说的解缠)。
务必注意静止状态下相邻的点不应互相碰撞:网格上本来就相距很近的点(同一个三角形的顶点、隔一条边的顶点)必须从自碰撞里排除,否则布料一开始就会被自己推开。
自碰撞要空间哈希、四点修正、厚度和解缠;最便宜的入门做法是质点-质点碰撞,并排除拓扑上的近邻。
空间哈希与点-三角形、边-边检测为布料模拟的常见做法;UE UChaosClothConfig 官方 API 文档
候选对数量大
任何两个三角形都可能碰到。
两边都会动
和身体碰撞时,碰撞面是“已知”的;
一旦穿过去就很难回来
如果某一子步布料穿过了自己,下一子步的检测会把它判断为“在另一侧”,碰撞约束反而会把它推得更远,布料就缠在一起了。
要点
自碰撞要空间哈希、四点修正、厚度和解缠;最便宜的入门做法是质点-质点碰撞,并排除拓扑上的近邻。
页签 5 / 7
工程落地,再自己写一个
求解顺序、图着色和 GPU;UE Chaos Cloth 暴露了哪些参数、程序和美术各调什么;然后按八个里程碑写一个 XPBD 布料。
07
工程:顺序、并行与控制面
高斯-赛德尔收敛快但串行;雅可比能并行但慢。图着色让两者兼得一部分。
前六章讲的是“算什么”。这一章讲“怎么排”:约束的求解顺序、并行化、数据布局;再看 UE 的 Chaos Cloth 怎样暴露这些参数,最后讨论程序和美术各控制什么。
7.1交互图
求解顺序:高斯-赛德尔与雅可比
2.4 节说过两种迭代方式。这里用一个数值实验把差别看清楚:一条 20 段的绳子竖直挂着,顶端固定,在一个子步里末端被猛拉了一下(3 m/s),然后反复投影,记录最大约束误差。高斯-赛德尔分两种扫描方向,雅可比带平均和松弛系数。
雅可比式要注意两件事。一是约束平均:一个点同时被几个约束推,直接相加会修过头,要除以作用在这个点上的约束数。二是超松弛:平均之后又修得太保守,乘一个 ω ∈ (1, 2) 可以加速,Macklin 等人 2014 年的统一粒子框架建议在 1 到 2 之间取值;ω 太大会振荡。
这张图里有三个规律。第一,高斯-赛德尔比雅可比快,但两者是同一个量级:一次扫描里,后面的约束已经能看到前面约束的结果,信息可以沿扫描方向一路传下去;雅可比每次迭代只传一跳,对一条 N 段的链,至少要 N 次迭代,末端的信息才能传到顶端。第二,“信息传到了”不等于“误差修好了”:每次只修掉一部分,所以两者都需要很多次迭代。第三,扫描方向的影响比想象的小,真正决定快慢的是质量比。第 8 章还会再见到第一条:图神经网络的消息传递,和雅可比一样,一轮只走一跳。
质量比是两种方法共同的敌人。末端越重,误差越难往上传,这在交互图里很明显。工程上的对策依次是:加子步(第 4 章),加长程附着(5.5 节),以及在必要时把极重的物体和轻的布料分开求解(重物作为钉住点驱动布料,不参与同一组迭代)。
高斯-赛德尔比雅可比快,但都要很多次迭代;真正拖慢收敛的是质量比。
Macklin et al., Unified Particle Physics for Real-Time Applications, SIGGRAPH 2014(约束平均与超松弛);交互图为本页的数值实验
7.2算法科普
图着色:让高斯-赛德尔也能并行
高斯-赛德尔不能并行,是因为两个共享质点的约束不能同时修:它们会同时读写同一个点的位置。但不共享质点的约束完全可以同时修。
图着色就是利用这一点:把约束看成图的节点,共享质点的约束之间连一条边,然后给节点上色,让相邻节点颜色不同。同一种颜色的约束互不相干,可以全部并行;不同颜色之间按顺序执行。这样在颜色之间保持了高斯-赛德尔的“立即使用新位置”,在颜色内部获得了雅可比的并行度。
对规则的四边形布料网格,结构化着色很简单:横向的边按奇偶分两组,纵向的边按奇偶分两组,两组对角线各再分两组,一共 8 种颜色左右。不规则网格用贪心着色,通常十几种颜色以内。
// 预处理:colorOf[c] = 约束 c 的颜色;batches[k] = 颜色 k 的约束列表 for (iter = 0; iter < N; iter++) for (k = 0; k < numColors; k++) parallel_for (c in batches[k]) // 同一颜色内无冲突,可并行 Solve(c, h); // 颜色之间需要一次同步(GPU 上是一次调度或一个屏障)
代价有两个。第一,颜色数就是每次迭代的同步次数,GPU 上每次同步都有开销,颜色太多会抵消并行的收益。第二,着色决定了求解顺序,固定的顺序会带来 2.4 节说的偏向。可以每帧轮换颜色的执行顺序来缓解。
整体体积约束这类涉及大量质点的约束很难着色(它和几乎所有约束都冲突),通常单独放在最后,串行或用归约处理。
图着色:同色约束并行,异色约束串行,兼得高斯-赛德尔的收敛和一部分并行度。
图着色并行高斯-赛德尔为常见做法,例如 Fratarcangeli et al., Vivace, SIGGRAPH Asia 2016
要点
图着色:同色约束并行,异色约束串行,兼得高斯-赛德尔的收敛和一部分并行度。
7.3清单
数据布局与 GPU 实现
把 XPBD 搬到 GPU 上,最重要的是三个选择:数据布局、求解方式、调度方式。
| 选择 | 建议 | 理由 |
|---|---|---|
| 数据布局 | 结构数组(SoA):位置、上一位置、速度、逆质量各一个数组 | 相邻线程读相邻地址,内存访问可合并;投影只读写位置 |
| 约束存储 | 按类型分开存,按颜色排序 | 同一调度内代码路径一致,没有分支发散 |
| 求解方式 | 小型物体用着色高斯-赛德尔;大型、不规则物体用雅可比 + 平均 + 超松弛 | 着色在规则网格上颜色少;雅可比只需原子加 |
| 雅可比的累加 | 每个约束把修正量原子加到点上,再除以计数 | 浮点原子加不保证顺序,结果不完全确定(见下) |
| 调度 | 一个物体的所有子步和迭代放进一个调度,用组内同步 | 小物体每子步一次调度,启动开销会超过计算本身 |
| 精度 | 单精度 + 局部空间 | 4.3 节的精度问题 |
确定性值得单独说。浮点原子加的结果依赖线程到达的顺序,同样的输入跑两次,结果可能差几个最低位,长时间模拟后会分叉。网络同步的游戏、需要回放的工具、以及第 9 章要做的训练数据生成,都可能需要确定性。办法是避免原子加:改用着色高斯-赛德尔,或者每个点按固定顺序收集相邻约束的修正量(“以点为中心”的写法)。
以点为中心的思路走到底,就是 Chen 等人 2024 年的 Vertex Block Descent(VBD):不再逐个约束投影,而是逐个顶点做局部优化,顶点之间用着色并行。它是 XPBD 之外近年值得关注的另一条路线,本课不展开。
结构数组 + 按颜色或类型分批 + 一个物体一次调度;需要确定性时避免浮点原子加。
结构数组布局、原子操作与调度合并为 GPU 物理模拟的常见工程做法;Chen et al., Vertex Block Descent, SIGGRAPH 2024
要点
结构数组 + 按颜色或类型分批 + 一个物体一次调度;需要确定性时避免浮点原子加。
7.4速查
UE 的 Chaos Cloth 暴露了哪些参数
把前面的概念对到一个真实引擎上。UE 的 Chaos Cloth 有两代配置:骨骼网格上的布料配置(UChaosClothConfig 和共享的模拟配置),以及较新的 Cloth Asset(用 Dataflow 节点组织)。下表按本课的概念整理官方 API 文档里能看到的参数:
| 本课概念 | UE 参数 | 说明 |
|---|---|---|
| 子步、迭代(第 4 章) | Subdivision Count、Iteration Count、Max Iteration Count | 共享模拟配置;界面范围 1~10 |
| XPBD 与 PBD(第 2、3 章) | Cloth Asset 约束节点的 Solver Type:XPBD / PBD | 官方描述:XPBD 是“带柔度的物理 PBD”;旧配置里的 Use XPBD Constraints 标注为实验性 |
| 经纬向拉伸(5.2 节) | Stretch Stiffness Warp / Weft / Bias,各向同性或异性 | Cloth Asset 拉伸节点;静止长度可取自 3D 或 2D 版片 |
| 约束阻尼(3.5 节) | Stretch Damping | 以临界阻尼为单位 |
| 全局阻尼 | Damping Coefficient、Local Damping Coefficient | 前者是点阻尼,后者是局部阻尼 |
| 弯曲(5.3 节) | Bending Stiffness、Use Bending Elements、Buckling Ratio / Stiffness | 默认跨边弹簧,可切换为弯曲单元 |
| 面积、体积(5.4 节) | Area Stiffness、Volume Stiffness | |
| 长程附着(5.5 节) | Tether Stiffness、Tether Scale、Use Geodesic Distance | |
| 碰撞与摩擦(第 6 章) | Collision Thickness、Friction Coefficient | 厚度是加在碰撞形状上的 |
| 自碰撞(6.4 节) | Use Self Collisions / Self Collision Spheres / Self Intersections、Self Collision Thickness、Self Collision Friction | 点-面排斥、球体、解缠三种 |
| 精度(4.3 节) | Use Local Space Simulation | 远离原点时减少抖动 |
两点提醒。第一,UE 里大多数“刚度”参数是 0~1 之间的无量纲值,内部再映射到求解器使用的量,映射方式没有完整公开;所以不要把这些数直接当成 N/m 或 m/N。第二,这些名称会随版本调整(旧配置里不少属性已标记为废弃),以你所用版本的文档为准。
UE 把子步、迭代、求解器类型、各类约束刚度、阻尼、碰撞和自碰撞都暴露出来了;本课的每一章都能在参数表里找到对应。
UE 官方 API 文档:UChaosClothConfig、UChaosClothSharedSimConfig、EChaosClothAssetConstraintSolverType、FChaosClothAssetSimulationStretchConfigNode(UE 5.8)。参数名以文档为准,默认值和内部实现细节未全部公开
要点
UE 把子步、迭代、求解器类型、各类约束刚度、阻尼、碰撞和自碰撞都暴露出来了;本课的每一章都能在参数表里找到对应。
7.5设计
控制面:程序管预算,美术管手感
一个布料系统交给团队用,参数分两类,归两类人:
预算类,归程序。子步数、迭代次数、自碰撞开关、碰撞体数量上限、模拟距离(离相机多远就停止模拟或降级)。它们决定每帧的毫秒数,应该按平台和画质档位设定,不让单个资产随意改。
手感类,归美术。拉伸、弯曲、剪切的硬度,阻尼,摩擦,厚度,长程附着的松紧,以及哪些点钉在骨骼上(逆质量绘制)。它们决定这件衣服像丝绸还是牛仔布。
XPBD 让这种分工第一次真正成立:在收敛的前提下,柔度和迭代次数、时间步无关,所以程序为低端机减少子步,不会把美术调好的牛仔布变成丝绸。PBD 做不到这一点(2.5 节)。但“在收敛的前提下”很关键:子步减得太多,硬材料依然会变软。所以预算档位要配一个验收:每个档位用标准资产测一次伸长率,偏差超过阈值就报警。
手感参数的设计建议:
| 建议 | 原因 |
|---|---|
| 界面给 0~1 滑杆,内部按对数映射到柔度 | 柔度跨好几个数量级,线性滑杆的大部分区间没有可见变化 |
| 参数按材料打包成预设(丝绸、棉、牛仔、皮革) | 美术挑预设再微调,比从零调快得多,也便于跨资产统一 |
| 参数在资产层面以“每单位长度/面积”存储,求解时按网格分辨率换算 | 3.4 节:否则换一个精度的网格就要重调 |
| 可以按顶点绘制的参数(钉住权重、厚度、刚度倍率)用贴图或顶点色 | 披风的上半截硬、下半截软,是常见需求 |
| 提供调试视图:每条边的应变颜色、残差、接触点 | 美术说“这里太长了”,调试视图能告诉你是柔度问题还是收敛问题 |
预算参数按平台锁定,手感参数交给美术;XPBD 让两者第一次可以互不干扰,但要用伸长率验收守住“收敛”这个前提。
本节为设计建议,参考 7.4 节的 UE 参数组织方式与前文原理
预算类,归程序
子步数、迭代次数、自碰撞开关、碰撞体数量上限、模拟距离(离相机多远就停止模拟或降级)。
手感类,归美术
拉伸、弯曲、剪切的硬度,阻尼,摩擦,厚度,长程附着的松紧,以及哪些点钉在骨骼上(逆质量绘制)。
要点
预算参数按平台锁定,手感参数交给美术;XPBD 让两者第一次可以互不干扰,但要用伸长率验收守住“收敛”这个前提。
A
动手:写一个 XPBD 布料
八个里程碑,从一个下落的质点写到带碰撞和摩擦的布料。每一步都有完成标准。
这个附录把前七章变成一份施工图。建议先写 CPU 单线程版本,所有量用国际单位,等每个里程碑的完成标准都过了,再考虑并行和 GPU。
A.1路线
先定范围:最小可用版本
这个附录的目标是:照着做完,你会得到一个 CPU 单线程的 XPBD 布料,有拉伸、剪切、弯曲约束,长程附着,和球体、胶囊、地面的碰撞,以及静/动摩擦。它不追求性能,追求每一步都可验证。
建议按八个里程碑推进,每个都有可观察的完成标准:
| 里程碑 | 做什么 | 做完你应该看到 |
|---|---|---|
| M1 | 质点、重力、半隐式欧拉 | 自由落体 1 秒后下落 4.9 米(误差随步长减小) |
| M2 | 单个距离约束(PBD,k = 1) | 单摆:长度恒定,周期接近 2π√(L/g) |
| M3 | XPBD:λ、柔度 | 挂重物的绳子,伸长率与理论值 α·T 一致,且不随迭代次数变(收敛时) |
| M4 | 布料网格:拉伸、剪切、弯曲 | 四角钉住的布自然下垂;关掉剪切会变成菱形 |
| M5 | 子步 | 固定预算下,子步多的配置伸长更小(4.2 节的结论能复现) |
| M6 | 碰撞与摩擦 | 布料盖在球上不穿透;斜坡上 tanθ < μs 时停住 |
| M7 | 长程附着、阻尼、动画驱动的钉住点 | 快速转身时披风不再被拉长 |
| M8 | 自碰撞、图着色、性能回归 | 对折的布不互相穿透;并行版与串行版结果接近 |
第一版建议直接砍掉:GPU、自碰撞(先只做质点-质点)、各向异性、弯曲单元(先用跨边距离)、连续碰撞检测。每一样都能单独加回去,而且加的时候你会很清楚它解决什么问题。
先 CPU 单线程、SI 单位、可验证;性能和花样等闭环稳定之后再加。
里程碑划分为本文的建议
A.2算例
数据结构
所有量都用国际单位(米、千克、秒)。这不是洁癖:柔度有单位(3.4 节),用厘米或游戏单位,柔度的数值会差好几个数量级,从论文和真实材料里查到的数就没法直接用。
struct Particles { // 结构数组
vector<float3> x, xPrev, v; // 位置、子步开始时的位置、速度
vector<float> w; // 逆质量;0 = 钉住
vector<float> radius; // 碰撞半径
};
struct DistanceCon { int a, b; float rest, compliance, lambda; };
struct BendCon { int i0, i1, i2, i3; float rest, compliance, lambda; };
struct Tether { int i, pin; float maxLen; }; // 长程附着,单边,不需要 λ
struct Contact { int i; float3 n; float3 q; float depth; }; // 每子步重建
struct ClothParams { // 资产层面的“手感”,按单位长度/面积存储
float stretchCompliance; // 二维膜:每条边的柔度约等于 1/(E·t)
float shearCompliance, bendCompliance;
float damping, muStatic, muKinetic, thickness;
};
struct SolverBudget { // 平台层面的“预算”
int substeps, iterations;
bool selfCollision;
};质量的分配:每个三角形的质量 = 面密度 × 面积,平分给三个顶点。不要给每个顶点同样的质量,否则不规则网格的密的地方会更重。
约束的生成:遍历网格的边生成拉伸约束;四边形网格加对角线;每条内部边(被两个三角形共享)生成一个弯曲约束;每个自由顶点找最近的钉住点生成一个长程附着,maxLen 取静止距离 × 1.05 左右。
SI 单位、结构数组、手感参数和预算参数分开存;质量按面积分配。
本节为实现建议
要点
SI 单位、结构数组、手感参数和预算参数分开存;质量按面积分配。
A.3算例
M1~M3:主循环和第一个约束
主循环把子步放在最外层。外力、预测、投影、速度更新都在子步内,碰撞宽相在子步外:
void Step(float frameDt) {
int n = budget.substeps;
float h = frameDt / n;
BroadPhase(frameDt); // M6:每帧一次
for (int s = 0; s < n; s++) {
float t = (s + 1) / float(n);
UpdateKinematicPins(t); // M7:钉住点在帧内插值
for (i : particles) if (w[i] > 0) {
v[i] += h * gravity;
xPrev[i] = x[i];
x[i] += h * v[i];
}
ResetLambdas(); // 所有约束 lambda = 0
NarrowPhase(); // M6:生成本子步的接触
for (int it = 0; it < budget.iterations; it++) {
for (c : distance) SolveDistance(c, h);
for (c : bending) SolveBending(c, h);
for (t : tethers) SolveTether(t);
for (c : contacts) SolveContact(c, h); // 接触放最后,摩擦在里面(6.3 节)
}
for (i : particles) if (w[i] > 0) {
v[i] = (x[i] - xPrev[i]) / h;
}
ApplyDamping(h);
}
}M1 的验收。没有约束,一个质点从静止下落 1 秒,位置应接近 ½gt² = 4.9 米。半隐式欧拉会略多一点(误差正比于 h),子步加倍误差减半。这个测试能抓住“速度更新和位置更新顺序写反”“h 用了帧长而不是子步长”这类错误。
M2 的验收。一个距离约束连接固定点和质点,做成单摆。小角度下周期应接近 2π√(L/g);长度在任何时刻都等于 L(α = 0)。周期偏长且振幅衰减,是隐式方法的数值阻尼,加子步应明显改善。
M3 的验收。换成 XPBD,竖直挂一个 1 kg 的重物,α = 10⁻³ m/N,静止后伸长应为 α × 9.8 ≈ 9.8 毫米。迭代 5 次和 50 次(加足够子步保证收敛),伸长相同。把 λ 的清零写错(每次迭代清零,或从不清零),这个测试会立刻失败:前者伸长偏大,后者会持续漂移。
先过 M1~M3 三个数值测试,再碰布料;它们能抓住主循环里几乎所有的顺序错误。
本节为实现建议,算法依据 2.1、3.3、第 4 章
M1 的验收
没有约束,一个质点从静止下落 1 秒,位置应接近 ½gt² = 4.9 米。
M2 的验收
一个距离约束连接固定点和质点,做成单摆。
M3 的验收
换成 XPBD,竖直挂一个 1 kg 的重物,α = 10⁻³ m/N,静止后伸长应为 α × 9.8 ≈ 9.8 毫米。
要点
先过 M1~M3 三个数值测试,再碰布料;它们能抓住主循环里几乎所有的顺序错误。
A.4算例
M4~M5:布料网格与子步
从一个规则网格开始:20 × 20 个顶点,边长 5 厘米,面密度 0.2 kg/m²(中等厚度的棉布)。顶边的两个角钉住。
约束顺序。同一次迭代里先拉伸、再剪切、再弯曲、最后接触。规律是:越重要、越“硬”的约束越往后放,因为高斯-赛德尔里最后修的满足得最好。不穿透最重要,所以接触最后。
M4 的验收。布自然下垂,没有锯齿状的抖动;关掉剪切约束,网格会被重力拉成菱形;关掉弯曲约束,布会像塑料薄膜一样随意折叠。三个开关分别对应三种可见的现象,比单纯“看起来还行”可靠得多。
M5 的验收。复现 4.2 节的实验:总预算固定(比如每帧 16 次投影),比较“1 子步 × 16 迭代”和“16 子步 × 1 迭代”,记录下垂稳定后最长边的伸长率。后者应明显更小。如果结果相反,检查 α̃ 是否用子步长计算、λ 是否每个子步清零、外部钉住点是否每子步插值。
布料开始有趣之后,务必做一个调试视图:每条边按应变着色(就像封面那张网),再加一个残差曲线。之后所有“这里怎么拉长了”的问题,先看这两个视图。
约束顺序:越重要越往后;用三个开关的可见现象验收 M4,用固定预算实验验收 M5。
本节为实现建议,依据第 4、5 章
约束顺序
同一次迭代里先拉伸、再剪切、再弯曲、最后接触。
M4 的验收
布自然下垂,没有锯齿状的抖动;
M5 的验收
复现 4.2 节的实验:总预算固定(比如每帧 16 次投影),比较“1 子步 × 16 迭代”和“16 子步 × 1 迭代”,记录下垂稳定后最长边的伸长率。
要点
约束顺序:越重要越往后;用三个开关的可见现象验收 M4,用固定预算实验验收 M5。
A.5算例
M6~M8:碰撞、附着与自碰撞
M6:碰撞与摩擦。先做解析形状:地面、球、胶囊。每个形状实现一个函数,输入点的位置,输出有符号距离和法线;接触约束用 6.1 节的单边投影,摩擦用 6.3 节的位置级规则。验收:布从一米高落在球上,最终静止且没有任何顶点在球内;斜坡测试复现 tanθ = μs 的临界角。
M7:附着、阻尼、动画驱动。让钉住点跟着一个旋转的骨骼走。没有长程附着时,快速旋转会把布甩长;加上之后,最远顶点到钉住点的距离不超过 maxLen。阻尼用 3.5 节表里的第二种(沿边的相对速度阻尼)入门最容易,记得乘 h。
M8:自碰撞与并行。先做质点-质点自碰撞:空间哈希,格子边长 = 2 × 半径,排除网格上两跳以内的邻居。然后做图着色并行,验收标准是:同样的输入,并行版和串行版每帧的最大位置差在毫米量级,长时间运行不出现系统性的偏移。
void SolveSphere(int i, float3 c, float R, float h) {
float3 d = x[i] - c;
float dist = length(d);
float C = dist - (R + radius[i]);
if (C >= 0 || dist < 1e-8f) return;
float3 n = d / dist;
x[i] -= C * n; // 法向:推出去(钉住点 w=0 不在此列)
float3 dx = x[i] - xPrev[i] - colliderMove(c, h); // 相对碰撞体的位移
float3 dt = dx - dot(dx, n) * n;
float len = length(dt), dn = -C;
if (len < muStatic * dn) x[i] -= dt;
else if (len > 0) x[i] -= dt * min(muKinetic * dn / len, 1.0f);
}M6 用解析碰撞体和斜坡测试,M7 用旋转骨骼测试长程附着,M8 用“并行版与串行版一致”验收。
本节为实现建议,依据第 5、6、7 章
M6:碰撞与摩擦
先做解析形状:地面、球、胶囊。
M7:附着、阻尼、动画驱动
让钉住点跟着一个旋转的骨骼走。
M8:自碰撞与并行
先做质点-质点自碰撞:空间哈希,格子边长 = 2 × 半径,排除网格上两跳以内的邻居。
要点
M6 用解析碰撞体和斜坡测试,M7 用旋转骨骼测试长程附着,M8 用“并行版与串行版一致”验收。
A.6清单
坑表:XPBD 实现最常见的十个问题
λ 没有累积(每次迭代都当成 0),或者分子漏了 −α̃λ。这时 XPBD 退化成了带柔度系数的 PBD。
α̃ 用的是帧长而不是子步长;或者阻尼没有乘 h;或者全局阻尼写成了固定的“每帧乘 0.99”。
λ 从不清零,跨时间步累积;或者速度更新用了投影前的位置。也可能是浮点精度:离原点太远,改到局部空间模拟。
质量比太大,收敛不了。加子步、加长程附着,或者把重物当作钉住点驱动布料而不参与同一组迭代。
一维结构(绳、头发)每段的柔度没有按段长缩放;或者弯曲柔度没有按边长和面积换算。
距离约束的方向 d/|d| 在 |d| = 0 时无定义。所有约束都要有退化分支(3.3 节代码里的提前返回)。
碰撞体和钉住点在子步之间没有插值,第一个子步就瞬移到帧末位置。
碰撞厚度小于每子步的移动距离,或者接触约束不在迭代的最后,被后面的约束又拉进去了。
摩擦用了绝对位移而不是相对碰撞体的位移;或者摩擦用了整帧的位移而不是本子步的。
高斯-赛德尔的顺序偏向。交替正反顺序,或者轮换颜色的执行顺序。
这张表和下一节的验证清单一一对应:每个坑都有一个能抓住它的测试。
大多数 XPBD 的 bug 出在三处:λ 的生命周期、h 用错了哪个、外部输入没有插值。
本节根据前文原理整理;属于实现经验,不同项目的表现可能不同
伸长率随迭代次数变
换了帧率,布料的手感变了
静止的布料慢慢漂移、抖动
重物挂在布上,布被拉得很长
细分网格后布料变软了
两点重合时出现 NaN
要点
大多数 XPBD 的 bug 出在三处:λ 的生命周期、h 用错了哪个、外部输入没有插值。
A.7清单
验证清单
| 测试 | 设置 | 通过标准 | 抓什么 |
|---|---|---|---|
| 自由落体 | 无约束,1 秒 | 下落 ≈ 4.9 米,误差随 h 线性减小 | 积分顺序、h 用错 |
| 单摆 | 一个距离约束,α = 0 | 长度恒定;周期 ≈ 2π√(L/g) | 投影公式、逆质量 |
| 挂重物 | 竖直绳,α = 10⁻³ | 伸长 = α × 张力;与迭代次数无关 | λ 的累积和清零 |
| 帧率无关 | 同一场景,30 / 60 / 120 帧 | 稳定后的形状一致(差异在毫米量级) | α̃、阻尼、外力是否乘对 h |
| 分辨率无关 | 同一块布,10×10 与 40×40 | 下垂的整体形状接近 | 柔度和质量的换算 |
| 斜坡 | 单质点,改变 θ | tanθ = μs 处由停转滑 | 摩擦的公式和位移选取 |
| 盖球 | 布落在球上 | 静止,无顶点在球内 | 接触顺序、厚度 |
| 甩披风 | 钉住点快速旋转 | 最远点不超过 maxLen | 长程附着、插值 |
| 并行一致 | 着色并行 vs 串行 | 逐帧位置差在毫米量级 | 着色是否有冲突 |
| 性能回归 | 标准资产,每档预算 | 耗时与伸长率都在阈值内 | 预算档位是否破坏收敛 |
把这些测试做成自动化的:每次改求解器都跑一遍。第 9 章和附录 B 会把这个 XPBD 当作“老师”生成训练数据,老师错了,网络学到的也是错的。
十个测试,每个抓一类 bug;XPBD 要当老师之前,必须先全部通过。
本节为实现建议
要点
十个测试,每个抓一类 bug;XPBD 要当老师之前,必须先全部通过。
页签 6 / 7
GNN:从数据里学模拟
用 XPBD 的语言读图神经网络:图、消息传递、编码-处理-解码、训练与噪声注入、滚动漂移。
08
GNN:让网络沿着网格传消息
图神经网络把模拟写成节点之间沿边传消息,和 XPBD 沿约束传修正量是同一种结构。
前面的 XPBD 是手写规则:每条约束是什么、怎么修,都由人定义。图神经网络(GNN)走另一条路:从模拟数据里学出“下一帧会怎样”。
这一章用 XPBD 读者熟悉的语言讲 GNN:网格就是图,约束就是边,一轮消息传递像一次雅可比迭代。然后讲 GNS 和 MeshGraphNets 的结构、训练方法,以及纯学习模拟器最大的问题:滚动漂移。
8.1比喻
把布料写成一张图
XPBD 里,布料是“质点 + 约束”。图神经网络里,它是“节点 + 边”。两者几乎一一对应:
| XPBD | GNN | 常见的特征 |
|---|---|---|
| 质点 | 节点 | 最近几步的速度(位移 / h)、节点类型(自由 / 钉住 / 碰撞体)、逆质量、材料参数 |
| 距离、弯曲约束 | 网格边(mesh edge) | 两端的相对位移(当前形状和静止形状各一份)、当前长度、静止长度 |
| 接触约束 | 世界边(world edge) | 当前空间里距离很近、但网格上相距很远的两点之间临时连的边,用来表达碰撞和自碰撞 |
两类边的区分来自 MeshGraphNets(Pfaff 等,2021):网格边是固定的,表达材料内部的相互作用;世界边每帧按空间距离重建,表达接触。这和第 6 章“约束固定、接触每子步重建”的结构完全一样。
特征的设计有一条铁律:只用相对量,不用绝对位置。一块布在原点和在一千米外,物理规律相同;如果网络的输入里有绝对坐标,它就得为每个位置分别学一遍,而且换一个位置就失效。同样的道理,速度用位移差,边用两端的相对位移。这和 4.3 节“在局部空间模拟”是同一个直觉。
输出通常是每个节点的加速度(或下一步的位移),再由网络外面的积分器(半隐式欧拉)得到位置。让网络预测加速度而不是位置,是因为加速度的数值范围稳定、和时间步的关系明确,好归一化。
质点是节点,约束是网格边,接触是世界边;特征只用相对量,输出预测加速度。
Sanchez-Gonzalez et al., Learning to Simulate Complex Physics with Graph Networks, ICML 2020;Pfaff et al., Learning Mesh-Based Simulation with Graph Networks (MeshGraphNets), ICLR 2021
要点
质点是节点,约束是网格边,接触是世界边;特征只用相对量,输出预测加速度。
8.2交互图
消息传递:一轮只走一跳
GNN 的核心操作是消息传递。每一轮分两步:
先更新每条边:看边自己和两端节点的状态,算出一条“消息”。再更新每个节点:把所有指向它的消息加起来,结合自己的状态,算出新状态。
e′ij = φe( eij, vi, vj ) v′i = φv( vi, Σj e′ij )
- φe, φv
- 两个小的多层感知机(MLP),所有边、所有节点共享同一组参数
- Σ
- 对邻居求和:和顺序无关,所以节点的邻居怎么排列都不影响结果
对照 XPBD:边更新像“每个约束算出自己的修正量”,节点更新像“每个质点把作用在它身上的修正量加起来”。这正是 7.1 节雅可比式迭代的结构:所有边同时基于旧状态计算,再一起汇总。区别是 XPBD 的“消息”是手写的投影公式,GNN 的消息是学出来的函数。
由此马上得到一个重要推论:K 轮消息传递,信息只能传 K 跳。布料一端被猛拉,拉力应该在一帧内传遍整块布(硬材料尤其如此);如果网络只做 10 轮,10 跳以外的点在这一帧里什么都不知道。这是 GNN 版本的“刚度问题”,和 XPBD 的收敛问题同源。
对策也类似:XPBD 用长程附着(5.5 节)直接连到远处的钉住点;GNN 用层级图,在粗化的网格上传消息,一跳顶细网格的好几跳。HOOD(Grigorev 等,2023)用三层层级图,把单步的传播半径从 15 条边扩大到 48 条边。
一轮消息传递就是一次雅可比式迭代,只走一跳;硬材料需要层级图或者更多轮。
Battaglia et al., Relational inductive biases, deep learning, and graph networks, 2018(图网络框架);Grigorev et al., HOOD, CVPR 2023(层级图的传播半径)
8.3流程
编码-处理-解码:GNS 和 MeshGraphNets 的结构
GNS 和 MeshGraphNets 用的是同一个三段结构:
编码器:两个 MLP 把节点特征和边特征分别映射成固定长度的隐向量(比如 128 维)。从这里开始,网络不再直接处理物理量。
处理器:M 轮消息传递,每轮一组 φe、φv,带残差连接(新状态 = 旧状态 + 更新量),让深层网络好训练。GNS 的典型配置是 10 轮,MeshGraphNets 是 15 轮,每轮参数不共享。
解码器:一个 MLP 把每个节点的隐向量映射回物理量(加速度)。然后在网络外面积分:v ← v + h·a,x ← x + h·v。
State Step(const State& s, const Graph& g, Net& net, float h) {
Features f = BuildFeatures(s, g); // 相对位移、速度历史、类型……(8.1 节)
f = Normalize(f, net.inputStats); // 用训练集统计量归一化
Latent z = net.Encode(f);
for (int m = 0; m < net.M; m++)
z = z + net.Process[m](z, g); // 消息传递 + 残差
float3 a[] = Denormalize(net.Decode(z), net.outputStats);
State out = s;
for (i : nodes) if (type[i] == Free) { // 钉住点和碰撞体不由网络决定
out.v[i] = s.v[i] + h * a[i];
out.x[i] = s.x[i] + h * out.v[i];
}
return out;
}注意最后的分支:钉住点和碰撞体的位置由动画给出,网络对它们的输出直接丢弃。这和 XPBD 里“逆质量为 0”的作用一样,是学习模拟器里最基本的硬约束,而且是唯一一个不需要求解器就能保证的约束。
和 XPBD 的成本对比:XPBD 每次迭代对每个约束做十几次浮点运算;GNN 每轮对每条边、每个节点各跑一个 MLP,每个 MLP 是几万次乘加。所以 GNN 的单轮成本比 XPBD 的单次迭代高两三个数量级。它要划算,必须用少得多的“轮数”换来 XPBD 需要大量子步才能达到的效果,或者换来 XPBD 根本没建模的现象。
编码、M 轮消息传递、解码出加速度、网络外积分;钉住点直接覆盖。单轮成本远高于一次 XPBD 迭代。
Sanchez-Gonzalez et al., GNS, ICML 2020;Pfaff et al., MeshGraphNets, ICLR 2021
编码器
:两个 MLP 把节点特征和边特征分别映射成固定长度的隐向量(比如 128 维)。
处理器
:M 轮消息传递,每轮一组 φe、φv,带残差连接(新状态 = 旧状态 + 更新量),让深层网络好训练。
解码器
:一个 MLP 把每个节点的隐向量映射回物理量(加速度)。
要点
编码、M 轮消息传递、解码出加速度、网络外积分;钉住点直接覆盖。单轮成本远高于一次 XPBD 迭代。
8.4推导
训练:一步监督,再加噪声
最直接的训练方式是一步监督:用一个可信的模拟器生成大量轨迹,每一对相邻帧 (状态t, 状态t+1) 就是一个样本。网络输入状态t,预测加速度,和真实加速度做均方误差。
问题在于测试时网络要滚动:把自己的输出当作下一步的输入,连续推几百帧。训练时它只见过“真实状态”,测试时却要面对“被自己的误差污染过的状态”,这是一种分布偏移。小误差让输入偏离训练分布,偏离的输入让误差更大,越推越歪。
GNS 的对策很简单却很有效:训练时给输入加噪声。在输入的速度历史上加随机游走噪声,同时把目标加速度修正成“能把这个带噪声的状态拉回真实轨迹”的值。这样网络学到的不只是“下一步怎么走”,还有“偏了怎么拉回来”。MeshGraphNets 沿用了这一做法。
另一条路线是自监督物理损失:不需要真实轨迹,直接用物理能量当损失。HOOD 的损失由惯性、重力、拉伸(StVK 有限元)、弯曲(二面角)、碰撞惩罚和摩擦组成,基于隐式欧拉的增量势能。网络输出让这个势能最小的加速度,等于是在学一个隐式欧拉求解器。
后者和 XPBD 的关系非常近:3.2 节的推导就是在最小化“惯性项 + ½·C²/α̃”。9.7 节会把 XPBD 的这个目标函数直接拿来当训练损失。
一步监督要配噪声注入,否则网络没见过自己犯的错;自监督物理损失则直接把隐式欧拉的能量当老师。
Sanchez-Gonzalez et al., GNS, ICML 2020(训练噪声);Pfaff et al., MeshGraphNets, ICLR 2021;Grigorev et al., HOOD, CVPR 2023(自监督物理损失)
要点
一步监督要配噪声注入,否则网络没见过自己犯的错;自监督物理损失则直接把隐式欧拉的能量当老师。
8.5清单
滚动漂移和学习模拟器的四个弱点
纯学习的模拟器已经能生成很好看的布料,但有四个结构性的弱点。每一个都对应 XPBD 的一个强项,这就是第 9 章组合的出发点。
| 弱点 | 表现 | XPBD 的对应强项 |
|---|---|---|
| 滚动漂移 | 误差逐帧累积,几百帧后形状、能量偏离;噪声注入能缓解但不能消除 | 每一步都把状态投影回约束流形,误差不会以“违反约束”的形式累积 |
| 不保证硬约束 | 布料轻微拉长、穿过身体几毫米;在近景里很显眼 | 不可伸长(α = 0)和不穿透是直接被投影保证的 |
| 传播半径有限 | 硬材料、长距离的拉力在一步内传不到(8.2 节) | 高斯-赛德尔扫描、长程附着可以一步传到底 |
| 分布外泛化 | 没见过的动作、材料、分辨率、拓扑,结果可能离谱 | 规则是手写的,对任何输入都成立 |
反过来,XPBD 的弱点也正是网络的强项:硬材料需要大量子步(贵);没被建模的现象(真实布料的复杂各向异性、褶皱的细节、与真实数据对齐的手感)写不出约束;参数调起来全靠经验。
值得一提的是 C-GNS(Rubanova 等,2022):它让 GNN 学一个标量约束函数,下一个状态通过对这个函数做梯度下降来求解,相当于“学习约束,再像 XPBD 那样求解”。它报告的好处之一,是测试时多做几次求解迭代就能提高精度,还可以在测试时加入手写的约束。这已经是一种组合思路了。
学习模拟器会漂、不守硬约束、传播半径有限、分布外不可靠;这四点恰好是 XPBD 的强项。
Sanchez-Gonzalez et al., GNS, ICML 2020;Pfaff et al., MeshGraphNets, ICLR 2021;Rubanova et al., Constraint-based Graph Network Simulator, ICML 2022;弱点的归纳为本文整理
要点
学习模拟器会漂、不守硬约束、传播半径有限、分布外不可靠;这四点恰好是 XPBD 的强项。
页签 7 / 7
GNN + XPBD:组合与落地
五种组合方式,哪类误差归网络、哪类归求解器;然后是一个组合系统从数据到上线的施工图、坑表和验证。
09
GNN + XPBD:一个提议,一个校正
网络擅长给出快而大致对的答案,XPBD 擅长守住硬约束。组合的关键是分清哪类误差归谁。
纯 XPBD 准但贵(硬材料需要大量子步);纯 GNN 快但会漂、会穿模,而且不保证约束。把两者接起来有好几种方式,每种方式让网络承担不同的工作。
这一章先列出五种组合,再逐一展开:网络提议、XPBD 校正;XPBD 主算、网络补残差;网络给 λ 热启动;网络预测材料参数(借助可微 XPBD);XPBD 当老师,提供训练数据和物理损失。
9.1对比
为什么要组合:各自擅长什么
| XPBD | GNN | |
|---|---|---|
| 硬约束(不伸长、不穿透、钉住) | 直接保证(收敛时) | 只能近似学到;钉住点可以直接覆盖 |
| 硬材料的成本 | 需要大量子步,成本随刚度上升 | 与刚度关系不大,但传播半径受轮数限制 |
| 单次“迭代”的成本 | 每个约束十几次浮点运算 | 每条边、每个节点一个 MLP,高两三个数量级 |
| 未建模的现象(复杂材料、细节褶皱) | 写不出来就没有 | 能从数据里学到 |
| 泛化 | 规则对任何输入都成立 | 依赖训练分布 |
| 可调性 | 参数有物理意义 | 改手感通常要重新训练,或者把参数作为输入 |
| 误差累积 | 投影把状态拉回约束 | 滚动漂移 |
这张表的读法是:约束能表达的东西交给 XPBD,约束表达不了或者算起来太贵的东西交给网络。不可伸长、不穿透、钉住,这些用约束表达既精确又便宜,没理由让网络去近似。复杂材料的手感、大步长下的动力学、细节褶皱,写成约束很难或者很贵,正是网络的用武之地。
组合的成本要实打实地算。网络推理的固定开销不小,如果组合系统在同样的画面误差下并不比“多加几个 XPBD 子步”更快,它就没有存在的理由。附录 B 的验证清单把这一条列为最终指标。
约束能表达的交给 XPBD,约束表达不了或算起来太贵的交给网络;组合必须在同等误差下比纯 XPBD 更快,或在同等时间下更准。
本节为综合比较;成本量级为推断,取决于网格规模、网络大小和硬件
要点
约束能表达的交给 XPBD,约束表达不了或算起来太贵的交给网络;组合必须在同等误差下比纯 XPBD 更快,或在同等时间下更准。
9.2速查
五种组合方式
| 模式 | 网络输出 | XPBD 做什么 | 保证了什么 | 代表或相近工作 | 节 |
|---|---|---|---|---|---|
| ① 网络提议,XPBD 校正 | 下一步的位置(或加速度) | 把提议投影回硬约束 | 不伸长、不穿透、钉住 | Neural Projections(Yang 等,2020);C-GNS(Rubanova 等,2022)思路相近 | 9.3 |
| ② XPBD 主算,网络补残差 | 对 XPBD 结果的修正量(或细网格上的细节) | 在粗网格或少子步下完整模拟 | 大尺度运动由物理决定 | UE ML Deformer 属于同类思路的工业做法(不基于 XPBD) | 9.4 |
| ③ 网络热启动 | λ 或位置的初值 | 照常迭代,只是起点更好 | 解与纯 XPBD 相同(收敛时) | 推断 | 9.5 |
| ④ 网络定参数 | 每条边的柔度、阻尼等 | 用这些参数正常模拟 | 运行时就是纯 XPBD | DiffXPBD(Stuyck 与 Chen,2023)提供梯度 | 9.6 |
| ⑤ XPBD 当老师 | (训练时) | 生成数据,或提供物理损失 | 训练目标有物理意义 | HOOD(Grigorev 等,2023)的自监督物理损失 | 9.7 |
五种模式可以叠加。比如一个实际系统可能是:用 ⑤ 训练一个 ① 的网络,再用 ④ 让美术的材料参数作为网络的输入。读后面几节时,留意每种模式里误差由谁负责:网络的哪些错误会被 XPBD 修掉,哪些会原封不动地留下来。
五种组合,网络分别负责:提议、补残差、给初值、定参数、(训练时)当学生。
各模式的代表工作见每行;“残差修正”和“热启动”两种在本课中按一般做法描述,未对应单一代表论文(推断)
要点
五种组合,网络分别负责:提议、补残差、给初值、定参数、(训练时)当学生。
9.3交互图
① 网络提议,XPBD 校正
最直接的组合:网络代替 XPBD 的“预测”那一步,给出下一帧的位置提议 x̂;XPBD 对 x̂ 做 K 次投影,守住硬约束;速度照常用 (x − xprev)/h 更新。Yang 等人 2020 年的 Neural Projections 走的就是“预测 + PBD 式投影”的结构,只是他们连约束本身也是学出来的。
下面的交互图里,“网络”是模拟出来的:用一个准确的求解器算出下一步,再在加速度上人为加三种误差。这样可以单独观察每种误差在投影之后的命运。投影包括每段的距离约束和到顶端的长程附着(5.5 节);没有后者,高斯-赛德尔几乎修不动“整根绳子均匀拉长”这种误差(7.1 节)。
三种误差的结局完全不同:
违反约束的误差(把绳子拉长):投影把它修掉了,投影次数越多,轨迹和能量越贴近参考。这正是这个组合的价值所在。
不违反约束的随机误差:沿绳子横向的噪声不违反任何约束,XPBD 看不到它;被修掉的那部分又通过速度更新变成了速度。结果是约束守住了,但绳子在抖,能量在漂。
系统偏差(比如网络凭空多学了一股风):完全不违反约束,投影修不到,轨迹持续偏离。
由此得到这个模式的设计原则:XPBD 只负责约束方向上的误差,自由方向上的误差必须由网络自己学好。训练时把投影放进循环(附录 B),网络就会学到“哪些错误会被修掉,不必在意;哪些不会,必须做准”。另外,投影修正量变成速度的问题,可以在训练损失里加一项“修正量的大小”来抑制,让网络的提议本身就接近约束流形。
投影能修掉违反约束的误差,修不掉约束允许范围内的误差;后者必须靠训练。
Yang, He, Zhu, Learning Physical Constraints with Neural Projections, NeurIPS 2020;交互图用“真值 + 人为误差”模拟网络输出,不是训练出来的网络
9.4设计
② XPBD 主算,网络补残差
反过来分工:XPBD 在便宜的配置下完整模拟(粗网格、少子步),网络负责把结果“补”到高质量配置的样子。网络学的是两者之差:
最终结果 = 便宜的 XPBD 结果 + 网络预测的修正量。网络的训练目标是“高质量 XPBD 结果 − 便宜 XPBD 结果”。
xfinal = xXPBD, cheap + Δnet( 状态, 特征 )
它有几个吸引人的性质。大尺度的运动(摆动、下垂、和身体的碰撞)由物理决定,网络出错时最多是细节不对,不会让整件衣服飞走。修正量的数值范围小,比学完整的动力学容易得多。网络可以作用在更细的网格上:粗网格跑 XPBD,网络把细节褶皱“画”上去。
它的风险在于修正量不受约束:加上去之后可能重新造成穿模或拉伸。常见对策是修正之后再做一次便宜的投影(只做接触和钉住),或者让网络的修正只作用于显示用的网格,不回写模拟状态,这样误差不会累积。
工业界有一个思路相近的系统:UE 的 ML Deformer 框架。它的训练输入是骨骼网格、覆盖大范围动作的动画序列,以及作为目标的几何缓存,学的是在线性蒙皮之上的形变修正;其中的 Nearest Neighbor 模型官方说明是面向服装这类复杂网格的,并标注为实验性。它不基于 XPBD,而是由姿态驱动,但“便宜的基础结果 + 学到的修正”这一结构是一样的。目标几何缓存可以来自离线的高质量布料模拟(这是推断,文档没有限定来源)。
便宜的 XPBD 管大尺度运动,网络补高质量与低质量之差;修正量最好只作用于显示,或者修正后再投影一次。
UE ML Deformer 框架官方文档(训练输入为骨骼网格、动画序列与目标几何缓存;Nearest Neighbor 模型面向服装且为实验性);残差学习的一般做法为本文整理
xfinal = xXPBD, cheap + Δnet( 状态, 特征 )
最终结果 = 便宜的 XPBD 结果 + 网络预测的修正量。网络的训练目标是“高质量 XPBD 结果 − 便宜 XPBD 结果”。
要点
便宜的 XPBD 管大尺度运动,网络补高质量与低质量之差;修正量最好只作用于显示,或者修正后再投影一次。
9.5深挖
③ 网络热启动:只给初值,不改答案
这是最“保守”的组合:网络不决定结果,只给迭代一个更好的起点。两种起点可以热启动:
位置初值:用网络的提议代替惯性预测 x̃ 作为迭代的初值,但动量方程里的 x̃ 仍然用惯性预测。这样 XPBD 求解的还是原来那个隐式欧拉方程,只是从一个更近的地方出发。和模式 ① 的区别在这里:① 把网络的提议当成了惯性预测本身,改变了要解的方程;③ 不改方程。代价是:严格来说,这要求求解器把 M(x − x̃) 那一项也纳入迭代(标准 XPBD 假设它近似为 0,见 3.2 节),实现要比标准 XPBD 复杂一些。
λ 初值:XPBD 每个时间步把 λ 清零。如果网络能预测出接近收敛值的 λ(约束力 × h²),迭代一开始就接近平衡。看更新公式:λ 出现在分子 −α̃λ 里,所以 λ 的初值不改变不动点(收敛时仍满足 C + α̃λ = 0),只改变从哪里出发。
这两种做法的共同性质:迭代足够多时,结果和纯 XPBD 完全一样,网络只能加速,不会改变答案。所以它是最安全的组合,适合对结果有严格要求的场合。代价也很明显:如果迭代次数本来就少(子步 + 每步 1 次迭代),初值的错误会直接留在结果里,热启动退化成了模式 ①。
值得先试的基线:用上一子步收敛的 λ 作为这一子步的初值(经典的时间相干热启动),不需要网络。只有当这个基线不够好时,网络热启动才值得做。
热启动不改要解的方程,只改起点:迭代够多时答案和纯 XPBD 一致。先试“用上一步的 λ”这个免费的基线。
本节为推断:基于 3.3 节的更新公式与迭代法的一般性质,未对应特定论文
位置初值
:用网络的提议代替惯性预测 x̃ 作为迭代的初值,但动量方程里的 x̃ 仍然用惯性预测。
λ 初值
:XPBD 每个时间步把 λ 清零。
要点
热启动不改要解的方程,只改起点:迭代够多时答案和纯 XPBD 一致。先试“用上一步的 λ”这个免费的基线。
9.6深挖
④ 网络定参数:可微 XPBD
另一种分工完全不碰运行时:网络(或优化器)只负责找出 XPBD 的参数,运行时跑的是纯 XPBD。
典型的问题是材料估计:有一段真实布料的视频或者一段高质量离线模拟,想知道 XPBD 的每条边该设什么柔度、阻尼,才能复现它。手调要几天;如果 XPBD 可微,就能用梯度下降直接求。
Stuyck 和 Chen 2023 年的 DiffXPBD 给出了 XPBD 对参数的解析梯度。论文展示的应用包括:估计弹性材料参数、优化初始条件、只从衣服的形状反推身体的形状和姿态、优化一串随时间变化的外力让模拟命中稀疏的关键帧形状。他们报告的规模超过两千六百万自由度,碰撞也包含在框架里,并且说明把一个现有的 XPBD 求解器改成可微的只需要少量修改,CPU 和 GPU 都能跑。
和网络结合的方式有两种。一是直接优化:参数就是优化变量,每个资产单独优化一次,结果存进资产。二是摊销:训练一个网络,输入资产的特征(版片、面料描述、目标视频),输出参数;训练时损失穿过可微 XPBD 回传。后者的好处是新资产不需要再跑优化。
这个模式最大的优点是运行时没有任何风险:上线的就是 XPBD 和一组参数,所有第 7 章的工程结论都成立,美术还能在网络给出的参数上继续手调。
让网络或优化器找参数,运行时跑纯 XPBD:零运行时风险,代价是需要一个可微的 XPBD。
Stuyck & Chen, DiffXPBD: Differentiable Position-Based Simulation of Compliant Constraint Dynamics, SCA 2023(arXiv 2301.01396)
要点
让网络或优化器找参数,运行时跑纯 XPBD:零运行时风险,代价是需要一个可微的 XPBD。
9.7推导
⑤ XPBD 当老师:数据和物理损失
无论选哪种运行时组合,训练时都需要一个“老师”。XPBD 可以用两种方式当老师。
当数据源。用高质量配置(大量子步、充分迭代)的 XPBD 生成轨迹,作为监督数据。好处是可控:能随机化动作、材料、风,能保证数据满足约束。前提是这个 XPBD 本身通过了附录 A 的验证清单,否则网络学到的是它的 bug。
当损失函数。3.2 节的推导其实在说:隐式欧拉的一步,等价于最小化下面这个能量:
惯性项(离预测位置有多远,按质量加权)+ 每个约束的弹性势能(按柔度和步长缩放)。
E(x) = ½ (x − x̃)ᵀ M (x − x̃) + Σc Cc(x)² / (2 α̃c)
- x̃
- 惯性预测
- α̃c
- αc / h²
如果网络直接输出下一步的位置 x,把 E(x) 当作损失,就不需要任何真实轨迹:网络在学的就是“一步隐式欧拉的解”。这正是 HOOD 的思路,只是它的能量项用的是有限元材料(StVK 拉伸、二面角弯曲)加上碰撞惩罚和摩擦。用 XPBD 的约束来写这个能量,好处是和你的运行时求解器用的是同一套约束和参数。
两个细节。α = 0 的硬约束会让 1/α̃ 无穷大,训练时要么用一个很小但非零的 α,要么把硬约束交给运行时的投影(模式 ①),损失里只放软约束。碰撞是不等式约束,损失里通常写成单边惩罚 max(0, −C)p。
XPBD 的一步等价于最小化“惯性 + 约束势能”;把它当损失,网络就能在没有真实轨迹的情况下学会一步隐式欧拉。
Grigorev et al., HOOD, CVPR 2023(增量势能作为自监督损失);Macklin et al., XPBD, 2016(目标函数形式);以 XPBD 目标作为损失为本文的推导
E(x) = ½ (x − x̃)ᵀ M (x − x̃) + Σc Cc(x)² / (2 α̃c)
惯性项(离预测位置有多远,按质量加权)+ 每个约束的弹性势能(按柔度和步长缩放)。
要点
XPBD 的一步等价于最小化“惯性 + 约束势能”;把它当损失,网络就能在没有真实轨迹的情况下学会一步隐式欧拉。
B
动手:搭一个 GNN + XPBD 系统
先有一个可信的 XPBD 当老师,再让网络学;训练时就把投影放进循环。
最后一个附录把第 9 章变成施工图:数据怎么生成、网络的输入输出怎么定、每一帧的调用顺序、训练的损失和课程、常见的坑和验证方法,最后是参考文献和证据等级。
B.1清单
数据:先有一个可信的老师
以模式 ①(网络提议,XPBD 校正)为例,从头搭一个组合系统。第一步是数据。
老师。用附录 A 写的 XPBD,开到高质量配置:子步多到伸长率不再随子步变化(比如 32~64 个子步),自碰撞打开。它必须先通过 A.7 节的全部测试。另外建议打开确定性模式(7.3 节),否则同一个场景每次生成的数据都不一样,训练时出问题很难复现。
随机化什么。决定网络能泛化到哪里。至少覆盖:
| 维度 | 怎么随机化 | 不覆盖的后果 |
|---|---|---|
| 动作 | 动作库随机片段 + 速度缩放 + 突然的转身、急停 | 上线后遇到急转身,布料飞出去 |
| 材料 | 柔度、弯曲、阻尼、摩擦在对数区间内采样,并作为网络输入 | 美术一改参数就要重新训练 |
| 初始状态 | 从静止、从上一段动作的中间开始 | 只会处理“从静止开始”的情况 |
| 外力 | 风的方向和强度 | 有风的关卡里表现异常 |
| 网格 | 同一件衣服的两三个分辨率 | 换 LOD 就失效 |
存什么。每帧存所有节点的位置(显示帧率,比如 60 Hz,而不是子步),以及碰撞体和钉住点的位置、材料参数。帧率必须和运行时一致:网络学的是“1/60 秒之后会怎样”,换成 1/30 秒就是另一个问题了。
怎么切分。按动作切分训练集和测试集,而不是按帧随机切分。按帧切分时,测试帧的前后帧都在训练集里,测出来的误差会好看得多,但说明不了泛化。
老师要先过验证、开确定性;随机化决定泛化范围;帧率和运行时一致;按动作切分数据。
本节为实现建议;噪声注入依据 GNS(2020),数据随机化为常见做法
老师
用附录 A 写的 XPBD,开到高质量配置:子步多到伸长率不再随子步变化(比如 32~64 个子步),自碰撞打开。
随机化什么
决定网络能泛化到哪里。
存什么
每帧存所有节点的位置(显示帧率,比如 60 Hz,而不是子步),以及碰撞体和钉住点的位置、材料参数。
怎么切分
按动作切分训练集和测试集,而不是按帧随机切分。
要点
老师要先过验证、开确定性;随机化决定泛化范围;帧率和运行时一致;按动作切分数据。
B.2算例
模型:输入、输出和归一化
| 位置 | 特征 | 说明 |
|---|---|---|
| 节点 | 最近 3~5 帧的速度(位移 / h) | 相对量;不放绝对位置 |
| 节点 | 类型独热编码:自由 / 钉住 / 碰撞体 | 钉住点的输出会被覆盖,但它的状态要作为输入 |
| 节点 | 逆质量、材料参数(柔度取对数) | 让美术的参数能直接控制网络 |
| 网格边 | 当前相对位移、当前长度、静止相对位移、静止长度 | 静止量告诉网络“应该是什么形状” |
| 世界边 | 相对位移、距离;到碰撞体的有符号距离和法线 | 每帧按半径重建,半径略大于碰撞厚度 |
| 输出 | 每个自由节点的加速度 â | 提议位置 x̂ = x + h·(v + h·â) |
坐标系。在角色的局部坐标系里构建特征,把角色整体的平移和旋转作为额外的节点特征(比如根骨骼的速度和角速度)。这和 4.3 节一样,既避免了大坐标的精度问题,也让网络不必为每个朝向单独学一遍。
归一化。每个输入、输出特征用训练集的均值和标准差归一化,统计量随模型一起保存。上线时用错统计量(比如用了另一个版本的),网络会给出看起来“合理但错误”的结果,很难察觉。
规模。MeshGraphNets 的典型配置是 128 维隐向量、15 轮消息传递。实时应用需要大幅缩小,具体多小要用 B.6 节的“同等误差下的耗时”去量,而不是先拍一个数。
相对量、局部坐标系、材料参数作为输入、输出加速度;归一化统计量和模型一起版本管理。
本节为实现建议,特征设计依据 GNS、MeshGraphNets(8.1 节)
坐标系
在角色的局部坐标系里构建特征,把角色整体的平移和旋转作为额外的节点特征(比如根骨骼的速度和角速度)。
归一化
每个输入、输出特征用训练集的均值和标准差归一化,统计量随模型一起保存。
规模
MeshGraphNets 的典型配置是 128 维隐向量、15 轮消息传递。
要点
相对量、局部坐标系、材料参数作为输入、输出加速度;归一化统计量和模型一起版本管理。
B.3流程
运行时的一帧
运行时把网络放进 XPBD 主循环的“预测”位置。注意子步:网络按显示帧率训练,所以每帧调用一次;XPBD 的投影可以在帧内用几个子步做,也可以只在帧末做一次。下面是最简单的“每帧一次网络、帧末投影”版本:
void Frame(float dt) {
UpdateKinematics(t); // 钉住点、碰撞体的新位置
Features f = BuildFeatures(state, colliders); // B.2 节
float3 a[] = net.Run(f); // 每个节点的加速度提议
for (i : particles) {
xPrev[i] = x[i];
if (w[i] == 0) { x[i] = PinTarget(i); continue; } // 钉住点:动画说了算
x[i] = x[i] + dt * (v[i] + dt * a[i]); // 提议位置
}
// 只投影硬约束:不可伸长、接触、钉住;软的手感交给网络
ResetLambdas();
NarrowPhase();
for (int k = 0; k < K; k++) {
for (c : stretch) SolveDistance(c, dt); // α 很小
for (t : tethers) SolveTether(t);
for (c : contacts) SolveContact(c, dt);
}
for (i : particles) if (w[i] > 0) v[i] = (x[i] - xPrev[i]) / dt;
}几个决定要明确地做出,而且训练时和运行时必须一致:
投影哪些约束。只投影“硬”的:不可伸长、接触、钉住。如果把弯曲等软约束也投影,XPBD 会和网络学到的手感打架。
投影几次(K)。训练时用的 K 和运行时的 K 要一样。训练时 K = 10、运行时 K = 2,网络的提议就是为一个更强的修正器优化的,上线后约束会明显变差。
速度怎么算。默认用投影后的位置,物理上一致。如果投影修正量较大导致抖动(9.3 节),先在训练里加修正量惩罚,而不是在运行时偷偷把修正量从速度里扣掉:后者会让布料在约束方向上“没有反应”,比如拉不住重物。
网络在“预测”的位置,XPBD 只投影硬约束;投影次数和速度算法在训练与运行时必须一致。
本节为实现建议,结构依据 9.3 节
投影哪些约束
只投影“硬”的:不可伸长、接触、钉住。
投影几次(K)
训练时用的 K 和运行时的 K 要一样。
速度怎么算
默认用投影后的位置,物理上一致。
要点
网络在“预测”的位置,XPBD 只投影硬约束;投影次数和速度算法在训练与运行时必须一致。
B.4推导
训练:把投影放进循环
最容易犯的错误,是先把网络当成纯学习模拟器训练好,上线时再接上 XPBD 投影。这样网络从来没见过投影,它的误差分布和投影的修正方式毫无配合,9.3 节的三种误差都会出现。
正确的做法是训练时就把投影放进前向计算:网络输出提议 → K 次 XPBD 投影(用自动微分框架写,展开成可求导的计算图)→ 和老师的下一帧比较。梯度穿过投影回到网络,网络学到的是“投影之后的结果要准”。
损失 = 投影后与老师的位置误差 + 投影修正量的大小 + (可选)多步滚动误差。
L = Σi |xiproj − xiteacher|² + μc Σi |xiproj − x̂i|² + μr Lrollout
- μc
- 修正量惩罚的权重:鼓励网络的提议本身就接近约束,减少 9.3 节的抖动
- Lrollout
- 把网络和投影连续展开 k 帧后的误差
课程。先只训练一步(k = 1),稳定后逐步把滚动长度加到 4、8、16 帧。长滚动的梯度容易爆炸,配合梯度裁剪;同时保留 GNS 式的输入噪声,两者是互补的。
不可导的地方。接触约束有“只在穿透时投影”的分支,梯度在分支边界上不连续。实践中通常直接让梯度穿过当前被激活的那一支(被激活的接触按等式约束求导,未激活的梯度为零),对训练影响不大。这一点属于经验做法。
老师在线还是离线。离线数据集简单,但网络只见过老师的状态;在线(DAgger 式)做法是用当前网络滚动出状态,再让老师从这些状态出发算下一步作为目标,网络就能学到“从自己的错误状态出发应该怎么走”。代价是训练时要同时跑老师。
训练时就把可微的投影放进循环,加修正量惩罚和逐步加长的滚动;接上投影再训练,比训练完再接投影好得多。
本节为实现建议;噪声注入依据 GNS(2020);可微投影的思路与 DiffXPBD(2023)、Neural Projections(2020)一致
L = Σi |xiproj − xiteacher|² + μc Σi |xiproj − x̂i|² + μr Lrollout
损失 = 投影后与老师的位置误差 + 投影修正量的大小 + (可选)多步滚动误差。
要点
训练时就把可微的投影放进循环,加修正量惩罚和逐步加长的滚动;接上投影再训练,比训练完再接投影好得多。
B.5清单
坑表:组合系统最常见的十个问题
网络提议的误差被投影修掉,修正量变成了速度(9.3 节)。训练时加修正量惩罚,并把投影放进训练循环。
误差在约束允许的方向上(系统偏差),投影修不到。加长滚动训练,检查训练数据是否覆盖这类动作。
运行时投影次数 K 比训练时少;或者运行时投影了训练时没投影的约束。
网络按固定的 h 学习动力学。要支持多种帧率,要么把 h 作为输入并在训练里随机化,要么固定模拟帧率、显示时插值。
归一化统计量与模型版本不一致,或输入里混入了绝对坐标。
网络对钉住点的输出没被覆盖,或者钉住点的历史速度特征是错的(动画瞬移时速度特征爆掉)。
网络一步的提议穿得比碰撞厚度还深,投影按最近点推到了另一侧。加厚度、限制提议的最大位移,或在帧内用子步做投影。
训练数据只有一种分辨率或一件衣服。网格随机化,或者对每类衣服单独训练并做好回退。
网络太大,或者每帧的特征构建和数据搬运太贵。先量“同等误差下的耗时”,再决定网络规模(B.6 节)。
老师没有通过附录 A 的验证就开始生成数据。老师的每次修改都要重跑验证,并给数据集打上老师的版本号。
组合系统的坑一半来自“训练和运行时不一致”,另一半来自“误差落在约束管不到的方向上”。
本节根据前文原理整理;属于工程经验,不同项目的表现可能不同
约束守住了,布料却在抖
越跑越偏,但从不拉伸
训练很好,上线约束变差
换了帧率完全不对
输出“合理但错误”
钉住点附近撕裂
要点
组合系统的坑一半来自“训练和运行时不一致”,另一半来自“误差落在约束管不到的方向上”。
B.6清单
验证:组合系统的验收清单
| 测试 | 怎么做 | 看什么 |
|---|---|---|
| 单步误差 | 测试集上从老师的状态出发走一步 | 投影前、投影后的位置误差;修正量大小 |
| 滚动误差 | 从老师的状态出发连续走 1 / 10 / 100 / 1000 帧 | 误差曲线的增长速度;是否在某个长度后爆掉 |
| 约束统计 | 长滚动中每帧记录 | 最大拉伸、穿透顶点数、钉住点误差(应为 0) |
| 能量漂移 | 无外力、无阻尼的场景 | 总能量随时间的变化,与纯 XPBD 对比 |
| 泛化 | 没见过的动作、材料、分辨率 | 与训练分布内的误差相比退化多少 |
| 消融 | 无投影 / 只在运行时投影 / 训练时也投影 | 确认投影和训练方式各自贡献了多少 |
| 最终指标 | 同一组场景,调纯 XPBD 的子步数直到误差与组合系统相同 | 两者的耗时;组合系统必须更快,否则不值得 |
最后一行是整个组合系统存在的理由。很多学习模拟器在论文里比的是“和纯网络基线相比更准”,但游戏关心的是“和多加几个子步的 XPBD 相比,是否更便宜”。这个对比要在目标硬件上、用目标网格规模做。如果答案是否定的,把精力放回第 4、7 章的子步和并行,往往收益更高。
还要留一条退路:运行时检测到异常(约束统计突然变差、能量暴涨),自动切回纯 XPBD 的保守配置。网络是概率性的,总会有它没见过的输入。
最终只看一个指标:同等误差下比纯 XPBD 更快;并且要有检测异常、切回纯 XPBD 的退路。
本节为实现建议
要点
最终只看一个指标:同等误差下比纯 XPBD 更快;并且要有检测异常、切回纯 XPBD 的退路。
B.7清单
参考资料与证据等级
这门课里的内容,证据强度并不一样,转述时请分清三档。
一档:原论文和官方文档的直接结论。XPBD 的更新公式、Small Steps 的主要结论、长程附着、DiffXPBD 的能力范围、HOOD 的损失构成、UE 的参数名称和官方说明。
二档:标准结论和常见工程做法。数值积分的稳定性条件、四面体体积梯度、图着色并行、分层碰撞检测、结构数组布局。它们不属于某一篇论文,但在教材和工程实践中广泛使用。
三档:本文的推断和建议。标了“推断”或“建议”的内容:热启动的分析(9.5 节)、UE 内部参数映射的猜测、两个附录的里程碑划分、训练流程和坑表、交互图里的数值。交互图是为了说明原理的简化实验,不是对任何引擎的复现。
XPBD 与相关
- Müller, Heidelberger, Hennix, Ratcliff. Position Based Dynamics. VRIPhys 2006 / JVCIR 2007
- Macklin, Müller, Chentanez. XPBD: Position-Based Simulation of Compliant Constrained Dynamics. MIG 2016
- Macklin, Storey, Lu, Terdiman, Chentanez, Jeschke, Müller. Small Steps in Physics Simulation. SCA 2019
- Müller, Macklin, Chentanez, Jeschke, Kim. Detailed Rigid Body Simulation with Extended Position Based Dynamics. SCA 2020
- Macklin, Müller. A Constraint-based Formulation of Stable Neo-Hookean Materials. MIG 2021
- Kim, Chentanez, Müller. Long Range Attachments: A Method to Simulate Inextensible Clothing in Computer Games. SCA 2012
- Macklin, Müller, Chentanez, Kim. Unified Particle Physics for Real-Time Applications. SIGGRAPH 2014
- Müller, Heidelberger, Teschner, Gross. Meshless Deformations Based on Shape Matching. SIGGRAPH 2005
- Bergou, Wardetzky, Harmon, Zorin, Grinspun. A Quadratic Bending Model for Inextensible Surfaces. SGP 2006
- Baraff, Witkin. Large Steps in Cloth Simulation. SIGGRAPH 1998
- Fratarcangeli, Tibaldo, Pellacini. Vivace: a Practical Gauss-Seidel Method for Stable Soft Body Dynamics. SIGGRAPH Asia 2016
- Chen, Liu, Yang, Yuksel. Vertex Block Descent. SIGGRAPH 2024
学习模拟与组合
- Battaglia et al. Relational Inductive Biases, Deep Learning, and Graph Networks. arXiv 2018
- Sanchez-Gonzalez et al. Learning to Simulate Complex Physics with Graph Networks. ICML 2020
- Pfaff, Fortunato, Sanchez-Gonzalez, Battaglia. Learning Mesh-Based Simulation with Graph Networks. ICLR 2021
- Yang, He, Zhu. Learning Physical Constraints with Neural Projections. NeurIPS 2020
- Rubanova et al. Constraint-based Graph Network Simulator. ICML 2022
- Grigorev, Thomaszewski, Black, Hilliges. HOOD: Hierarchical Graphs for Generalized Modelling of Clothing Dynamics. CVPR 2023
- Stuyck, Chen. DiffXPBD: Differentiable Position-Based Simulation of Compliant Constraint Dynamics. SCA 2023 (arXiv 2301.01396)
Epic 官方文档(dev.epicgames.com,按版本查阅)
- UChaosClothConfig、UChaosClothSharedSimConfig(API)
- EChaosClothAssetConstraintSolverType、FChaosClothAssetSimulationStretchConfigNode(UE 5.8 API)
- ML Deformer Framework in Unreal Engine
公式和论文结论可以直接引用;工程做法当作常识;标了“推断”“建议”的,用之前自己验证。
每节末尾都标了出处;一节里混用多档时,以最低的那一档为准
一档:原论文和官方文档的直接结论
XPBD 的更新公式、Small Steps 的主要结论、长程附着、DiffXPBD 的能力范围、HOOD 的损失构成、UE 的参数名称和官方说明。
二档:标准结论和常见工程做法
数值积分的稳定性条件、四面体体积梯度、图着色并行、分层碰撞检测、结构数组布局。
三档:本文的推断和建议
标了“推断”或“建议”的内容:热启动的分析(9.5 节)、UE 内部参数映射的猜测、两个附录的里程碑划分、训练流程和坑表、交互图里的数值。
XPBD 与相关
要点
公式和论文结论可以直接引用;工程做法当作常识;标了“推断”“建议”的,用之前自己验证。