物理规模化:先缩小每帧工作集,再降低模拟精度
自动关联目录:物理规模化:先缩小每帧工作集,再降低模拟精度
核心判断:海量物理对象的第一优化对象不是 Solver 精度,而是每帧被访问的集合。注册对象、可接触对象、本帧发生接触的对象、活跃刚体、需要发送的对象和需要呈现的对象必须是不同集合;任何降频又必须满足插值 Buffer、事件延迟和权威恢复的下游约束。
一、业务冲突:对象很多不等于每个对象每帧都有工作
可编辑场景允许大量可碰撞物体,其中多数长期 Anchored、Sleeping、无接触或远离玩家;只有少量对象在当前帧发生接触、受力、切权或需要同步。若每个对象挂一个 Tick,即使函数开头立即返回,也要承担 UE TickFunction 调度、对象遍历、WeakPtr 检查和 Insights 噪声。DS 没有渲染成本,这类固定开销反而更突出。
接触事件还有另一种放大:Contact Modify 位于物理线程高频路径,不能为每个 Pair 扫 Actor 或跨线程访问复杂 UObject;Game Thread 又需要在 PostPhysics 形成 Touched/TouchEnded 的稳定帧结果。把所有注册物体都扫描一遍虽简单,却让成本与场景总量相关,而不是与真实接触量相关。
网络降频也并非越低越好。工程中对近静止物体降低发送率后,单个对象的插值 Buffer 被抽空;客户端又使用共享 Playout Clock,低 Fill 对象把全局播放速率拉低,最终连正常移动物体都出现节奏变化。局部看似省带宽的策略,破坏了共享下游的最小供给约束。
二、实际实现:活跃集、事件工作集、批处理与有界诊断
物理行为不再由每个 Component 独立 Tick,而由 World Subsystem 在 TG_PostPhysics 集中调度。注册表保存“具备 CanTouch 能力”的 Active Set,但每帧真正遍历的是 Pending Set:物理线程把软接触写入 World 级缓冲,拖拽隔离等来源也只提交受影响对象;PostPhysics 交换快照、构造稳定 Work Buffer,逐个结算接触状态。仍有未结束接触的对象才进入下一帧 Pending。Marker 丢失则由物理线程写 SPSC 队列,Game Thread 按事件修复。
Contact Modify 用 shape filter 中保留的组件身份做 O(1) 注册表查找,读多写少路径使用 FRWLock。回调内部有 Batch 与 Pair 级 Insights Scope、路径计数和接触对突增警告,可以区分 marker miss、普通路径与快速路径。共享碰撞 Cook、根碰撞代理和 Compound 又减少重复 Cook 与重复 Component,但没有假设它们会自动降低 shape/contact 数。
物理复制只遍历当前 Authority 需要输出的 Active Set,按对象更新率节流,Initial 与 Releasing 绕过节流;样本按固定数量分批。近静止判断同时观察线速度、角速度和自上次发送后的位移。曾经的简单“发送间隔翻倍”被修正为:降频后的最大间隔不能超过插值延迟除以目标 Buffer Fill,从消费端反推生产下限。约束岛只有所有成员近静止时才整体降频。
诊断链路记录 ingress、decode、queue、dispatch、receive、playout、apply 与 outcome,并保留 World、Driver、Connection、协议、方向、类别、字节、数量、原因和 Age。采集有内存上限、队列高水位、丢失标记和 Seal 条件;性能窗口只统计完整帧区间,记录 Frame Mean/P95/Max、进程 CPU、采样内存峰值和 IO,时钟回退、采样溢出或停止过晚会使结果失败,而不是输出看似完整的报告。
flowchart LR
R[全部注册对象] --> E[具备事件/模拟/复制资格]
E --> A[当前活跃集]
A --> W[本帧事件工作集]
W --> B[批量处理]
B --> M[成本与质量指标]
M -->|超预算| D[可逆降级]
M -->|有余量| U[迟滞升级]
D --> E
U --> E三、值得保留:从总对象遍历转向与事件量相关的成本
Active Set 与 Pending Workset 分离是最直接的规模收益。CanTouch 只是资格,不意味着每帧有事件;本帧真正接触或仍需结算的对象才进入 Work Buffer。交换 Buffer 后再遍历,也避免回调过程中修改集合使迭代失效。集中 Tick 让 UE 调度从 N 个 TickFunction 变成一个,并能在 Insights 中看到一次完整成本。
跨线程设计也较稳健。物理线程只做 ID 查找、数值读取和入队,不直接修改 UObject 或 shape;Game Thread 在 PostPhysics 处理生命周期与事件。World 级注册表与 PxScene 作用域一致,销毁时统一清理,WeakPtr 失效对象从活跃集移除。它没有通过“锁住整个世界”换安全,而是缩短了物理回调中的临界区。
静止降频修复体现了更重要的方法:优化参数不能在生产者局部决定,必须从消费者最小 Buffer Fill 反推。Initial/Releasing 必发、岛级统一静止判断和固定重广播节奏也都在保护时序边界。性能窗口对不完整测量直接判失败,则避免拿缺帧、时钟跳变或存储截断的数据证明优化成功。
四、结构性缺陷:已有工作集优化,还没有统一物理预算系统
当前 Active Set 分散在接触、服务器复制、客户端观察、Owner 上行、Constraint Dirty 与岛级组装等子系统中。它们分别优化了局部循环,但没有共同的 Significance 与 Budget Frame;同一对象可能在接触层被视为闲置,在网络层仍高频发送,或在远端不可见时仍维持高精度碰撞和 Constraint 更新。
休眠主要依赖物理后端自然状态,尚未成为上层调度契约。Sleeping Body 是否仍需要 Touched、查询、Owner 心跳、低频 Keyframe 和唤醒监视,应由对象能力决定。简单把远处 Body 关掉会破坏射线、机关、投射物和 DS 权威;不区分模拟碰撞与 Query Collision,又无法单独降低 Solver 成本。
更大的缺口是没有把 Body、Shape、活跃 Contact Pair、Constraint、Query、Cook、快照成员和网络字节放进统一预算。碰撞 LOD、静态转换、流式移出和约束降频仍是目标设计,不应写成已经实现。现有诊断擅长网络生命周期与进程级性能,但还缺少“哪个对象因什么原因处于哪一层、占多少 shape/pair/constraint/send budget”的实时视图。
五、目标设计:能力底线之上做多维 Fidelity Tier
先为对象声明不可牺牲能力:是否影响角色阻挡、是否需要 DS Query、是否会被投射物命中、是否产生脚本接触、是否是活跃约束岛、是否可切权。Significance 只在这些底线上选择成本最低的 Tier,而不是单纯按距离关物理。一个岛的 Tier 由最重要成员和 Constraint 类型共同决定,不能成员各降各的。
void SchedulePhysics(const WorldFacts& facts, BudgetFrame& budget)
{
for (SimulationUnit& unit : BuildUnits(facts)) { // 独立 Body 或 Constraint 岛
RequiredCapabilities keep = GatherHardRequirements(unit);
Significance score = EvaluatePlayersQueriesAndActivity(unit, facts);
FidelityTier tier = ChooseTierWithHysteresis(keep, score, budget);
ApplyCollisionTier(unit, tier); // simulation/query shape 分开
ApplySolverTier(unit, tier); // substep、迭代、休眠与静态化
ApplyEventTier(unit, tier); // 精确、聚合或禁用的明确契约
ApplyReplicationTier(unit, tier); // 频率、Keyframe、量化
RecordDecision(unit, tier, keep, budget);
}
}降级必须可逆并有滞回。远处 Anchored 装饰可保留简单 Query Shape、移除 Simulation Shape;Sleeping 交互物保留唤醒触发和低频 Keyframe;活跃约束岛即使远,也不能成员分层。升档先准备高精度 Artifact 与快照 Baseline,再原子切换,不能在一帧内让碰撞和表现使用不同 Bounds。
六、决策规则:优化前后同时看成本、供给与物理质量
| 对象状态 | 可采用的层级 | 不能破坏的约束 |
|---|---|---|
| 近景活跃交互/战斗 | 完整模拟、接触事件、高频快照 | Authority、命中、手感和约束完整性 |
| Sleeping 可交互物 | 后端休眠、事件驱动唤醒、低频 Keyframe | 查询、唤醒条件、Owner 心跳 |
| 远处 Anchored 装饰 | 简单 Query Shape 或流式移出 | DS 必需查询与角色阻挡按内容声明保留 |
| 活跃 Constraint 岛 | 整岛统一 Solver/复制 Tier | 不允许成员独立降级或分帧 |
| CanTouch 但本帧无接触 | 保留资格,不进入本帧 Workset | Begin/End 事件不能漏,生命周期可清理 |
| 近静止复制对象 | 在 Buffer 安全下限以上降频 | Initial/Releasing 必发,共享时钟不被饿死 |
| 采集与诊断 | 有界内存、结构化阶段与原因 | 截断、时钟异常、缺失能力必须显式失败 |
验证指标应至少同时包含:注册/活跃/工作集数量,Body/Shape/Constraint/Pair/Query 数,Physics GT 与 Solver 时间,Wake/Sleep 与 Body Recreate,快照字节、Buffer Fill、外推和冻结时长,以及 Tier 升降级原因。只看平均帧率或总带宽会掩盖尖峰与错误降级。
这段实践已经证明两个可迁移原则:成本应尽量与“本帧真实工作”相关,降频应由消费链的最低供给反推。下一步不是再加几个距离阈值,而是把分散 Active Set 统一到 Simulation Unit、能力契约与预算控制器,并让每次降级都能解释节省了什么、牺牲了什么、由什么证据恢复。