别让箱子往下陷
一句话总结
只修正速度,无法消除已经发生的穿透。还要单独移动位置,但推得太猛会抖动,所以应保留容差(slop),逐步推出;存在多个接触时,则要在同一步内迭代多次。
为什么需要它
前一模块的冲量修正的是速度。把接触地面的盒子向下速度改为 0,它就不会继续下降。但此时盒子已经略微穿入地面,因为系统发现相交时,重叠已经发生。
速度变为 0 后,盒子会停在穿透位置。下一步,重力再次赋予速度,位置又下降一些,然后速度再次被清零,如此逐渐下沉。堆叠多个盒子后,下层会被压住并在互相穿透的状态下固化。
如何工作
位置校正是按逆质量比例分配重叠量,把两侧推开。
보정량 = max(깊이 - slop, 0) / (inv_m1 + inv_m2) * percent
물체1 을 -n 방향으로 보정량 * inv_m1 만큼
물체2 을 +n 방향으로 보정량 * inv_m2 만큼
这里有两个调节参数。percent 表示一步内恢复多少比例。设为 1.0 会一次完全推出,但物体可能弹开,下一步再次碰撞,形成振荡。通常取 0.2 到 0.8。
slop 是忽略的重叠容差,通常设为 0.005 到 0.01。没有它,为把重叠精确降为 0 的校正会每一步以极小数值持续发生,让物体轻微抖动。不追求重叠恰好为 0,而是允许极薄的重叠,反而更稳定。
还要注意,位置校正不改变速度。它只移动位置,不会创造能量。若同时修改速度,校正就会成为推动物体的力,使堆叠自行跳起。
迭代次数用于多个接触同时存在时。三个盒子堆叠会产生三个接触(地面-1、1-2、2-3),这些接触互相耦合。推动最上方盒子会压迫中间盒子,修正中间又会压迫下方。逐个求解时,后一个结果会破坏前一个。
精确方法是把所有接触作为联立方程求解,但成本昂贵。因此,引擎会多次重复相同遍历。迭代越多越接近正确答案,停止点就是精度与成本的折中。这称为顺序冲量(sequential impulse),由 Erin Catto 在 Box2D 中系统整理后,几乎成为标准。盒子堆看起来有多坚固,基本直接取决于迭代次数。
除了位置校正还有其他方法。Baumgarte 稳定化把部分穿透量混入速度目标。把“相对速度变为 0”改为“相对速度变为 beta * 깊이 / dt”,就能在速度求解中一并推出重叠。它实现简单、应用广泛,但会把能量注入系统,也可能导致堆叠轻微跳动。
因此,现代引擎常使用分离冲量(split impulse)。修正速度的冲量与修正位置的冲量分开,位置冲量不写入真实速度。这样既能推出重叠,又不增加能量,保留两种方法的优点。本实验的位置校正就是这一思想最简化的形式。
在实际项目中
堆叠随时间逐渐变矮,通常表示迭代次数不足。下层接触尚未完全求解,当前步已经结束,于是每一步都会略微压缩。增加迭代能明显改善,但成本也同比增加。
另一项技术是休眠(sleeping)。堆叠稳定后,计算仍持续进行,结果却几乎不变。引擎会在速度低于阈值并持续一定时间后,把物体移出计算;发生接触时再唤醒。包含一千个堆叠盒子的场景之所以能运行,依靠的就是它。
还应明确如何衡量堆叠稳定性。“看起来不晃”不能成为判定标准。本实验采用两种方法:一是最大接触穿透深度是否保持在 slop 附近,二是连续两帧图像差异是否收敛到 0。后者尤其有用,因为它测量的正是人眼看到的现象——数值略有变化,但若落在相同像素,对人而言就是静止。
请记住这一层的规则:稳定优先于绝对精确。 试图把重叠精确清零会产生抖动,试图一步完全恢复会产生弹跳。安静站立且有轻微重叠的堆叠,优于精确接触却持续微振的堆叠。
下一实验要做什么
实现位置校正函数,通过数值比较:未校正的盒子如何固化在穿透状态,开启校正后如何恢复到 slop。然后对三个盒子把迭代次数依次设为 1、4、10,观察穿透深度下降;最后用十二张图记录五个盒子落下、堆叠并稳定的过程。