刚体约束求解器核心是速度级Gauss-Seidel迭代投影,而非直接解方程;流程为预测位置→检测约束→构建J与C_dot→计算等效质量→解lambda→更新速度,需引入bias防漂移,优先用距离约束验证,注意J构造、权重顺序、dt与参数协同。

刚体约束求解器的核心是迭代投影,不是直接解方程
手写刚体约束求解器时,别一上来就推导雅可比矩阵或写 LDLT 分解——游戏物理要求稳定、快、容忍误差,主流做法是用 **速度级约束的 Gauss-Seidel 迭代投影**。它不求精确解,而是每帧对每个约束做一次“拉回”:算出当前速度违反约束的程度(即 lambda 冲量),再反向施加到关联刚体上。
典型流程是:预测位置 → 检测约束(如距离、铰链、接触)→ 构建约束误差和雅可比 J → 计算等效质量 J * M⁻¹ * Jᵀ → 解标量冲量 lambda = -C_dot / (J * M⁻¹ * Jᵀ + bias) → 更新刚体速度。
常见错误是把位置约束(C(x)=0)当成速度约束来处理,结果漂移严重;必须引入 bias 项(如 Baumgarte 稳定化里的 beta * C / dt)来修正位置误差累积。
距离约束(Point-to-Point)是最稳的起点,别先碰角动量耦合
从两个刚体间的单点距离约束入手,能避开旋转惯量张量、坐标系变换等干扰项,快速验证求解逻辑。它的约束函数是 C = |p₂ − p₁| − d₀,但实际只用其速度形式:C_dot = J * v,其中 J 是 1×12 行向量(含线速度与角速度贡献)。
实操建议:
- 先禁用角速度(设刚体
inertia = zero),只处理质心平移,验证lambda符号和大小是否合理 -
J的角速度部分为± cross(r, axis),注意r是局部锚点到质心的向量,且必须在世界坐标下计算 - 每次迭代后要立即更新刚体速度:
v1 += inv_mass1 * J1ᵀ * lambda,否则后续约束看到的是过期状态 - 迭代 3–5 次足够,再多收益极小,还可能因浮点误差发散
多个约束共存时顺序和权重决定稳定性
同一刚体参与多个约束(比如一个刚体连着两个弹簧、又被地面支撑),Gauss-Seidel 的求解效果强烈依赖迭代顺序和约束权重。没加权时,后处理的约束会“覆盖”前面的修正,导致高频抖动或约束失效。
关键控制点:
- 把高优先级约束(如关节、不可穿透接触)放在迭代循环前面
- 对软约束(如布料弹簧)降低其等效刚度(减小
beta或乘以0.1~0.5缩放因子),避免压制硬约束 - 接触约束必须带恢复系数(
restitution),在lambda计算中加入分离速度判断,否则物体会粘在地上 - 所有约束的
J * M⁻¹ * Jᵀ分母项不能为零或负——检查质量是否为零、惯量是否奇异、r是否为零向量
为什么你的约束总在抖?大概率是时间步和偏置项没对齐
抖动(jittering)不是算法问题,而是数值配置失配:dt、beta、gamma(阻尼项)、迭代次数四者必须协同。例如 Baumgarte 中的 bias = beta * C / dt,若你用了变长帧时间但 beta 没缩放,位置漂移就会周期性爆发。
调试时盯住这几个信号:
- 打印每帧最大约束误差
abs(C),超过 1e−3 就说明稳定化不足 - 观察
lambda符号是否频繁翻转——这是过度校正,应降低beta(试试0.01 ~ 0.2) - 固定时间步(如
dt = 1.0/60)比可变步更易调参;若必须变步,所有基于dt的项(bias、gamma)都要线性缩放 - 角速度大的刚体容易因
cross(r, omega)计算误差放大抖动,可在J中加入小量阻尼项gamma * lambda抑制
真正难的不是写出第一个约束,而是让十种不同约束(球窝、滑动、轮式、碰撞)在同一个迭代器里不打架——它们共享速度变量,但误差来源、响应强度、更新时机全不一样。留神那些没显式出现在代码里、却悄悄破坏一致性的隐含假设。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











