本节摘要:多个接触同时成立时,一条冲量会破坏另一条刚修好的相对速度。顺序冲量(投影高斯-塞德尔)轮流访问每条约束,算本条的增量冲量,立刻写到速度上,并把累计 lambda 钳在合法集合里。它不是数学最优,但实时、可热启动、可并行按岛拆。摩擦用锥投影,迭代次数和子步一起调。
阅读完本节,你应当能够:
两个箱子叠着,底下还有地。先解上对下,再解下对地,上对下又坏了。必须迭代。顺序冲量每次只看一条约束的当前速度误差,算 delta-lambda,加到累计 lambda,再把 delta 应用到刚体速度。下一条看到的是已经更新过的速度。这是高斯-塞德尔:用最新的邻居未知量。雅可比迭代用全旧值,刚体引擎里少见,因为 GS 通常收敛更快。
for iter in 1..N: for each contact: vn = relativeNormalVel(...) dLambda = (vn_target - vn) / K lambda0 = contact.lambdaN contact.lambdaN = max(lambda0 + dLambda, 0) dLambda = contact.lambdaN - lambda0 applyImpulse(dLambda * n)
关键是累计非负,不是单次 dLambda 非负。否则上一次推开的量会被下一次“吸回去”。热启动:循环前 lambdaN 用上一帧缓存,先 applyImpulse(lambdaN) 一次(或等价地从缓存速度开始),再迭代修正。静止堆叠几乎一开始就满足。
| 求解器 | 实时性 | 精度 | 并行 | 备注 |
|---|---|---|---|---|
| 顺序冲量 PGS | 好 | 中,迭代不够会软 | 按岛 | 工业默认 |
| 拟牛顿 / 直接 LCP | 差一些 | 高 | 难 | 离线、小系统 |
| SI / MLCP 内点 | 更差 | 高 | 难 | 研究与机器人 |
| 位置 PBD | 好 | 软 | 好 | 布料,刚体堆叠偏软 |
游戏刚体选 PGS。机器人高精度轨迹选更硬的求解器或更多迭代加更小 dt。不要在游戏里上内点法除非你知道预算。
三维切向二维。可以准备两个正交切向 t1、t2,对每个做类似法向的约束,但累计向量长度 sqrt(lt1^2+lt2^2) <= mu * lambdaN。超过则缩放切向累计。二维一个切向,钳制在 [-mu*jn, mu*jn]。
静摩擦表现为:锥内能把切向相对速度刹到 0。锥外滑动,方向与 vt 相反。热启动的切向 lambda 让静摩擦锚住。mu 动静可以不同,静摩擦略大,但实现要带滞回,否则在阈值附近振荡。很多游戏用一个 mu 省事。
约束顺序:通常先接触法向,再摩擦,再关节。有人把关节放前面,让机构先硬。门被箱子挤时,关节先还是接触先,手感不同。做成可配置,默认接触优先往往堆叠更稳。
💡 关键直觉:PGS 是在“轮流把台球摆正”。次数不够,堆会像海绵;次数太多,收益递减还费时。先加子步刷新接触,再加迭代。
迭代次数:简单场景 6–10;堆金字塔 15–25;睡眠后可以少算。残差:最大法向速度误差低于阈值可早停。早停对休眠友好。不要每帧固定 50 次“以求安心”,手机上会卡,PC 上你会失去找真瓶颈的机会。
允许轻微穿透的软约束:K 改成 K + softness/dt 或给 lambda 上限。软接触能避免爆炸,堆叠会略沉入地面。过软像沼泽。刚性由迭代、dt、beta、softness 共同决定,调参一次只动一个。

抖动典型原因:流形点跳、法向跳、beta 太大、e 在静止时没关、迭代太少、dt 变。按这个清单查,不要先改 mu 到 10。mu 过大加迭代不足,切向与法向互殴,更抖。
⚠️ 常见坑:每轮都把 lambda 清零再算。等于扔掉热启动,静止堆永远像新碰撞。累计必须跨迭代、跨帧(经流形缓存)活着。
分岛:互相接触的刚体进同一岛,岛之间无共享约束,求解完全独立。第 6 章并行按岛调度。现在单线程也可以分岛:小岛少迭代,睡着的岛跳过求解。这是顺序冲量的结构红利,直接 LCP 一个大矩阵反而不好拆。
位置修正阶段:速度求解后写位置,若仍穿透,可再跑几次只含 bias 的迭代,或轻量位置投影。Box2D 风格把位置求解和速度求解分开,避免 bias 污染真实速度导致额外弹跳。值得做:速度步 e 生效;位置步只消穿透、e=0。两套 lambda 不要共用缓存,位置步 lambda 用完即扔。
测试:金字塔 5 层应在 2 秒内休眠;斜坡静摩擦不蠕动;长铰链链条两端拉直不爆炸。失败就减 beta、加子步、查流形,而不是把迭代加到 100 假装解决。
互补条件用文字记:接触力不能拉(lambda≥0),不能有未解决的接近速度同时为零力还在穿透——实际用松弛:允许微小穿透。LCP 理论保证存在解的条件在库仑摩擦下并不总成立,PGS 可能在摩擦问题上循环。锥投影是启发式,但二十年游戏证明它够用。若你看到切向冲量在两个方向间振荡,减小 mu 或把摩擦迭代放在法向之后并减少切向 overshoot。
重力在速度步开始前已经积分进去,第一轮接触会立刻出现很大的接近速度,热启动的支撑冲量应把它们抵消。若没有热启动,金字塔底层第一帧会被压得深深穿透,bias 再猛拉,抖动源。所以 3.3 的缓存不是美容,是求解器的初值。
旋钮列表:迭代次数、子步、beta、e 阈值、mu、softness、位置步开关。抖动时按 7.1 现象表先定性,再拧对应旋钮。禁止一次改五个然后说“好像好了”。记录:场景名、旋钮、前后醒岛时间、是否蠕动。这是物理程序员的实验笔记,不是玄学。
位置步与速度步分离的最小实现:速度步用 e 和摩擦;写位置后,对仍穿透的接触再跑 2–3 次只含 bias、e=0、无摩擦的迭代,lambda 不写回流形缓存。位置步过猛会把物体抬飞,上限钳制 bias。Box2D 用户会觉得眼熟,因为这条路被走通了。
摩擦振荡:切向 lambda 在正负之间跳,物体微颤。减小切向有效质量的 overshoot,或把切向迭代放在法向之后并减少切向次数。mu 从 0 加起,斜坡实验确定最小值,不要从 2 往下调。
小岛优先:接触少的岛少迭代,接触多的岛多迭代。固定全局 20 次对单箱子浪费,对楼房倒塌不够。按岛接触数分段。这和并行无关,单线程也省。
单次非负会把上一轮推开的量吸回去。接触力不能拉,指的是累计 lambda。这是互补条件在 PGS 里的样子。
可以先把缓存 lambda 施加到速度,再迭代修正;或把速度留在上一帧末状态上迭代。两种都行,不要每轮清零。清零等于扔热启动。
不一定。PGS 锥投影是启发式。游戏二十年够用。切向振荡时减 mu 或减切向 overshoot,不要改成内点法除非你是机器人精度项目。
单箱子看不出来。倒塌楼房不够再按接触数加。分段比全局 50 次聪明。
位置步只消穿透,缓存进速度步会污染弹跳。用完即扔。两套 lambda 别混。
扔热启动,堆像橡皮。
无法复现。笔记一次一个。
假弹跳。位置步用完即扔。
振荡。从 0 加起斜坡确定。
手机卡,还失去找瓶颈机会。按岛分段。
游戏不必。减 mu 或 overshoot。
接触过时。先子步 2。
浪费或不够。按接触数分段。
固定时间步是「02-顺序冲量约束求解」所有数值实验的尺子。尺子在抖,你无法比较积分器,无法调 beta,无法谈确定性。谁把渲染帧时间直接喂进物理,谁就在用弹性的尺子量钢梁。 本条对应「02-顺序冲量约束求解」第 1 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-顺序冲量约束求解」里出现 NaN 要在源头断言:未归一化法向、零长度边当轴、除以过小的有效质量、四元数没归一。把 NaN 传到求解器再查,整场速度都会脏,回溯成本高一个数量级。 本条对应「02-顺序冲量约束求解」第 2 号备忘,和相邻条目不要合并成一句空话,分开验收。
和美术、策划交接「02-顺序冲量约束求解」参数时,用测试盒而不是形容词。说清楚二十厘米台阶能上、八十厘米不能,比说手感更跟手有用。测试盒还能在引擎升级后当合同跑一遍。 本条对应「02-顺序冲量约束求解」第 3 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-顺序冲量约束求解」若跨模块耦合,先单向再双向。布料先撞静地,流体先撞静箱,关节先吊静锚。双向一步到位时,你分不清是 A 推 B 写错还是 B 还力写错。控制变量不是学术,是省生命。 本条对应「02-顺序冲量约束求解」第 4 号备忘,和相邻条目不要合并成一句空话,分开验收。
睡眠和唤醒属于「02-顺序冲量约束求解」正确性,不只是性能。该醒不醒会穿模,不该醒全醒会卡顿。爆炸查询、关节边、运动学平台都是唤醒图的边。漏一条边,现场像鬼。 本条对应「02-顺序冲量约束求解」第 5 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-顺序冲量约束求解」的默认值要进协议版本。改默认迭代次数、默认 dt、默认皮肤半径,旧回放会分叉。版本头拒绝错版本,比静默错乱重放更负责任。 本条对应「02-顺序冲量约束求解」第 6 号备忘,和相邻条目不要合并成一句空话,分开验收。
先写 2D 再写 3D 适用于「02-顺序冲量约束求解」里所有几何麻烦事。二维金字塔、二维绳、二维铰链能看见点的增减。三维裁剪 bug 极难看,会让你怀疑人生。二维过了再加一根轴。 本条对应「02-顺序冲量约束求解」第 7 号备忘,和相邻条目不要合并成一句空话,分开验收。
下一节把铰链和电机写成同一套速度约束,和接触挤在一次迭代里。