刚体碰撞冲量核心公式由动量守恒与恢复系数约束联立推导:设相对速度法向分量为 $ v_{\text{rel}} \cdot n $,则冲量大小 $ j = \frac{-(1+e)(v_{\text{rel}} \cdot n)}{1/m_A + 1/m_B + (n \times r_A) \cdot (I_A^{-1} (n \times r_A)) + (n \times r_B) \cdot (I_B^{-1} (n \times r_B))} $,分母为有效质量倒数,含平动与转动贡献。

刚体碰撞冲量计算的核心公式怎么推导
冲量响应本质是求解一个标量 j(冲量大小),使得碰撞后相对速度沿法向满足恢复系数约束。关键不是记住公式,而是理解它从哪来:从动量守恒 + 碰撞约束(v_rel_after · n = -e * v_rel_before · n)联立得到。
对两个刚体 A 和 B,设接触点处的相对速度为 v_rel = v_B + ω_B × r_B − (v_A + ω_A × r_A),法向为 n,则冲量 j 满足:
j = (−(1 + e) * (v_rel · n)) / (1/m_A + 1/m_B + (n × r_A)·(I_A⁻¹ × (n × r_A)) + (n × r_B)·(I_B⁻¹ × (n × r_B)))
分母就是“有效质量”(effective mass)的倒数,它综合了平动与转动对法向运动的阻碍。漏掉转动项(即忽略 I⁻¹ 部分)是初学者最常犯的错——哪怕物体在旋转,只要接触点偏移质心,转动惯量就直接影响冲量大小。
如何在代码里稳定计算 I⁻¹ × (n × r)
刚体的惯性张量 I 是世界坐标系下的 3×3 矩阵,但通常只在物体本地坐标系中存储对角形式(如 vec3 I_local)。直接用 I⁻¹ 会出错:必须先将 n × r 变换到本地坐标系,用对角逆惯性乘,再变回世界坐标系。
常见错误写法:I_world_inv * cross(n, r) —— I_world_inv 很难实时维护,且易因数值误差奇异。
正确做法(假设 R 是刚体旋转矩阵):
- 计算本地坐标系下的叉积:
vec3 local_torque_axis = R.transpose() * cross(n, r) - 逐分量缩放:
vec3 local_result = local_torque_axis / I_local(除法为分量除) - 变换回世界坐标:
vec3 world_result = R * local_result
注意:若使用四元数表示旋转,R.transpose() 等价于共轭四元数旋转;用 inverse(R) 在浮点下不安全。
为什么冲量方向固定为法向 n,但实际接触可能有摩擦
基础冲量响应只处理法向(non-penetration)约束,所以 j 是沿 n 的标量。摩擦是切向约束,需额外求解另一个冲量 j_t,且受库仑摩擦锥限制:|j_t| ≤ μ * |j|。
实操中别一上来就加摩擦:先确保法向冲量能稳定分离物体(无抖动、无穿模),否则摩擦只会放大数值误差。验证方法很简单——把 e 设为 0,μ 设为 0,观察碰撞后是否严格满足 v_rel · n ≥ 0。
另外,n 必须单位化。未归一化的法向会导致分母缩放错误,冲量被放大或缩小数倍——这是调试时最隐蔽的 bug 来源之一。
多次碰撞或连续接触时,为什么单次冲量不够
真实物理引擎(如 Box2D、Bullet)对同一帧内多个接触点,会迭代求解冲量(如 10–20 次 Jacobi 或 Gauss-Seidel),因为 A 与 B 碰,B 又与 C 碰,彼此耦合。只算一次 j 会导致能量凭空增加或物体“弹跳过高”。
简单系统可只做 1–3 次迭代,但要注意:每次迭代必须更新速度(v, ω),再重新计算当前时刻的 v_rel 和有效质量分母(尤其是 r_A, r_B 相对位置不变,但 n 若来自几何检测,可能随姿态微变)。
更关键的是顺序:先处理静止/高质量物体(如地面),再处理轻物体,收敛更快。把所有接触点塞进一个数组然后乱序处理,容易震荡发散。
实际部署时,冲量值本身不重要,重要的是它是否让相对速度在法向“刚好归零或反弹”。盯住 v_rel · n 这个标量,比盯着 j 的数值更有意义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











