物理加速

理解 PhysX 在 Unity 中的开销来源,用碰撞层、睡眠与 Fixed Timestep 控制物理成本。

学习目标

  • 能解释 Update 与 FixedUpdate 的分工,以及 Fixed Timestep 如何影响 Profiler 里的 Physics 耗时
  • 能说出 Layer Collision Matrix 如何减少 broadphase 检测对,以及 Rigidbody Sleep 何时让静止物体退出模拟
  • 能回答:Profiler 显示 Physics.FixedUpdate 顶满帧预算时,你会先查 Fixed Timestep、碰撞矩阵、还是 Rigidbody/CCD 设置?各解决哪类问题?

原书边界:Chapter 5

Packt 第三版把本章命名为 Faster Physics。官方目录列出的核心范围如下;这些条目共同构成本章的一对一边界:

  • Understanding the physics engine:纳入本章实验与验收,不拆到别章重复计数。
  • Physics performance optimizations:纳入本章实验与验收,不拆到别章重复计数。

物理成本由固定步数、Broad Phase 候选对、Narrow Phase 接触、求解迭代、查询与同步共同决定。PhysX 版本和作业化实现会变化,但 timestep、Layer Matrix、Collider 复杂度、sleep 与 CCD 的成本模型仍成立。 因此,本章既保留 2019/2020 语境,也明确标出迁移到现代 Unity 时哪些是稳定原理、哪些只是版本相关接口。

场景里刚体一多,CPU 物理先爆

关卡里堆了几百个可交互箱子、敌人、子弹——渲染你已经合批了,GPU 不重,帧率却在混战时掉到 40。展开 Profiler → CPU Usage,PhysicsFixedUpdate 占了主线程大半。这不是脚本写慢,而是 PhysX 每固定时间步都要做碰撞 broadphase、窄相检测和约束求解;刚体越多、接触对越多,成本近似线性上涨。

上一章你把美术资源瘦下来了;这章要解决的,是 3D 物理模拟本身的 CPU 账单。目标不是关掉物理,而是 少算不该算的碰撞、让不动的物体睡过去、别为少数高速弹丸把整个场景开到最高精度

像工厂流水线:渲染是包装线,物理是冲压车间

想象一条工厂:包装线(Update) 按顾客下单节奏运转——帧率高就转得快,只负责把画面送到屏幕。冲压车间(PhysX) 则按固定节拍冲压——不管包装线多快,车间仍按 0.02 秒一拍 推进刚体。你在包装线上推箱子,车间下一拍才认;在车间节拍里施力,箱子才按物理规则动。

就是冲压车间。Profiler 里看到的 Physics 模块,基本就是这间车间的电表读数。

FixedUpdate 与 Fixed Timestep

决定 每秒跑多少次物理。默认 50 次/秒;改成 0.01 就变 100 次——刚体更稳,但 Physics CPU 往往近似翻倍

是你在车间节拍里伸手的窗口。Update 每渲染帧一次,适合读输入、插值相机;FixedUpdate 每物理步一次,适合 AddForceMovePosition(Kinematic)等。

0ms+17ms+33ms+50ms+66msUpdate每渲染帧 1 次 · 间隔随 FPS 变化FixedUpdate固定 0.02s(50Hz)· 与帧率解耦PhysXsimsimsimsim碰撞检测 + 求解 · 成本随刚体/接触对数上升渲染帧与物理步不同步:逻辑分 Update(看)与 FixedUpdate(推)
物理在固定时间步推进;渲染帧快时一帧可能跑 0~2 次 FixedUpdate。

在 60 FPS 下,一帧可能 0、1 或 2 次 FixedUpdate——帧时间短时物理步会「追」上来。因此 不要在 Update 里直接改 transform.position 驱动 Dynamic Rigidbody(会被物理步覆盖且抖动);施力、改速度应进 FixedUpdate 或用 Rigidbody.MovePosition(Kinematic)。

0ms+17ms+33ms+50ms+66msUpdate每渲染帧 1 次 · 间隔随 FPS 变化FixedUpdate固定 0.02s(50Hz)· 与帧率解耦PhysXsimsimsimsim碰撞检测 + 求解 · 成本随刚体/接触对数上升Fixed Timestep 越小,每秒物理步越多,CPU 物理 ms 线性上涨
物理在固定时间步推进;渲染帧快时一帧可能跑 0~2 次 FixedUpdate。

Layer Collision Matrix:关掉不该撞的层

是 broadphase 的第一道筛子。场景里有 Default、Player、Enemy、Projectile、Trigger 等层时,敌人与敌人、装饰物与装饰物 往往不需要两两检测——矩阵里关掉,PhysX 连 broadphase 对都不会建。

每个 GameObject 的 Layer(Inspector 顶部)决定它参与哪一行/列。Physics.Raycast 也可通过 layerMask 只打需要的层,减少射线与场景碰撞体的测试次数。

Rigidbody Sleep:静止的箱子别每拍都算

像关电的冲压机——堆在角落不再动的箱子,不应每 0.02 秒仍参与求解。

箱子 AAwake · 每步积分箱子 BAwake · 每步积分箱子 CSleeping 💤刚体运动或受力时保持 Awake,PhysX 每 FixedUpdate 都参与求解
堆满静止道具的场景,睡眠能让大量刚体零成本「挂起」。
箱子 ASleeping 💤箱子 BSleeping 💤箱子 CSleeping 💤速度/角速度低于阈值且稳定 → Sleep,不再参与模拟直到被唤醒
堆满静止道具的场景,睡眠能让大量刚体零成本「挂起」。
箱子 AAwake · 每步积分箱子 BSleeping 💤箱子 CAwake · 每步积分玩家 bump碰撞、力、Kinematic 移动、脚本 WakeUp() 会把 Sleep 体拉回 Awake
堆满静止道具的场景,睡眠能让大量刚体零成本「挂起」。

唤醒 条件:与其它 Awake 体碰撞、受力、WakeUp()、Kinematic 体移动穿过等。因此 频繁微抖的物体很难睡——检查是否脚本每帧给微小力、或碰撞体嵌进地面。

Project Settings → Physics 可调 Sleep ThresholdDefault Contact Offset 等;一般先用默认,优化重点是 减少无意义的 Awake 刚体数量(见下文清单)。

离散 vs 连续碰撞检测

是精度与成本的开关。

tt+1 穿过Discrete:步间 tunnelingDiscrete(默认)
CCD 更贵,只给高速小物体(子弹、球)开;静态场景用 Discrete 即可。

Discrete(默认):每物理步看一次位置——便宜,适合大多数行走、慢速交互。Continuous Dynamic / Continuous Speculative:沿运动方向扫掠,防止子弹、高速球 穿墙(tunneling)——更贵,只应给少数高速 Dynamic 刚体。

CCD:沿运动扫掠,命中墙面Continuous Dynamic / Speculative
CCD 更贵,只给高速小物体(子弹、球)开;静态场景用 Discrete 即可。

动手:物理性能排查清单

下面五步把「Physics 顶满」从测量到改动走通。每切一步,示意图对应当前关注点——建议在混战场景里同步打开 Profiler(Physics 模块)。

猜一猜:500 个堆叠静止箱子和 500 个每帧被 AddForce 的箱子,哪个更可能让 Sleep 失效?

分步1 / 5

① 基线:Profiler 记下 Physics / FixedUpdate ms

0ms+17ms+33ms+50ms+66msUpdate每渲染帧 1 次 · 间隔随 FPS 变化FixedUpdate固定 0.02s(50Hz)· 与帧率解耦PhysXsimsimsimsim碰撞检测 + 求解 · 成本随刚体/接触对数上升Profiler → Physics 看 FixedUpdate 与 PhysX 是否顶满帧预算
物理在固定时间步推进;渲染帧快时一帧可能跑 0~2 次 FixedUpdate。

Play 混战场景 30 秒,Profiler → CPU Usage 展开 PhysicsFixedUpdate。记下 Physics 占帧预算比例;若 >30% 帧时间,物理已是主要 CPU 热点。

代码速查:Layer、Sleep 与检测模式

// 射线只打 Enemy 层(layer 名需与 Tags and Layers 一致)
int mask = LayerMask.GetMask("Enemy");
if (Physics.Raycast(origin, dir, out RaycastHit hit, 100f, mask)) { /* ... */ }
 
// 脚本唤醒 / 强制睡眠(慎用 ForceSleep,需确知无接触)
rb.WakeUp();
// rb.Sleep(); // 仅当确定无交互时使用
 
// 运行时改碰撞检测(仅高速弹丸)
bulletRb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

MeshCollider:非 convex 不能与 Dynamic Rigidbody 做可靠碰撞,且更贵——角色控制器常用 Capsule + 简化 Mesh(convex hull)

容易踩的坑

小结

  • Fixed Timestep 决定每秒物理步数;FixedUpdate 与 PhysX 同步,Update 与渲染同步
  • Layer Collision Matrix 在引擎层剔除层对,比脚本 ignore 更早、更省
  • Rigidbody Sleep 让静止物体退出模拟;微抖、持续接触会阻止睡眠
  • Discrete 默认够用;Continuous 只给高速小 Dynamic 体,全局开启极贵
  • Profiler Physics / FixedUpdate 验收;优化顺序:步频 → 矩阵 → Sleep/刚体数量 → CCD/碰撞体形状

练习

问题 1(改 Demo 型) 在 Physics ms 最高的场景打开 Profiler,记下 Physics 与 FixedUpdate 占比。任选 Layer Matrix 关一对层把一种物体从 Dynamic 改 Kinematic/Static 只做一处改动,复测并填表——Physics ms 降了多少?Active Contacts 有无变化?

问题 2(问答型) Update 与 FixedUpdate 分别适合做什么?为什么改 Dynamic Rigidbody 不应放在 Update?

问题 3(问答型) Layer Collision Matrix 与 Physics.Raycast 的 layerMask 各解决什么问题?

名词解释

本章出现的专业名词,用大白话再讲一遍。

PhysX

Unity 集成的 3D 物理引擎,负责 FixedUpdate 中的刚体模拟。详见「工厂流水线」一节。

Fixed Timestep(固定时间步)

物理步长,默认 0.02s。详见 PhysicsTimestepDiagram highlight fixed。

FixedUpdate

每个物理步调用一次的 MonoBehaviour 回调。详见 PhysicsTimestepDiagram。

Layer Collision Matrix(层碰撞矩阵)

层×层碰撞开关表。详见 LayerCollisionDiagram。

Rigidbody Sleep(刚体睡眠)

静止刚体暂停模拟直至唤醒。详见 RigidbodySleepDiagram state sleep。

Collision Detection(碰撞检测模式)

Discrete 与 Continuous 等模式,权衡 tunneling 与 CPU。详见 CollisionDetectionDiagram。

Continuous Dynamic

对高速 Dynamic 刚体的扫掠检测,防穿墙。详见 CollisionDetectionDiagram mode continuous。

broadphase

物理引擎粗筛可能碰撞的物体对;Layer Matrix 在此阶段前减少候选对。

资料与写作方式声明

本章以Packt《Unity Game Optimization》第3版权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…