UE5.8 Chaos Physics 物理基础

仓库里的木箱

阿诚正在做一个仓库解谜 Demo。场景中央堆着十几只木箱,玩家可以推箱子、拉货架,也可以从二楼扔下一袋水泥。他在 UE4 时代就做过类似的玩法,当时用的是 PhysX:箱子叠高了会莫名抖动,快速抛出的物体会穿过地面,睡着的刚体被碰撞唤醒后偶尔会抽搐一下。升级到 UE5.8 之后,底层换成了 Chaos Physics。阿诚发现同样数量的箱子稳定了许多,但一些旧项目的调参经验不再完全适用。这篇文章从这次仓库搬箱子的经历出发,把 Chaos 里最常见的几个基础概念串一遍。

Chaos 与旧 PhysX 的差异

PhysX 是 NVIDIA 的闭源物理引擎,Epic 在 UE4 中把它深度集成到 FPhysSceneUBodyInstance 里。开发者大部分时候只接触蓝图节点和碰撞预设,黑盒内部却很难干预。Chaos 是 Epic 自研的求解器,从 UE5 开始全面替代 PhysX。两者最直观的区别并不是 API,而是所有权结构:Chaos 把几何、刚体、约束、求解器都放进了引擎自己的模块,调试、扩展和定制都更直接。

group engine[Unreal Engine 5.8]
    service game[Game Thread] in engine
    service tick[Physics Tick] in engine
    service chaos[Chaos Solver] in engine
    service broad[Broadphase] in engine
    service narrow[Narrowphase] in engine
game:B --> T:tick
tick:B --> T:chaos
chaos:B --> T:broad
broad:B --> T:narrow

上面的示意图只画到碰撞阶段。真实管线还会经过约束求解、积分、同步渲染位置等步骤。PhysX 时代这些步骤在第三方库内部完成;Chaos 时代 Epic 把管线拆得更开,方便后续做 Determinism、Replay、网络同步和破坏系统。阿诚的实际感受是:同样的蓝图调用,Add ImpulseSet Physics Linear Velocity 行为基本一致,但堆叠稳定性、CCD 表现和报错信息都变好了。

Chaos 还统一了刚体与破坏的数据表示。PhysX 的破坏需要 APEX 或单独的插件;Chaos 的 Geometry Collection 直接复用同一套刚体与约束框架,因此可以做出“一堵墙被砸开后碎片继续参与物理交互”的效果,而不用在多个子系统之间切换。

刚体模拟基础

刚体是物理世界最常见的抽象:它假设物体内部任意两点之间的距离不变,因此只需要记录位置、朝向、线速度、角速度、质量和惯性张量。阿诚在仓库里放了一只圆桶,让它从斜坡上滚下来。圆桶的质量是 20kg20\,\text{kg},重力加速度取 9.8m/s29.8\,\text{m/s}^2,那么重力给它的合力就是 196N196\,\text{N}

动力学方程用最朴素的形式写就是:

\mathbf{F} = m\mathbf{a}, \quad \boldsymbol{\tau} = \mathbf{I}\boldsymbol{\alpha}

其中 F\mathbf{F} 是合外力,mm 是质量,a\mathbf{a} 是线加速度;\boldsymbol{\tau} 是合外力矩,I\mathbf{I} 是惯性张量,\boldsymbol{\alpha} 是角加速度。游戏引擎里不会真的去解这个连续方程,而是把每帧切成很小的时间片,做数值积分。

最常用的显式欧拉积分写成递推式:

xt+1=xt+vtΔt,vt+1=vt+atΔt\mathbf{x}_{t+1} = \mathbf{x}_t + \mathbf{v}_t \Delta t, \quad \mathbf{v}_{t+1} = \mathbf{v}_t + \mathbf{a}_t \Delta t

这里 Δt\Delta t 是当前步长。步长越大,误差越明显:圆桶可能直接滚进坡底,也可能因为速度过大而穿透地面。Chaos 默认以固定频率运行物理 tick,具体数值可以在 Project Settings > Physics > Physics Simulation 里找到,默认通常是 60Hz60\,\text{Hz},也就是每步约 16.67ms16.67\,\text{ms}

刚体还有两个容易被忽略的属性:质心和阻尼。质心决定了力施加在哪里会产生纯平动还是转动;阻尼则像空气阻力一样持续消耗动能。阿诚一度把圆桶的线性阻尼设得很高,以为这样滚动会更稳,结果箱子像泡在水里一样慢吞吞。后来他只在需要快速稳定的地方提高角阻尼,保留线性阻尼默认值。

碰撞检测与响应

圆桶滚下斜坡后撞上了一摞木箱。碰撞检测并不是一上来就计算精确接触点,而是分两步走。

第一步是 Broadphase。Chaos 会给每个参与物理的物体维护一个轴对齐包围盒(AABB),只在包围盒重叠的物体对之间进入下一步。这一步把 O(n2)O(n^2) 的配对降到接近 O(n)O(n)

第二步是 Narrowphase。对于真正靠近的几何体,Chaos 使用 GJK 和 EPA 算法计算最近距离、穿透深度和接触法线。GJK 擅长判断两个凸体是否相交,EPA 在相交时给出最小穿透向量。复杂网格通常会被预处理为凸包或三角网格,用于不同精度的查询。

TD
A[Game Tick 开始] --> B[Broadphase AABB 筛选] B --> C[Narrowphase GJK/EPA] C --> D{是否存在接触} D -->|是| E[构建 Contact Manifold] D -->|否| F[跳过该物体对] E --> G[求解接触约束] G --> H[计算冲量并应用] H --> I[积分更新速度与位置] I --> J[同步到渲染线程]

碰撞响应基于冲量。两个刚体在接触点交换的冲量大小由相对速度、质量、恢复系数和摩擦系数共同决定。Chaos 内部会把接触约束和其他约束一起放进求解器,迭代若干次后得到满足所有约束的速度。阿诚之前用 PhysX 时,箱子叠到五层以上就开始高频抖动,原因往往是接触法线在不断翻转,求解器每一帧给出的冲量方向都不一致。Chaos 在 Manifold 持久化和 contact offset 上做了不少改进,同样堆叠高度下明显更稳。

Sleep / Wake 机制

木箱被撞散后,有几只滚到墙角停了下来。如果引擎继续为它们做积分、碰撞查询和约束求解,帧时间会白白浪费。Sleep 机制就是为了解决这个问题:当刚体的线速度和角速度都持续低于某个阈值,求解器就把它标记为 Sleeping,暂时停止模拟。

阿诚在场景里放了一座由八只箱子堆成的金字塔。金字塔静止几秒后全部进入 Sleep 状态。此时他控制玩家从二楼扔下一袋水泥,水泥袋撞到底部箱子,Chaos 会把被撞的箱子 Wake 起来,箱子再带动上面一层,整个金字塔像多米诺一样逐层苏醒。

participant Player as 玩家
participant Bag as 水泥袋
participant Bottom as 底层木箱
participant Top as 顶层木箱
participant Solver as Chaos Solver
Player->>Bag: 投掷
Solver->>Bag: 速度高于阈值,保持 Wake
Bag->>Bottom: 碰撞
Solver->>Bottom: Wake
Bottom->>Top: 接触并传递力
Solver->>Top: Wake
Solver->>Solver: 求解整帧约束
Top-->>Bottom: 位移与速度更新

Sleep 阈值可以在 Project Settings > Physics 里调整,也能在单个 Primitive Component 上覆盖。需要注意的是,Sleep 并不代表物体从世界中移除,只是不再主动积分。如果其他物体撞上它,或者代码调用 WakeRigidBody,它会立刻恢复模拟。阿诚踩过的一个坑是:他把 Sleep Threshold 设得太低,导致轻微抖动的箱子迟迟不能入睡,CPU 开销居高不下;设得太高又会让应该静止的箱子在玩家注视下微微滑动,破坏沉浸感。

Sub-stepping 与物理频率

阿诚的 Demo 里还有一段弹弓玩法:玩家把石块高速弹射出去,击中远处货架。在 60Hz60\,\text{Hz} 的默认物理频率下,石块飞行速度如果太快,单帧位移可能超过自身尺寸,这时 Narrowphase 就来不及捕获接触,石块会直接穿过货架。这种穿透问题叫 Tunneling。

Sub-stepping 是 Chaos 用来缓解 Tunneling 的主要手段。它会在一帧游戏 tick 内把物理时间切成更小的片段,在每个子步里重新跑碰撞检测和积分。Sub-stepping 的数量可以在项目设置里限制,常见做法是 Max Substeps = 5Fixed Time Step = 0.0167 秒。

TD
A[游戏帧 tick] --> B{当前帧剩余时间 > 0} B -->|是| C[取最小子步长] C --> D[在该子步内积分] D --> E[碰撞检测与约束求解] E --> F[更新刚体状态] F --> G[剩余时间 -= 子步长] G --> B B -->|否| H[本帧结束,同步结果]

子步带来更高的模拟精度,但代价是 CPU 时间。阿诚在普通仓库场景里保持默认子步,只在弹弓关卡把 Max Substeps 临时调高。他还在代码里用 FMath::Min 钳制了石块的最大角速度,避免过快的自转让接触点频繁跳变。

UE5.8 还继续改进了异步物理 tick。游戏逻辑线程和物理线程不再严格同步,Physics State 通过缓冲区与渲染线程交互。这样即使物理帧率波动,渲染帧率也能保持相对平滑。开发者在蓝图中写物理交互时要小心:不要在 Tick 里直接读取当前帧的物理位置做精确判定,因为读取到的可能是上一物理步的结果。

物理材质与摩擦力、弹性

仓库地面有时是水磨石,有时是结冰的卸货台。阿诚用 Physical Material 给不同表面区分摩擦和弹性。Physical Material 不是材质球,而是单独的资源,可以在 Static Mesh 的 Collision 部分指定。

两个物体接触时,摩擦力和反弹力由双方的 Physical Material 共同决定。摩擦系数 μ\mu 的常见混合方式有 Average、Minimum、Maximum 和 Multiply。假设木箱与地面的 μ\mu 分别是 0.60.60.40.4,使用 Average 混合后等效摩擦就是 0.50.5

库仑摩擦模型把摩擦力限制在:

FfrictionμFnormalF_{\text{friction}} \leq \mu F_{\text{normal}}

其中 FnormalF_{\text{normal}} 是接触法向力。也就是说,法向压力越大,能提供的最大摩擦力也越大。如果切向力没有超过这个上限,物体就不会滑动。

弹性用恢复系数 ee 表示,范围通常在 0011 之间。e=0e=0 是完全非弹性碰撞,碰撞后两物体贴在一起;e=1e=1 是理想弹性碰撞,动能没有损失。现实中的橡胶球 ee 可能在 0.80.8 左右,沙袋接近 00。阿诚把水泥袋的恢复系数调到很低,落地时不会乱蹦;把弹弓石块的弹性适中调高,击中货架时更有打击感。

UE5.8 里 Physical Material 的界面和 PhysX 时代看起来差不多,但底层参数已经映射到 Chaos 的摩擦与恢复模型。迁移项目时要重新抽查几个关键表面的手感,旧数值不一定能一比一直接复用。

简单物理交互示例

阿诚写了一个蓝图函数,在玩家点击箱子时给它一个水平冲量。如果用 C++ 实现,可以在继承自 AActor 的类里这样写:

void AMyCrate::OnPoked()
{
    UPrimitiveComponent* Prim = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!Prim || !Prim->IsSimulatingPhysics())
    {
        return;
    }

    FVector Impulse = GetActorForwardVector() * 800.0f;
    FVector Location = GetActorLocation();

    Prim->AddImpulseAtLocation(Impulse, Location);
}

这段代码先判断根组件是不是物理模拟状态,然后沿角色正前方向施加 800Ns800\,\text{N}\cdot\text{s} 的冲量。冲量作用点是物体中心,所以不会产生额外转矩。如果想让箱子翻倒,可以把作用点抬高到箱子顶部。

另一个常用调试命令是显示物理休眠状态。在控制台输入:

p.Awake 1
p.Chaos.DebugDraw.Sleep 1

运行时 Sleeping 的物体会被染成深色,Wake 的物体呈亮色。阿诚靠这个快速定位哪些箱子因为阈值不当而一直醒着。

UE5.8 Chaos 的改进

UE5.8 并不是 Chaos 的第一次登场,而是把前几年积累的东西打磨得更适合生产。官方 release notes 把 Dataflow 标为 Production-Ready,这意味着破坏资产可以用节点化、非破坏式的方式反复迭代。Chaos Cloth 也进入 Production-Ready,并通过新的 Dataflow-based Cloth Panel Editor 与服装制作流程对齐。Chaos Hair、Chaos Fluids、Chaos Destruction 和模拟核心本身都收到了大量小幅度更新。

对阿诚这种主要用刚体做Gameplay 的开发者来说,UE5.8 最实在的变化是稳定性和调试体验。堆叠Solver 在接触法线跳变时更稳,异步 tick 的边界情况处理得更干净,Chaos Visual Debugger 也能把物理状态录下来逐帧回放。Epic 还在统一 Chaos Flesh 与 Chaos Cloth 的求解器,未来肌肉与皮肤之间的交互会自然许多。

小结

仓库里的木箱、圆桶和水泥袋,把 Chaos Physics 的几块基础拼图画了出来。Chaos 与 PhysX 最大的不同在于所有权和管线结构;刚体模拟靠质量、惯性、力和积分推进;碰撞检测分 Broadphase 与 Narrowphase;Sleep/Wake 控制静止物体的开销;Sub-stepping 用更多 CPU 换取高速运动时的稳定性;Physical Material 则把摩擦和弹性从美术材质里抽离出来。

阿诚最后把仓库 Demo 的物理频率保持默认,只在弹弓关卡临时提高子步;把箱子堆叠层数放宽到八层而不抖;给不同地面分配了 Physical Material。UE5.8 的 Chaos 已经能撑起这些常见玩法,理解底层机制之后,调参就不再是盲目试错。