从应用层看PhysX 一步模拟通常只有scene-simulate(dt);scene-fetchResults(true);但内部完成的是一个完整的数据处理过程读取当前物理状态和游戏输入发现物体之间的交互将接触和关节转化为约束求解新的运动状态最后交付模拟结果与事件。下面以PhysX 4.x/5.x 的 CPU 刚体模拟为主说明。不同版本、PGS/TGS 求解器、CCD 和 GPU 配置会改变执行细节。以下步骤按照职责和数据依赖划分不是某个版本严格串行的函数调用顺序。一、先明确“物理一步”不等于“渲染一帧”渲染帧的时间通常不固定物理模拟一般使用固定时间步constexprfloatphysicsDt1.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 几何描述更新形状的世界空间边界通常是 AABBAxis-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 posebody-getGlobalPose();PxVec3 velocitybody-getLinearVelocity();常见事件接触开始或结束配置请求的持续接触通知触发器进入或离开睡眠或唤醒约束断裂。不是所有回调都在fetchResults()内发生。接触修改等回调可能发生在模拟任务执行过程中其线程和访问限制应单独遵守。另外fetchResults(true)耗时长不代表所有计算都在这个函数里发生它可能主要是在等待未完成的模拟任务。四、哪些工作不是 PhysX 这一步自动负责的模拟完成后通常还需要游戏引擎执行读取物理结果 ↓ 更新 ECS / 游戏对象 Transform ↓ 消费碰撞与触发事件 ↓ 处理动画与物理控制权 ↓ 执行渲染插值这些不是 PhysX 自动完成的业务工作。同样scene-raycast(...);scene-sweep(...);scene-overlap(...);是应用发起的场景查询不是每次模拟必然执行的三个固定步骤。不过查询加速结构的维护可能与场景更新流程相关。五、职责速查表阶段核心问题主要输出输入提交游戏希望怎样影响物理世界力、目标、对象变化模拟准备本步执行哪些工作任务图、内部准备数据运动准备没有约束时会怎样运动候选运动状态边界更新形状大致占据哪里世界空间边界宽相检测哪些形状可能相交候选对过滤与交互哪些候选对应该处理有效交互窄相检测接触在哪里、方向是什么接触数据岛屿管理哪些对象相互影响活动对象与连通关系约束准备接触和关节如何限制运动求解器约束约束求解应施加多大冲量修正后的运动状态积分与 CCD下一时刻在哪里是否越过障碍新位姿及连续碰撞结果睡眠与整理哪些对象可停止活跃计算活动状态与结果数据结果获取怎样将结果交付应用可读取状态和事件最核心的理解PhysX 一步模拟不是简单的“碰撞检测后把物体推开”而是先用空间算法找交互再用几何算法生成接触然后用约束求解统一处理接触、摩擦和关节最后推进状态并交付结果。源码阅读时最重要的分界线就是碰撞检测发生了什么接触 约束准备这个接触如何限制运动 约束求解需要多大的运动修正 状态积分下一时刻的物体在哪里