Unity物理系统由浅入深第四节:物理约束求解与稳定性
Unity物理系统由浅入深第一节:Unity 物理系统基础与应用
Unity物理系统由浅入深第二节:物理系统高级特性与优化
Unity物理系统由浅入深第三节:物理引擎底层原理剖析
Unity物理系统由浅入深第四节:物理约束求解与稳定性
物理引擎的最终目标是让模拟看起来真实且稳定。但当多个物体同时发生复杂交互时(比如一堆箱子倒塌,或者一个布娃娃角色着地),它们之间的力学关系会变得非常复杂,形成一个庞大的多体约束问题。约束求解器(Constraint Solver)就是用来解决这些复杂相互作用的“大脑”。
1. 什么是物理约束?
在物理引擎中,“约束”不仅仅指我们 Unity 中使用的 Joint
组件。它是一个更广义的概念,包含了:
- 接触约束 (Contact Constraints):这是最常见的约束,用于防止两个物体相互穿透。当两个物体发生碰撞并产生接触点时,物理引擎会施加一个“推力”来将它们分开,并处理摩擦力和弹性(弹跳)。
- 关节约束 (Joint Constraints):我们上一篇讲过的
Hinge Joint
、Spring Joint
等,它们限制了两个 Rigidbody 之间的相对运动(比如只能绕某个轴旋转,或者保持固定距离)。 - 运动学约束 (Kinematic Constraints):当一个 Rigidbody 被设置为
Is Kinematic
时,它的位置和旋转完全由用户代码控制,但它仍然可以影响其他非运动学 Rigidbody。它本质上是对自身运动的一种强制约束。 - 休眠约束 (Sleep Constraints):当 Rigidbody 长时间静止时,为了节省性能,物理引擎会将其设置为“睡眠”状态,停止对其进行计算。这也算是一种隐性的约束,即约束其保持静止。
所有这些约束,无论表现形式如何,最终都会被转化为数学问题,通过约束求解器来解决。
2. 线性互补问题 (LCP - Linear Complementarity Problem)
在实时物理引擎中,解决刚体之间的复杂交互,特别是摩擦和非穿透条件,常常可以建模为一个线性互补问题 (LCP)。
- 核心思想: LCP 是一种数学问题,它涉及寻找满足一系列线性方程和不等式的变量,并且这些变量与某些互补变量的乘积为零。
- 在物理中的应用:
- 非穿透 (Non-Penetration):碰撞后的反弹力(法向冲量)必须是非负的(只能将物体推开,不能拉近),并且只有当物体相互接触时才施加力。
- 摩擦 (Friction):摩擦力必须与相对滑动速度方向相反,并且其大小不能超过最大摩擦力。当物体不滑动时,摩擦力可以小于最大摩擦力。
- 将所有接触点、关节限制等转化为 LCP 问题,物理引擎就能在统一的框架下计算出所有所需的冲量,从而更新物体的位置和速度。
3. 约束求解器 (Constraint Solver)
约束求解器是物理引擎的“心脏”,负责计算如何施加必要的冲量(或力)来满足所有的约束条件。由于 LCP 问题通常没有解析解(无法直接计算出结果),所以物理引擎通常采用迭代求解器 (Iterative Solver)。
3.1. 顺序脉冲法 (Sequential Impulse - SI)
- 原理: 这是 PhysX (以及许多其他实时物理引擎) 最常用的迭代求解器之一。它的核心思想是:每次只处理一个约束。
- 遍历所有需要解决的约束(碰撞接触、关节等)。
- 对于每个约束,计算出为了满足它所需的冲量。
- 立即将这个冲量施加到相关的 Rigidbody 上,更新它们的速度。
- 重复这个过程多次(迭代),直到所有约束都近似满足,或者达到预设的迭代次数。
- 优点:
- 简单且高效: 实现相对简单,每一步计算量小。
- 并行性好: 现代实现中,可以通过多线程并行处理多个独立的约束。
- 收敛性相对较好: 对于大多数游戏场景,经过几次迭代就能达到可接受的效果。
- 缺点:
- 迭代次数影响精度: 迭代次数越多,结果越精确和稳定,但性能开销也越大。
- 顺序依赖性: 约束的处理顺序可能会影响收敛速度和最终结果,尤其是在复杂的多体碰撞中。
- 刚性连接: 对于长链条或高密度物体堆叠,可能需要更多的迭代才能解决抖动和不稳定性。
3.2. 投影高斯-赛德尔法 (Projected Gauss-Seidel - PGS)
- 原理: 顺序脉冲法其实是投影高斯-赛德尔法的一种具体实现形式。PGS 是一种用于解决线性方程组的迭代方法,当应用于物理约束求解时,它会在每次迭代中,将当前计算出的冲量“投影”到可行域(满足约束的范围)内。
- 与 SI 的关系: 顺序脉冲法可以看作是 PGS 在特定物理问题(LCP)上的应用,每次迭代更新一个变量(冲量),并立即将这个更新反映到后续的计算中。
3.3. 迭代次数与精度
- 在 Unity 中,你可以在 Edit -> Project Settings -> Physics 中调整物理引擎的迭代次数:
Solver Iteration Count
(位置迭代):控制约束求解器解决位置穿透和关节限制的迭代次数。Velocity Iteration Count
(速度迭代):控制约束求解器解决速度(冲量)约束的迭代次数,这会影响反弹、摩擦的准确性。
- 更高的迭代次数意味着:
- 更精确的物理模拟。
- 更少的穿透和抖动。
- 更稳定的关节和堆叠物体。
- 更高的 CPU 开销。
- 更低的迭代次数意味着:
- 性能更好。
- 但可能出现物体穿透、抖动或不稳定的情况。
最佳实践: 找到一个平衡点。通常默认值适用于大多数游戏。只有当出现明显的物理不稳定问题时,才考虑适当增加迭代次数。
4. 数值稳定性:避免抖动与穿透
物理模拟的稳定性是其可靠性的基石。常见的数值不稳定问题包括:
- 穿透 (Penetration):物体相互进入对方内部,而不是正确地碰撞和分离。这是最常见的问题,通常由时间步长过大或迭代次数不足引起。
- 抖动 (Jitter):物体在应该静止时却不断地微小振动。这通常发生在求解器无法完全满足所有约束,或者由于浮点精度问题。
- 爆发 (Explosion):物体突然以极高的速度飞散开来,模拟崩溃。这通常是由于极端的数值不稳定导致的。
导致不稳定的原因:
- 时间步长过大 (
Fixed Timestep
):- 如果物理更新频率过低,高速移动的物体在两个物理帧之间可能移动了很长的距离,导致它们“跳过”了碰撞检测,直接穿透了其他物体(“隧道效应”)。
- 解决方案: 减小
Fixed Timestep
(提高物理更新频率) 可以提高稳定性,但会增加性能开销。对于高速物体,可以结合Continuous Collision Detection
(CCD)。
- 迭代次数不足 (
Solver Iteration Count
):- 求解器无法在有限的迭代次数内完全解决所有约束,导致残余的穿透或不满足的力。
- 解决方案: 适当增加迭代次数。
- 浮点精度问题 (Floating Point Precision):
- 计算机使用浮点数表示实数,存在精度限制。长时间模拟或大世界场景可能累积误差。
- 解决方案: 对于大世界场景,考虑使用双精度浮点数(Unity Editor 默认为单精度,但一些特定功能或自定义物理系统可能会使用双精度)。但在标准 Unity 游戏开发中,通常不需要特别关注这点,因为它会带来性能开销。
- 不正确的物理参数设置:
- 过高的弹跳 (
Bounciness
) 或过低的阻力 (Drag
) 可能导致物体持续反弹或滑动,难以进入睡眠状态。 - 不合理的质量比或过大的力。
- 解决方案: 合理调整
Physic Material
和Rigidbody
属性。
- 过高的弹跳 (
- 碰撞体形状问题:
- 复杂的 Mesh Collider(特别是凹的或非凸的)可能导致碰撞检测不准确或性能问题。
- 解决方案: 简化碰撞体,尽量使用原始碰撞体组合。
物理睡眠状态 (Sleeping):稳定性的重要机制
- 为了优化性能和提高稳定性,当一个 Rigidbody 在一段时间内(通常是几秒)速度和角速度都非常低时,物理引擎会将其设置为睡眠 (Sleeping) 状态。
- 处于睡眠状态的 Rigidbody 不再进行物理计算,直到它受到外力、与其发生碰撞,或者被脚本手动唤醒 (
Rigidbody.WakeUp()
)。 - 重要性: 睡眠状态极大地减少了场景中静止物体的计算量,是物理引擎保持高性能的关键。如果你的物体在静止时仍然不断抖动,它们就无法进入睡眠,持续消耗 CPU 资源。
总结
通过本篇的学习,我们已经深入了解了物理引擎如何解决复杂的约束问题,理解了 LCP 问题的概念,以及 顺序脉冲法 (Sequential Impulse) 这样的迭代求解器是如何工作的。更重要的是,我们现在掌握了影响物理系统稳定性的关键因素,并知道如何通过调整时间步长、迭代次数以及优化物体参数来解决常见的抖动和穿透问题。
理解这些底层原理,能够让我们在面对 Unity 物理系统中的疑难杂症时,不再盲目尝试,而是能够有针对性地进行分析和解决。
接下来,我们将把这些理论知识付诸实践,进入第五篇:手写物理系统入门与实践。
Unity物理系统由浅入深第一节:Unity 物理系统基础与应用
Unity物理系统由浅入深第二节:物理系统高级特性与优化
Unity物理系统由浅入深第三节:物理引擎底层原理剖析
Unity物理系统由浅入深第四节:物理约束求解与稳定性