Baumgarte
在物理模拟中,我们最终的目标是控制物体的位置,确保它们遵守物理规则(如不穿透、关节连接等)。然而,直接在位置层面 (Position Level) 上满足这些约束,即求解 C(x) = 0,是一个非常困难的非线性问题。现代物理引擎普遍采用一种更高效、更稳定的策略:在速度层面 (Velocity Level) 上工作。我们不去直接修正位置,而是计算并施加一个约束冲量,来调整物体的速度,使得约束在下一瞬间能够被满足。这种方法被称为速度级约束,它是所有基于冲量的迭代求解器(如PGS)的核心。
1. 从位置约束到速度约束
假设我们有一个位置约束方程 C(x) = 0。
如果我们对它求时间导数,我们会得到速度级约束:
这个方程的物理意义是:物体的速度 v 必须位于雅可比矩阵 J 的零空间中,即物体的运动不能导致约束 C 的值发生变化。
求解 Jv = 0 比求解 C(x) = 0 要容易得多,因为前者是一个线性方程组。
2. 位置漂移问题 (Positional Drift)
然而,只满足速度级约束会带来一个新问题。由于我们使用的是数值积分(如半隐式欧拉),即使我们确保每一步的速度都满足 Jv=0,在积分 x_{n+1} = x_n + v_{n+1} * Δt 之后,新的位置 x_{n+1} 也不会精确地满足 C(x_{n+1}) = 0。会存在一个微小的误差。
这个误差会在每一帧累积,导致物体的位置逐渐“漂移”出约束。例如,一个铰链关节可能会慢慢地被拉开,一个堆叠的箱子可能会慢慢地沉入地面。这种现象被称为位置漂移或约束漂移。
3. Baumgarte 稳定化 (Baumgarte Stabilization)
为了解决位置漂移问题,我们需要一种机制,在求解速度约束的同时,也把位置误差考虑进去。最流行的方法是Baumgarte稳定化。
其核心思想是:我们不再要求 Ċ = 0,而是要求 Ċ 等于一个与位置误差 C(x) 成正比的“修正速度”。
C(x): 当前的位置误差(例如,穿透的深度)。Δt: 时间步长。β(Beta): 一个可调的常数,通常在0.1到0.2之间。它决定了修正位置误差的“积极性”。
这个方程可以被看作是一个简单的控制系统:它告诉我们,为了在下一帧消除位置误差 C(x),我们需要一个等于 - (β/Δt) * C(x) 的相对速度。
现在,我们的速度级约束目标从 Jv = 0 变成了:
这个 -(β/Δt) * C(x) 就是我们在《约束的构建》一文中提到的偏置项 (bias term) b。它被直接代入到求解拉格朗日乘子 λ 的方程中:
通过这种方式,求解器计算出的冲量 λ 不仅会阻止物体在下一帧继续违反速度约束,还会额外施加一个“推力”,将物体推回到正确的位置上,从而主动地修正位置漂移。
优点:
- 实现简单,只需在计算约束方程右侧的
b时加上一项。 - 将位置修正无缝地集成到了速度级求解器中。
缺点:
β的选择是一个经验性的调整过程。如果β太小,位置修正会很慢,物体看起来会很“软”。如果β太大,系统可能会变得不稳定,导致抖动或“爆炸”。
4. 恢复系数 (Restitution)
速度级约束也为处理弹性碰撞(反弹)提供了一个优雅的框架。
我们希望碰撞后的相对法向速度 v_new_n 等于 -e * v_old_n,其中 e 是恢复系数。
这可以直接通过设置偏置项 b 来实现:
J * v_new = -e * (J * v_old)
如果同时考虑 Baumgarte 修正和恢复系数,我们的最终偏置项 b 就是:
b = max( -e * (J * v_old), 0 ) - (β/Δt) * C(x)
max 函数确保了只有在物体正在接近时(J*v_old < 0)才会应用反弹效果。
伪代码示例 (在求解器中计算偏置项 b):
// 在迭代求解一个接触约束之前
// 1. 计算当前相对速度
float jv = constraint.J_vA.dot(bodyA.velocity) +
constraint.J_wA.dot(bodyA.angularVelocity) +
constraint.J_vB.dot(bodyB.velocity) +
constraint.J_wB.dot(bodyB.angularVelocity);
// 2. 计算 Baumgarte 偏置 (修正位置误差)
float baumgarte_bias = 0.0f;
if (contact.penetration > slop) {
baumgarte_bias = (beta / dt) * (contact.penetration - slop);
}
// 3. 计算恢复系数偏置 (模拟反弹)
float restitution_bias = 0.0f;
float e = min(bodyA.restitution, bodyB.restitution);
if (jv < -velocity_threshold) { // 只对高速碰撞应用反弹
restitution_bias = -e * jv;
}
// 4. 最终的偏置项
float bias = max(baumgarte_bias, restitution_bias);
// 5. 计算求解冲量所需的右侧项 (rhs)
float rhs = -jv + bias;
// 求解冲量增量
float delta_lambda = rhs * constraint.effectiveMass;
总结
在速度层面处理约束是现代物理引擎的标准实践。它将复杂的非线性位置问题转化为一个高效的线性速度问题。然而,这种方法会引入位置漂移的副作用。通过Baumgarte稳定化,我们可以在速度级求解器中引入一个与位置误差成比例的修正项,从而主动地、持续地将物体拉回到它们应该在的位置。这个简单而强大的技术,使得我们能够在一个统一的框架内,同时处理碰撞响应、位置修正和弹性反弹,是构建一个既高效又稳定的物理引擎的关键所在。