3D 物理引擎核心概念:碰撞、分离、约束与并行
从刚体与形状出发,沿一次物理更新的管线理解碰撞检测、接触分离、约束求解、休眠、岛屿划分与多线程——不绑定具体引擎实现,建立可迁移的 mental model。
打开 Unity 的 Rigidbody、Godot 的 CharacterBody3D,或自研引擎里的一行 world.step(dt),背后往往是一套高度结构化的仿真管线。它并不神秘:把连续世界离散成固定时间步,在每一步里预测运动、找出相交、修正穿透、满足约束,再把结果写回游戏逻辑。
本文从 3D 刚体物理引擎 的共性出发,梳理各核心模块在做什么、彼此如何衔接。不深入某个库的源码细节;若你关心 PhysX、Jolt、Bullet 等具体选型,可继续读 游戏物理引擎横评。若「时间步如何推进、积分器为何影响稳定性」还不清楚,数值积分与游戏物理 是很好的前置。
范围说明
下文聚焦 刚体动力学 + 碰撞 + 约束 这一层——角色控制器、布料、流体、破坏等常在其上再建一层,或走完全不同的求解器。
1引擎在管什么:四个核心对象
在 mental model 里,先把这几样东西分开:
| 概念 | 职责 | 典型数据 |
|---|---|---|
| 刚体(Rigid Body) | 会动、有质量、受力和力矩 | 位姿、线/角速度、质量与惯性张量 |
| 碰撞形状(Shape / Collider) | 定义「占据哪块空间」 | 球、盒、胶囊、凸包、三角网格等 |
| 接触与约束(Contact / Constraint) | 描述物体之间「不能怎样」 | 接触法线、穿透深度、关节限制 |
| 仿真世界(World / Scene) | 调度一步更新里的全部阶段 | 宽相结构、岛屿、休眠表、求解器参数 |
刚体 可以 带多个形状(复合碰撞体);形状 不一定 等于渲染网格——高精度网格往往只用于静态环境,动态物体更常用凸包或简单图元,这是性能与稳定性的工程取舍,而非数学必然。
2一步更新里发生了什么
大多数实时 3D 引擎把物理帧拆成固定顺序。名字因实现而异,逻辑大致如下:
先建立这条主线
碰撞检测 回答「谁碰到了谁」;分离(position correction) 与 约束求解 回答「碰到之后怎样才不穿模、怎样摩擦、关节怎样约束」;休眠 与 岛屿 回答「哪些东西这一步可以不算」;并行 则在这些阶段里找可独立的工作块。
固定时间步(fixed timestep)很常见:渲染帧率波动时,逻辑仍用 dt = 1/50 或 1/60 推进,必要时在一帧内 子步(substep) 多次,避免大步长导致隧道效应或求解发散。1
3碰撞检测:从「可能」到「确实」
碰撞检测通常分两阶段,因为 的 pairwise 测试在数百个动态体时就吃不消。
3.1宽相(Broad Phase)
目标:快速筛出 可能 相交的刚体对,丢弃明显远离的组合。
常见结构:
| 结构 | 思路 | 适合场景 |
|---|---|---|
| 动态 AABB 树(BVH) | 层次包围盒,增量更新 | 分布较散、物体尺寸差异大 |
| 空间哈希 / 均匀网格 | 按格子索引 AABB | 规模中等、实现简单 |
| Sweep and Prune | 排序轴上的区间重叠 | 2D 或某一轴主导的场景 |
宽相只处理 轴对齐包围盒(AABB) 或 定向包围盒(OBB) 的相交,结果是一批 候选对(broad phase pair),仍可能包含假阳性。
3.2窄相(Narrow Phase)
对每一对候选,用 精确几何 判断是否相交、穿透多深、接触点在哪。
- 球、盒、胶囊 等解析图元:有闭式或专用算法,便宜且稳定。
- 凸体 vs 凸体:GJK(Gilbert–Johnson–Keerthi)求最近点/是否相交;若已穿透,常接 EPA(Expanding Polytope Algorithm)估深度与法线。2
- 凹体 / 三角网格:通常先分解为凸包,或只对 静态 网格做更贵的测试;网格自相交、薄墙、非流形是经典坑点。
输出不是「碰了/没碰」这么简单,而是 接触流形(contact manifold):少量代表点(常 1–4 个)、接触法线 、穿透深度 。流形在下一步会转成 非穿透约束。
3.3连续碰撞检测(CCD)
离散步长下,薄物体或高速子弹可能在两帧之间 穿过 薄墙——所谓 tunneling。CCD 在步内沿运动方向做 扫掠测试(sweep test) 或 保守推进,在 TOI(time of impact)处提前产生接触。3
代价更高,一般只对标记为「高速」的物体开启。
4分离:先把穿透「掰开」
检测到穿透后,引擎必须 减少几何重叠。玩家感知上的「穿模」,往往来自这一步做得不够,或只做速度层修正、位置仍嵌在一起。
4.1速度层 vs 位置层
- 速度层(velocity-level):通过冲量改变 ,使相对法向速度满足非穿透趋势;适合 即将接触 或 轻微擦碰。
- 位置层(position-level / separation):直接微调位姿 ,把深度 消掉一部分;适合 已经深度嵌入 的情况。
许多引擎在求解器里 同时 做两者:先位置修正(split impulse / position iterations),再速度冲量(velocity iterations),名称因库而异。1
4.2Baumgarte 稳定化
纯硬约束在离散时间下容易 抖动或弹飞。Baumgarte 把「期望的法向相对速度」写成与穿透深度成比例的一项:
控制 多快把洞填平;过大则像硬弹开,过小则长时间轻微穿模。这是 稳定化,不是真实材料的物理定律——游戏物理里大量参数都属于这一类「看起来对」的旋钮。
4.3分离与堆叠
堆箱子时,底层接触多、穿透深度小但 约束耦合强。只做一次分离往往不够;引擎会用 多次 position iteration,并在深度很小时 允许微小穿透(slop) 换稳定。这也是为什么「solver iterations」是 Inspector 里常见的调参项。
分离 ≠ 求解完成
把两物体推开只解决 几何重叠。摩擦、反弹、关节、堆叠重量分配,仍要靠下一节的 约束求解 在速度空间迭代完成。
5约束求解:冲量、摩擦与关节
接触可以看成 不等式约束:法向只能推、不能拉(除非用粘性模型);切向摩擦有 Coulomb 上限。铰链、滑块、弹簧则是 等式或带界限的等式约束。统一写法下,一步更新末尾常解一个 ** complementarity problem** 或在其上的 迭代近似。1
5.1序列冲量(Sequential Impulse)
Erin Catto 在 Box2D 里推广的做法:把全局问题拆成 逐约束标量冲量,Gauss–Seidel 式 依次更新 每个约束,多轮迭代逼近 simultaneous 解。1
对单个接触点,法向冲量 限制物体沿 的相对运动;若 ,再算切向摩擦 ,并钳在 以内( 为摩擦系数)。
关节(hinge、fixed、6-DOF spring)同样化为 Jacobian 行:约束对两个刚体速度的微分关系,以及 有效质量(effective mass) 的逆用于求冲量步长。
5.2迭代次数与收敛
| 参数 | 典型作用 |
|---|---|
| velocity iterations | 摩擦、反弹、关节「跟手」程度 |
| position iterations | 穿模深度、堆叠稳定性 |
| ERP / CFM 等 | 软约束、数值柔顺(因引擎而异) |
迭代 不是越多越好:收益递减,且每轮都要扫一遍约束。竞技或 VR 项目常在「稳定堆叠」与「CPU 预算」之间折中。
5.3与积分的先后
常见顺序(与上文流程图一致):
- 用 半隐式 Euler 或类似积分器,从力更新速度;
- 碰撞 + 约束求解 修正速度(有时也修正位置);
- 再用速度更新位姿。
若先更新位姿再求解,穿透会更深;若积分器与求解器不匹配,会出现 数值积分文章 里说的 能量爆炸 或 假阻尼。
6休眠:不动的物体别算
堆好的家具、掉线后停住的 debris,若每帧仍走完整管线,是纯浪费。休眠(sleeping / deactivation) 把 动能低于阈值 且 持续若干帧 的刚体标记为 sleep,从宽相活跃集、岛屿求解里剔除,直到被 唤醒(wake)。
典型唤醒条件:
- 与 活跃 刚体发生碰撞或产生冲量;
- 被脚本 施加力/冲量 或 teleport;
- 关节 连到活跃体,整条链可能被拉起。
工程意义
休眠是开放世界与物理 puzzle 能否「scalable」的关键之一。设计关卡时,大量「装饰性物理」若默认 Dynamic,可能在不知不觉中吃掉整个物理线程。
阈值设得太松,物体会在「几乎不动」时仍抖动而无法入睡;太紧则轻微碰撞后难以再次稳定——需要结合 linearSleepingThreshold、angularSleepingThreshold 等(各引擎命名不同)与 solver 迭代 一起调。
7岛屿:谁和谁必须一起算
岛屿(island) 是 约束图上的连通分量:通过接触或关节 直接或间接相连 的刚体属于同一岛;与当前岛无任何约束联系的,可以在本步 独立求解。
静态体、运动学体、休眠体通常 不进入活跃岛,或作为 零质量/无限质量 端点参与约束但不积分。
岛屿划分的作用:
- 正确性:求解器只在有耦合的子集上迭代,避免把无关物体绑进同一线性系统。
- 并行:不同岛屿之间 无数据依赖,可分配到不同 worker(见下节)。
- 增量:仅 活跃岛 需要重建接触与迭代;睡着的岛整岛可跳过。
8并行:哪里能拆,哪里不能
现代 3D 引擎(Jolt、PhysX 3+、Bullet 多线程分支等)普遍用 任务/job 系统 做并行,但 并非每一步都能任意并行。
| 阶段 | 并行性 | 说明 |
|---|---|---|
| 宽相 | 较好 | 更新 BVH、扫描格子可分区或并行遍历 |
| 窄相 | 中等 | 各 候选对 独立,但需避免写同一刚体的 race |
| 约束求解 | 岛级并行 | 岛内 多为顺序 GS;岛与岛之间 可并行 |
| 积分 | 较好 | 各活跃刚体位姿更新通常独立 |
确定性(determinism) 与 并行 常冲突:同一输入若 worker 调度顺序不同,浮点累加顺序变化会导致联机回滚或 replays 不一致。竞技联机有时强制 单线程求解 或固定调度;单机则优先吞吐。4
射线检测、overlap query 常与 动态场景查询结构 共享 BVH,可在仿真步之外 只读并行,这也是「物理引擎 = 碰撞 + 动力学」一体的原因。
9把概念对齐到调参面板
读文档或 Inspector 时,可以把旋钮映射回上文:
| 你看到的参数 | 对应模块 |
|---|---|
| Collision layers / groups | 宽相前的 过滤,减少无效对 |
| Contact offset / skin width | 窄相与 提前生成接触,减抖动 |
| Solver iterations | 分离 + 约束 轮数 |
| Bounce / friction | 约束求解里的 恢复系数与 Coulomb 摩擦 |
| Sleep threshold | 休眠 判定 |
| CCD / continuous | 窄相扫掠,防隧道 |
| Max depenetration velocity | 分离 时限制单步推开速度,防弹飞 |
没有「一组万能默认值」:堆叠 puzzle、第一人称射击、载具链式关节,最优迭代次数和 sleep 策略会差很多。
10小结
3D 物理引擎的核心可以压缩成一条链:
形状定义空间 → 宽相/窄相找接触 → 分离减少穿透 → 迭代冲量满足法向/摩擦/关节 → 积分推进 → 休眠与分岛跳过无关工作 → 多线程吃满岛屿与独立对。
理解这条链之后,再看具体引擎文档,重点就不是背 API,而是问:它的宽相结构、求解器模型、CCD 与网格策略、线程模型 是否符合你的项目瓶颈。下一篇自然可以是 游戏物理引擎横评,把 mental model 落到选型与集成成本上。
注释
-
Catto, Erin. Soft Constraints / Modeling and Solving Constraints(GDC 系列讲义,Box2D 作者)。阐述序列冲量、Baumgarte 稳定化、接触流形与 position/velocity 迭代的分工,是理解 2D/3D 游戏物理求解器的常用入口。 ↩ ↩2 ↩3 ↩4
-
van den Bergen, G. Collision Detection in Interactive 3D Environments;以及 Ericson, Real-Time Collision Detection 中 GJK/EPA 章节。说明凸体窄相在实时引擎中的标准用法。 ↩
-
Bullet Physics Documentation, Continuous Collision Detection and Ray Test。概述 sweep 与 CCD 在离散仿真中缓解 tunneling 的角色。 ↩
-
Jolt Physics, Architecture overview 与文档中关于 job system、island 并行与 determinism 的说明。Jolt 明确以多核友好与可配置确定性为设计目标之一。 ↩
CONTINUE EXPLORING