从应用层看,PhysX 一步模拟通常只有:
scene->simulate(dt);scene->fetchResults(true);但内部完成的是一个完整的数据处理过程:
读取当前物理状态和游戏输入,发现物体之间的交互,将接触和关节转化为约束,求解新的运动状态,最后交付模拟结果与事件。
下面以PhysX 4.x/5.x 的 CPU 刚体模拟为主说明。不同版本、PGS/TGS 求解器、CCD 和 GPU 配置会改变执行细节。
以下步骤按照职责和数据依赖划分,不是某个版本严格串行的函数调用顺序。
一、先明确:“物理一步”不等于“渲染一帧”
渲染帧的时间通常不固定,物理模拟一般使用固定时间步:
constexprfloatphysicsDt=1.0f/60.0f;因此,一个渲染帧中可能执行零次、一次或多次物理模拟。
本文中的“一帧模拟”,指的是:
从时刻 t 的物理状态,推进到时刻 t + dt 的物理状态其输入和输出可以概括为:
输入: 当前位姿、速度 力、力矩、运动学目标 碰撞形状、材质、过滤规则 关节及驱动参数 时间步 dt 输出: 新的位姿、速度 更新后的接触与交互状态 睡眠、唤醒、碰撞、触发器等事件二、整体流程图
游戏逻辑提交物理输入 │ ▼ ① 模拟准备与任务调度 │ ├──→ 运动学状态与外力相关准备 │ ▼ ② 更新形状边界 │ ▼ ③ 宽相检测 Broad Phase │ ▼ ④ 碰撞过滤与交互关系维护 │ ▼ ⑤ 窄相检测 Narrow Phase │ ▼ ⑥ 岛屿与活动对象管理 │ ▼ ⑦ 约束准备 │ ▼ ⑧ 约束求解 │ ▼ ⑨ 状态积分与必要的 CCD 处理 │ ▼ ⑩ 睡眠判定与内部结果整理 │ ▼ ⑪ fetchResults:完成结果获取与事件交付 │ ▼ 游戏引擎同步 Transform、消费事件、渲染插值实际内部存在并行、分阶段执行和任务交织。例如岛屿管理不是只运行一次,TGS 的求解与状态推进也不是完全分离的。
三、逐步解析:每一步做什么、为什么需要它
第 0 步:游戏逻辑提交输入
作用
告诉物理系统:下一步有哪些外部变化需要考虑。
主要职责
- 添加力、力矩或冲量;
- 设置运动学刚体目标;
- 创建、移除物理对象;
- 修改碰撞规则;
- 更新关节驱动目标;
- 必要时唤醒刚体。
box->addForce(PxVec3(10.0f,0.0f,0.0f));platform->setKinematicTarget(PxTransform(PxVec3(0.0f,2.0f,0.0f)));需要区分
动态刚体: 游戏提交力等输入,物理决定运动。 运动学刚体: 游戏指定运动目标,物理处理它对其他对象的影响。直接修改动态刚体位姿,更接近重新放置或瞬移,不等同于施力运动。
这一阶段主要由游戏引擎的物理适配层负责,而不是 PhysX 自动生成玩法输入。
第 1 步:模拟准备与任务调度
作用
把外部 API 操作转化为本次模拟可以执行的内部工作。
主要职责
- 检查时间步与场景调用状态;
- 处理需要反映到模拟中的对象变化;
- 准备本步使用的临时数据和内存;
- 建立任务及依赖关系;
- 向 CPU Dispatcher 提交任务。
调用入口:
scene->simulate(dt);常见源码阅读方向:
NpScene ↓ Sc::Scene ↓ 底层碰撞与动力学任务输出
可执行的模拟任务图 本步需要更新的对象与内部数据simulate()返回时,模拟可能还未结束,也可能部分工作已经完成。它不是“返回即获得完整结果”的接口。
第 2 步:运动状态准备
这一职责可能分散在不同任务中,并非必然是一个独立阶段。
作用
计算本步运动所需的基础状态。
主要职责
对于动态刚体:
- 考虑重力;
- 考虑累计的外力和力矩;
- 处理阻尼;
- 应用相应的速度限制。
简化的线速度更新:
[
\mathbf{v}^{*}
\mathbf{v}_{t}
+
\left(\mathbf{g}+\frac{\mathbf{F}}{m}\right)\Delta t
]
这里的 (\mathbf{v}^{*}) 是尚未完成接触和关节约束修正的速度。
对于运动学刚体:
- 根据当前位姿和运动目标准备运动状态;
- 形成接触处理需要的相对运动信息。
输出
供碰撞响应和求解使用的运动数据外力更新只描述“物体自由运动时会怎样”,不负责阻止穿墙或维持关节。
第 3 步:更新形状边界
作用
用便宜的空间表示包住复杂碰撞形状,为后续筛选提供依据。
主要职责
根据:
Actor 世界位姿 Shape 局部位姿 Geometry 几何描述更新形状的世界空间边界,通常是 AABB:
Axis-Aligned Bounding Box 轴对齐包围盒概念上:
旋转后的箱子 ↓ 包住它的世界空间 AABB边界还可能根据接触裕量或特定运动处理进行扩展。
输出
新增、变化或需要移除的包围盒数据引擎会利用状态变化信息,避免无差别地重算所有静止对象。
职责边界
这一阶段只回答:
“形状大致占据什么空间?”
不回答是否真正接触。
第 4 步:宽相检测 Broad Phase
作用
从大量形状中,快速筛选出可能发生碰撞的对象对。
如果有 (N) 个形状,直接两两检查需要:
[
\frac{N(N-1)}{2}
]
次配对。宽相检测的目的就是避免这种全量精确检测。
主要职责
- 维护空间结构;
- 处理包围盒变化;
- 找到新增的候选重叠对;
- 找到已经分离的候选对。
例如:
箱子 A 与地面:候选重叠仍然存在 箱子 A 与箱子 B:新增候选重叠 箱子 B 与墙壁:候选重叠消失输出
可能发生交互的形状对及其变化职责边界
宽相不计算:
- 精确接触点;
- 接触法线;
- 碰撞冲量。
源码重点:
source/lowlevelaabb/第 5 步:碰撞过滤与交互关系维护
作用
判断空间上接近的对象,是否应该交互,以及以什么方式交互。
主要职责
结合过滤数据、过滤 Shader/Callback 和对象配置,决定:
- 忽略某对对象;
- 抑制其碰撞处理;
- 建立普通接触交互;
- 建立触发器交互;
- 是否请求接触通知;
- 是否启用某些特殊接触处理。
例如:
子弹与发射者: 忽略。 箱子与地面: 生成接触并求解。 玩家与任务区域: 检测重叠,产生触发事件,不产生阻挡。持续存在的交互还需要维护缓存和生命周期,而不是每一步全部重建。
输出
有效的接触交互 有效的触发器交互 后续处理所需的配对配置检测到碰撞、产生物理响应、收到回调,是三个不同维度的行为。
第 6 步:窄相检测 Narrow Phase
作用
对有效候选对执行精确几何计算。
输入
两个形状的几何与位姿 接触生成参数 上一物理步保留的接触缓存主要职责
根据几何组合选择算法:
球体 vs 球体 胶囊 vs 盒体 凸体 vs 凸体 凸体 vs 三角网格生成或更新:
- 接触位置;
- 接触法线;
- 分离距离;
- 接触流形;
- 后续材质处理等所需的附加信息。
输出
供约束准备使用的接触数据两个重要细节
第一,接触不一定意味着已经穿透。
contactOffset、restOffset等参数会影响接触生成与期望间距。
第二,接触数据通常会被复用。
箱子稳定放在桌面时,相邻两步的接触变化很小。持久接触流形利用这种时间相干性,减少计算并改善稳定性。
职责边界
窄相只负责:
“接触发生在哪里、沿哪个方向、距离如何?”
它不负责最终把箱子弹开。
相关源码:
source/geomutils/ source/lowlevel/第 7 步:岛屿管理与活动对象组织
作用
找出哪些动态对象相互影响,从而组织调度、唤醒与求解。
主要职责
将刚体视为节点,将接触或关节视为连接:
箱子 A —— 箱子 B —— 箱子 C 球体 D —— 球体 E这两组对象可以形成不同的模拟岛屿。
岛屿管理用于:
- 识别相互耦合的系统;
- 组织独立工作;
- 传播唤醒;
- 协调睡眠;
- 为求解准备活动对象集合。
静态地面不会仅因为同时支撑多堆箱子,就像动态连接那样把所有箱子耦合到一起。
输出
需要处理的活动刚体和约束集合 相关连通关系及睡眠、唤醒状态注意:
一个岛屿 ≠ 固定占用一个线程实际任务粒度由批处理与调度策略决定。
第 8 步:约束准备
作用
把几何意义上的接触和关节,转换成求解器能计算的数学约束。
主要职责
结合:
- 质量与逆质量;
- 惯性与逆惯性;
- 接触点相对质心的位置;
- 相对速度;
- 摩擦和恢复参数;
- 误差修正参数;
- 关节限制及驱动配置;
构建约束数据。
接触转化成什么?
例如箱子接触地面:
法向约束: 限制继续向地面内部运动。 摩擦约束: 限制沿地面切向滑动。关节转化成什么?
例如门轴:
限制不允许的平移和旋转 允许绕指定轴转动 根据配置加入限位和驱动输出
求解器刚体数据 约束数据 批处理信息 必要的历史冲量信息接触点是几何事实,约束是对运动的数学限制。两者不能混为一谈。
第 9 步:约束求解
作用
求出接触、摩擦和关节应该施加多大的冲量,使运动尽可能满足约束。
概念上:
[
\Delta \mathbf{v}
M{-1}JT\lambda
]
其中:
- (M^{-1}):广义逆质量;
- (J):约束雅可比;
- (\lambda):约束冲量。
主要职责
- 修正接触法向上的相对运动;
- 计算摩擦响应;
- 在满足条件时处理反弹;
- 维持关节限制;
- 求解驱动与限位;
- 在相互耦合的约束间迭代传播修正。
例如:
修正底层箱子 ↓ 影响中层箱子 ↓ 影响顶层箱子 ↓ 继续迭代调整PhysX 常见求解器包括 PGS 和 TGS。
输出
满足约束程度更高的速度和相关状态 求得的约束冲量为什么只能“尽可能满足”?
实时模拟使用有限时间预算和有限迭代次数。因此:
- 穿透误差不一定完全消失;
- 关节可能存在偏差;
- 不合理的质量比可能导致收敛困难。
源码重点:
source/lowleveldynamics/第 10 步:状态积分与 CCD
作用
根据运动状态推进物体位置和旋转,并处理配置启用的连续碰撞问题。
状态积分
最简单的线性表达:
[
\mathbf{x}_{t+\Delta t}
\mathbf{x}_t+\mathbf{v}\Delta t
]
旋转则根据角速度更新。
实际实现中,求解、误差修正与积分可能交织,尤其不能把 TGS 简化为“求解完后只积分一次”。
CCD 的职责
高速物体可能出现:
本步开始:子弹在墙左侧 本步结束:子弹在墙右侧 两个离散位置都没与墙重叠CCD 用于降低这种穿透风险。
但要区分:
- Sweep-based CCD:包含沿运动路径的检测及相关响应;
- Speculative CCD:通过预测性接触等方式处理。
CCD 不是所有对象都会执行的统一末尾步骤,也不是所有模式都有相同调用路径。
输出
本步更新后的刚体位姿与运动状态 必要的连续碰撞处理结果第 11 步:睡眠判定与内部结果整理
作用
减少后续步骤中的无效计算,并整理本步结果。
主要职责
- 更新睡眠相关计数与状态;
- 判断稳定对象是否可休眠;
- 整理刚体状态及约束结果;
- 更新交互生命周期;
- 准备需要交付的事件。
休眠对象通常仍保留在场景里,并不是被删除。它们可以继续被查询,并在满足条件时唤醒。
为什么重要?
一万个正在运动和碰撞的刚体与:
一万个大部分已休眠的刚体计算成本可能相差很大。
这也是为什么评估场景性能时,不能只看刚体总数,还要看活动刚体、接触和约束数量。
第 12 步:fetchResults()获取结果与事件
作用
完成模拟结果获取,让应用安全地进入后续处理。
scene->fetchResults(true);其中true表示必要时等待模拟完成。
主要职责
- 等待相关模拟任务完成;
- 完成结果获取相关内部工作;
- 在相应流程中交付常规模拟事件;
- 完成本步调用周期。
随后应用可以读取:
PxTransform pose=body->getGlobalPose();PxVec3 velocity=body->getLinearVelocity();常见事件
- 接触开始或结束;
- 配置请求的持续接触通知;
- 触发器进入或离开;
- 睡眠或唤醒;
- 约束断裂。
不是所有回调都在fetchResults()内发生。接触修改等回调可能发生在模拟任务执行过程中,其线程和访问限制应单独遵守。
另外:
fetchResults(true)耗时长,不代表所有计算都在这个函数里发生;它可能主要是在等待未完成的模拟任务。
四、哪些工作不是 PhysX 这一步自动负责的?
模拟完成后,通常还需要游戏引擎执行:
读取物理结果 ↓ 更新 ECS / 游戏对象 Transform ↓ 消费碰撞与触发事件 ↓ 处理动画与物理控制权 ↓ 执行渲染插值这些不是 PhysX 自动完成的业务工作。
同样:
scene->raycast(...);scene->sweep(...);scene->overlap(...);是应用发起的场景查询,不是每次模拟必然执行的三个固定步骤。不过,查询加速结构的维护可能与场景更新流程相关。
五、职责速查表
| 阶段 | 核心问题 | 主要输出 |
|---|---|---|
| 输入提交 | 游戏希望怎样影响物理世界? | 力、目标、对象变化 |
| 模拟准备 | 本步执行哪些工作? | 任务图、内部准备数据 |
| 运动准备 | 没有约束时会怎样运动? | 候选运动状态 |
| 边界更新 | 形状大致占据哪里? | 世界空间边界 |
| 宽相检测 | 哪些形状可能相交? | 候选对 |
| 过滤与交互 | 哪些候选对应该处理? | 有效交互 |
| 窄相检测 | 接触在哪里、方向是什么? | 接触数据 |
| 岛屿管理 | 哪些对象相互影响? | 活动对象与连通关系 |
| 约束准备 | 接触和关节如何限制运动? | 求解器约束 |
| 约束求解 | 应施加多大冲量? | 修正后的运动状态 |
| 积分与 CCD | 下一时刻在哪里,是否越过障碍? | 新位姿及连续碰撞结果 |
| 睡眠与整理 | 哪些对象可停止活跃计算? | 活动状态与结果数据 |
| 结果获取 | 怎样将结果交付应用? | 可读取状态和事件 |
最核心的理解
PhysX 一步模拟不是简单的“碰撞检测后把物体推开”,而是:
先用空间算法找交互,再用几何算法生成接触,然后用约束求解统一处理接触、摩擦和关节,最后推进状态并交付结果。
源码阅读时,最重要的分界线就是:
碰撞检测:发生了什么接触? 约束准备:这个接触如何限制运动? 约束求解:需要多大的运动修正? 状态积分:下一时刻的物体在哪里?