本节摘要:用接触和关节当边、刚体当点,连通分量就是岛。岛内约束耦合,岛间无共享未知量,求解可并行。睡着的岛整岛跳过。线程按岛调度,接触数组按岛切片避免假共享。静态世界不进岛。唤醒沿边传播。先单线程分岛跳过睡眠,再开线程,否则你无法分辨是并行 bug 还是分岛 bug。
阅读完本节,你应当能够:
动态刚体是节点。每条接触、每条关节是无向边。静态体不是节点,但接触仍把动态体连到“地面”——地面不把不同动态体连在一起,除非你错误地把静态体当节点且所有静体是同一个。静体当同一个超级节点会把全图并成一座岛,分岛失败。正确:边只连两个动态体;与静体的接触不产生边,只挂在该动态体所属岛的约束列表里。
关节两端都是动态,必有边。运动学体:可当动态节点但不睡眠,或当移动的静体。电梯应唤醒站在上面的岛,所以电梯与乘客之间的接触要能唤醒,即使电梯自己不睡。
islands = connectedComponents(dynamicBodies, edgesFromContactsAndJoints) for each island: if allSleeping(island): skip solve(island.contacts, island.joints)
并查集在接触生成后扫一遍即可。物体几千,便宜。
| 规则 | 原因 |
|---|---|
| 静体不作为连通节点 | 否则全场景一座岛 |
| 关节必连边 | 否则链条一端动另一端睡 |
| 触发器接触不连边 | 否则传感器把无关物体焊在一岛 |
| 布料顶点可单独成岛系统 | 不要和刚体岛混用同一并查集 |

岛内所有动态体速度低于阈连续 N 帧,整岛睡:跳过积分和求解,宽相可简化为只对醒着的邻居。唤醒:新接触来自醒着的物体、关节电机启动、脚本施力、爆炸查询。唤醒必须沿关节边传播,否则铰链门一边醒一边睡,轴被撕开。
阈值要带滞回:睡的阈更低,以免边界振荡。爆炸用球体查询把区域内岛全部醒,不要指望接触下一帧才生成——睡着的宽相可能已经把它们摘出去了,要主动查。
💡 关键直觉:睡眠是最大的性能优化,比 SIMD 还大。开放世界里 90% 箱子在睡。错唤醒会把 CPU 打满,错不唤醒会穿模。
并行:每个岛一个任务。小岛合并成任务包,避免十万座单物体岛调度爆炸。单物体无接触岛积分很便宜,可以打包。大岛(倒塌楼房)单线程解,或岛内再着色分约束,那是进阶,第一版大岛单线程。
线程完成顺序若影响 PGS 迭代顺序,结果会变。要确定性:岛内接触排序按 id 固定;岛之间本就不耦合,并行无妨。岛内再并行约束则必须固定颜色顺序。联机游戏:要么物理单线程,要么严格固定排序加定点数。7.2 会把这当成硬约束。
⚠️ 常见坑:多线程写同一刚体速度——发生在分岛错误,两边接触都指向同一刚体却分到两岛。单元测试:随机接触图,断言同一刚体只属于一岛。
宽相并行:按空间格子分,格子边界对要规则去重。比分岛更容易写错重复对。可以先串行宽相,并行只求解。宽相往往不是最热点。
双缓冲状态:求解写当前速度,渲染读上一帧快照。不要在求解未完成时插值。任务图:宽相 → 窄相(可并行对)→ 建岛 → 并行求解 → 写位置 → 睡眠判定。窄相对之间无写冲突如果接触输出槽预先分配。
测试:一千个分离的箱子应形成一千岛并很快全睡,CPU 接近空闲;推倒一个不应唤醒全图。用计数器显示“醒着的岛 / 总岛”。这是 7.1 要画的数字,6.2 就要打点。
关节跨很远两个堆(一根长杆),两堆变一岛。这是正确的。若不想,就不要用硬杆连它们。设计关卡时一根钢筋会让睡眠失效,是玩法问题也是性能问题。
线程数不超过物理核。超线程对这种不规则 gather 循环帮助有限。绑核可减轻迁移,通常不必第一步就做。
任务图固定:更新 AABB → 宽相(可并行格子)→ 窄相写到预分配槽 → 建岛 → 并行求解岛 → 写位置 → 睡眠。窄相槽按下标写,不要 push_back。push 要锁,锁把并行吃掉。预分配 maxContacts,超了丢最浅的并打日志。
断言:每个动态体 islandId 唯一;接触两端若都是动态则 islandId 相同;关节两端相同。随机生成接触图跑一万次。失败就是分岛 bug,不要用“偶发抖动”蒙混。
调度:小于 8 个接触的岛打包到一个任务。大岛超过 512 接触单线程解,避免岛内再并行把确定性搞崩。线程数取物理核减一,留一核给渲染,或与引擎任务系统共享池,不要自己再创一套池抢核。
唤醒洪水:爆炸查询半径过大,全图醒来,帧时间炸。用层掩码限制可唤醒类型,碎片层可以不睡也可以睡得更狠提前删。开放世界爆炸是睡眠系统的压力测试,必须在大场景测,不要只在五只箱子的盒子里测睡眠。
不要。十万任务调度会爆炸。打包小岛。大岛单线程。调度策略比线程数先定。
查询半径和层掩码。碎片可提前删。必须在大场景测,五只箱子测不出洪水。
可以按格,边界去重要小心。第一版串行宽相、并行求解。宽相往往不是最热点。先对的并行比全面并行重要。
随机图加断言。失败就是分岛 bug。不要等“偶发抖动”才查。这是单元测试,不是玩法测试。
岛间无耦合,并行无妨。岛内顺序按 id 固定。岛内再并行约束必须着色且顺序固定。联机写进协议。
并行被吃。预分配槽按下标写。
静默丢接触难查。丢最浅的并记录。
确定性崩。第一版大岛单线程。
不规则 gather 帮助有限。核减一留给渲染。
站上去还睡。运动学要能唤醒。
传感器焊无关物体成一岛。
链条一端动一端睡轴撕开。
测不出唤醒洪水。必须大场景。
睡眠和唤醒属于「02-多线程岛分割」正确性,不只是性能。该醒不醒会穿模,不该醒全醒会卡顿。爆炸查询、关节边、运动学平台都是唤醒图的边。漏一条边,现场像鬼。 本条对应「02-多线程岛分割」第 1 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-多线程岛分割」的默认值要进协议版本。改默认迭代次数、默认 dt、默认皮肤半径,旧回放会分叉。版本头拒绝错版本,比静默错乱重放更负责任。 本条对应「02-多线程岛分割」第 2 号备忘,和相邻条目不要合并成一句空话,分开验收。
先写 2D 再写 3D 适用于「02-多线程岛分割」里所有几何麻烦事。二维金字塔、二维绳、二维铰链能看见点的增减。三维裁剪 bug 极难看,会让你怀疑人生。二维过了再加一根轴。 本条对应「02-多线程岛分割」第 3 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-多线程岛分割」不要在热路径调用脚本。求解器迭代十次回调十次,缓存和确定性一起死。标记事件,步末再发。这是布局问题也是架构问题。 本条对应「02-多线程岛分割」第 4 号备忘,和相邻条目不要合并成一句空话,分开验收。
对账数字比观感先行。静止支撑冲量是否接近质量乘重力乘 dt,自由落体一秒是否接近四点九米,两球对心是否交换速度。「02-多线程岛分割」相关实现先过对账,再谈好看。 本条对应「02-多线程岛分割」第 5 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-多线程岛分割」出抖动时按清单:时间步是否固定、流形点是否闪、法向是否跳、恢复系数是否在静止时仍生效、bias 是否过大、迭代是否其实在清零热启动。不要先把摩擦系数调到十。 本条对应「02-多线程岛分割」第 6 号备忘,和相邻条目不要合并成一句空话,分开验收。
空间结构必须在世界系。把「02-多线程岛分割」绑到摄像机或屏幕格子,逻辑和确定性都会在转视角时崩。渲染剔除不能代替宽相,看不见的物体仍能被撞到。 本条对应「02-多线程岛分割」第 7 号备忘,和相邻条目不要合并成一句空话,分开验收。
「02-多线程岛分割」的预分配策略:按关卡上限留容量,超出打日志并降级,不要在 step 中途默默扩容。扩容是卡顿尖峰,也是回放时分配顺序不同导致的潜在分叉源。 本条对应「02-多线程岛分割」第 8 号备忘,和相邻条目不要合并成一句空话,分开验收。
把「02-多线程岛分割」相关的失败做成最小复现:关掉渲染特效、关掉音频、只留固定 dt 的 step 和一份输入日志。能在十秒内重放出来的 bug 才叫被抓住。不能重放就先补录制,再谈修。很多人在这一步省时间,后面用几天陪着偶发抖动。 本条对应「02-多线程岛分割」第 9 号备忘,和相邻条目不要合并成一句空话,分开验收。
在「02-多线程岛分割」路径上加计数器要进 step 末尾快照,不要在热循环里格式化字符串。计数器本身若分配内存,你会优化一个被测量污染的世界。发布版可用宏剥掉绘制,但计数器的定义要保留,方便线上开一个极轻的统计开关。 本条对应「02-多线程岛分割」第 10 号备忘,和相邻条目不要合并成一句空话,分开验收。
下一节回到复杂度:宽相和邻居结构才是 N 变大时决定死活的那一层。