LabHub
学习 学习路径 课程

物理引擎的骨架

一发冲量就解决

在 LabHub 中继续学习

一句话总结

碰撞响应不是用力计算,而是用冲量计算。直接求出一个能让两物体互相远离的瞬时速度变化量,并立即应用。

概念图: 冲量 · 直接计算结果 · 恢复系数 · 逆质量

为什么需要它

若用力处理碰撞,就必须对接触期间的力随时间积分。但刚性物体接触时间不足 1 毫秒,在以 1/60 秒为间隔的模拟中,整个接触过程都落在同一步内。无论把力设得多大,同一步里物体都已经互相穿透。

因此,物理引擎不会模拟接触过程,而是直接计算结果。建立碰撞前后速度关系的方程,一次求出满足它的速度变化量。这个变化量就是冲量,单位为力乘时间,与动量相同。

如何工作

关键是两个物体的相对速度在碰撞法线方向上的分量。

v_rel = dot(v2 - v1, n)        # n 은 1에서 2를 향하는 단위 벡터

若该值为正,两物体已经在远离,无需处理。漏掉这个检查,就会把正在分开的物体重新拉回,导致物体粘在表面。

若为负,则计算冲量。恢复系数 e 决定碰撞后相对速度以碰撞前的多少倍反向恢复。e=1 表示完全弹性碰撞(能量守恒),e=0 表示完全非弹性碰撞(粘在一起运动)。

j = -(1 + e) * v_rel / (inv_m1 + inv_m2)

v1 -= j * inv_m1 * n
v2 += j * inv_m2 * n

惯例上使用的不是质量,而是逆质量。把除法变成乘法略快,但真正原因是:静止物体可以自然表示为逆质量 0。 若在代码中使用无穷大质量,每次除法都需要特殊处理;逆质量 0 只是一个普通数字。

这个计算始终守恒动量,因为两个物体获得大小相同、方向相反的冲量。动能则只在 e=1 时守恒,较小时会损失。现实中,损失的能量转化为声音、热与形变。

摩擦是在切线方向再执行一次类似计算,但限制最大值。

t = normalize(v - dot(v, n) * n)      # 법선 성분을 뺀 나머지 = 접선 방향
jt = -dot(v, t) / inv_m               # 접선 속도를 0 으로 만드는 값
jt = clamp(jt, -mu * jn, +mu * jn)    # 쿨롱 마찰의 원뿔

先求完全消除切向速度所需的值;若超过 mu * jn,就截断到上限。未超过时,物体停在原地(静摩擦);超过时,只施加截断后的值,物体继续滑动(动摩擦)。一个条件分支即可得到两种摩擦。

加入旋转后公式会变长。接触点远离质心时,冲量还会产生旋转。此时分母要加入两个物体转动惯量产生的项,应用冲量时不仅改变线速度,也要改变角速度。整体结构仍相同:求相对速度,解出能把它变为目标值的冲量,再应用。本实验忽略旋转,是为了先看清公式骨架。

恢复系数不是单个物体的固有性质。 它属于两个物体组合,还会随碰撞速度变化。实务中常把两物体的值相乘或取较小值进行粗略合并,但物理依据都不强。与其追求“精确”数值,不如选择能产生所需手感的值。

在实际项目中

低高度反复弹跳的物体经常无法停下,而是持续微小抖动。即使恢复系数很小,只要不为 0,弹跳速度就不会收敛到 0。实务解决办法是:相对速度低于某个阈值时,强制把恢复系数设为 0。

另一个问题出现在多个接触同时发生时。逐个求解接触,后求解的结果会破坏前一个。解决方法是在同一步内迭代多次,这是下一模块的主题。

最后再次强调符号与方向约定。若规定法线从物体 1 指向物体 2,整个代码都必须遵守。检测侧若反向返回,物体不会互相推开,反而互相吸引,症状表现为“物体粘住无法分离”,很难从表面猜到原因。因此,检测函数与响应函数之间的约定,最好用测试固定,而不是只写注释。

下一实验要做什么

亲手实现冲量公式,通过数值确认不同恢复系数下的动量与能量,再以图表验证球每次反弹高度缩小为 倍。随后用库仑摩擦求滑动盒子的停止距离,与解析解对照;最后让五个球在盒子内弹跳并绘制轨迹。