本节摘要:盒子压在盒子上不是一个点,而是一个接触面。流形生成把参考面和入射面裁剪出最多四个点,每个点带穿透深度。跨帧缓存这些点并热启动冲量,摩擦才有锚,PGS 才收敛得快。每帧扔掉重建,堆叠会抖,箱子会在静止时自己散步。
阅读完本节,你应当能够:
把箱子放在地板上,若只有中心一个接触点,箱子会绕该点摇,像踩高跷。四个角各一个点,才构成静定支撑。二维多边形对多边形通常最多 2 个点,三维盒子对平面最多 4 个。流形就是“这一对形状当前的接触点集合”,有上限,有特征编号。
特征编号:盒子的面、边、顶点有稳定 id。接触点用“A 的特征 × B 的特征”命名。下一帧还是这两个特征挨着,就认为是同一个点,继承上一帧的法向冲量、切向冲量。这叫热启动。没有它,求解器每帧从零猜接触力,堆叠要很多迭代才压实,看起来像橡皮。
Manifold: pairId normal points[4]: localA, localB featureId impulseN, impulseT penetration
点存在局部坐标里,随刚体变换到世界,比存世界坐标抗积分漂移。穿透每帧重算:世界点沿法向的分离距离。法向通常整帧对流形共用一个,来自窄相的最小分离轴。点各自深度可以不同,角上已经离开的点深度为负,应剔除。
| 策略 | 静止堆叠 | 滑动摩擦 | 实现量 |
|---|---|---|---|
| 每帧一个接触点 | 差,会摇 | 差 | 小 |
| 每帧重建多点无缓存 | 中,易抖 | 摩擦无记忆 | 中 |
| 多点加特征缓存热启动 | 好 | 静摩擦能抓住 | 大 |
| 只用距离约束无摩擦锚 | 尚可 | 滑 | 中 |
产品引擎走第三行。作业可以第二行先看堆叠,但要知道抖动来自何处。
SAT 给出法向后,选法向主要朝向的那一侧的面为参考面(面积大、更对齐法向的那个物体上的面)。另一物体上最朝向参考面的面是入射面。把入射面的顶点裁进参考面的四条边,再投影到参考面平面,去掉深度为负(已分离)的点,若超过 4 个,按深度或面积启发式留 4 个。这是 Sutherland–Hodgman 裁剪在接触生成上的应用。
球对什么都是 1 个点,不用裁剪。胶囊对平面 1 个点。凸包对凸包通用裁剪更烦,许多引擎对凸包用“GJK 最近点 + 若穿透则 EPA 再采样相邻面”。第一版刚体引擎:球、胶囊、盒、平面四种,盒对盒裁剪写对,已经能堆金字塔。
匹配算法:对新点,找旧点中 featureId 相同者;找不到再找世界位置距离小于阈值者;再找不到当新点,冲量清零。旧点本帧没被匹配到就丢弃。阈值约 2 到 5 毫米。太大,滑动时锚点粘在原地,物体被无形绳拉住;太小,每帧都当新点,热启动失效。
💡 关键直觉:静摩擦需要一个“锚在切向平面上的记忆”。记忆就是缓存的切向冲量和接触点局部坐标。没有记忆,库仑锥每帧重启,微观滑动被当成动摩擦,堆叠会慢慢挪。
求解器侧:每个点一条法向约束、两条切向约束(三维)。热启动把 lambda 初值设为上一帧冲量。PGS 会在此附近迭代,静止接触几乎一轮就满足。这是缓存值得写的原因——不只是为了摩擦观感,也是为了性能。
法向能不能每帧变?可以小变,突变要当新流形。若 SAT 这帧选了另一条几乎同样浅的轴,法向翻 90 度,旧冲量全无意义,应重置。用点积判断:新旧法向点积小于 0.7 左右就重置流形。滞后可以防止轴在两条候选间振荡。

接触打破阈值:深度小于负的皮肤半径就移除。不要用“深度<0 立刻删”,否则接触在零附近闪烁,热启动反复清零。迟滞:进入接触用 0,离开用 -skin。触发器同理要迟滞,否则事件每帧进进出出。
⚠️ 常见坑:接触点存在世界坐标,物体一转,点留在空中,摩擦把物体拧成麻花。存局部坐标,每帧用当前变换刷新世界位置再算深度。
多物体:每个 pair 一个流形,不要把所有点塞一张全球表还丢了 pair 信息。宽相不再报告某对时,流形延迟一两帧再删,防止接触在阈值附近闪断。延迟删除要有上限,否则物体飞走后还被幽灵点拉着。
调试可视化:每个接触点画小十字,法向画短线,线长度正比于法向冲量。静止堆叠的线应几乎恒定;若在闪,先查流形匹配,再查求解器。这是 7.1 的预告,但 3.3 实现时就要把冲量写回流形,否则后面没东西可画。
2D 简化:边对边最多两点,裁剪变成区间重叠。先写 2D 流形,测试金字塔,再上 3D。3D 裁剪 bug 极难看,2D 里点的增减一目了然。动手优先在这里非常具体:用十个 2D 盒子堆起来睡住,才配写 3D 裁剪。
缩放再次强调:局部接触点乘了带缩放的变换,深度会错。碰撞体正交矩阵加平移,缩放烘焙。流形局部点定义在无缩放的刚体空间。
传感器流形可以不缓存冲量,但要缓存重叠状态以生成 enter/stay/exit。和物理流形分开存,免得传感器点进求解器。
毕业实验:斜坡上放箱子,摩擦足够时应静止。没有切向热启动时,箱子会一帧一帧往下蠕动。加上缓存,蠕动消失。这比任何公式都能说服你为什么要写这么麻烦的匹配。
点的顺序:求解器不依赖顺序,但可视化依赖。保持特征 id 排序,画面上点不会每帧换颜色。减少“看起来在抖其实只是调试颜色在抖”的误报。
凹包分解后,一对刚体可能有多个凸块对,因而多个流形。这是正常的。求解器按流形点迭代,不必合并。合并反而破坏特征 id。一对刚体 4×4 个凸块最坏 16 个流形,要用上限保护,超出则按深度优先保留。
盒子六个面、十二条边、八个顶点,编号写死。接触特征用“参考面 id + 入射边或顶点 id”打包成 32 位。不要用世界坐标哈希当 id,坐标一抖 id 就变,热启动全失效。多边形凸包用顶点索引对,注意索引顺序按最小编号在前,避免 (1,2) 和 (2,1) 当两个点。
调试颜色按 featureId 哈希,不按数组下标。下标每帧因剔除变化,颜色会闪,你会误判流形不稳。真不稳是十字位置闪,不是颜色闪。把这句话贴在显示器上。
延迟删除:宽相本帧没报告的 pair,寿命减一,减到零才删流形。寿命 2 足够。寿命 10 会让飞走的物体还被幽灵点拉。和接触进入迟滞是两件事,不要合成一个魔法数。
2D 验收金字塔:十层盒子,绘制打开,底层四点(二维是两点)位置在 2 秒后应几乎不动。若底层点在左右边之间跳,裁剪在边端点间抖动,加大特征匹配距离或给点位置加微小滞后。滞后太大摩擦会拖影,用斜坡蠕动实验盯住。
太大,滑动锚点粘住像无形绳;太小,每帧新点,热启动失效,斜坡蠕动。用斜坡静摩擦实验盯这个数,不要凭空拍。
大约 0.7 作起点。太敏感会常重置,热启动没了;太钝会把近 90 度跳仍当同一流形,冲量方向错。滞后可一起用。
不要。合并破坏特征 id。用上限按深度保留。求解器按点迭代,多流形是正常的。
物体一转,点留空中,摩擦把物体拧向幽灵点。局部坐标每帧刷新世界位置再算深度。这是流形第一铁律。
一转拧麻花。存局部。
坐标一抖身份变。用拓扑编号。
颜色闪误报不稳。按特征哈希。
飞走仍被幽灵拉。寿命 2 够。
阈值附近闪断。延迟一两帧。
冲量方向无意义。点积过低重置。
3D bug 极难看。先 2D 睡住。
零力仍可能挡。分表。
可视化是「03-接触流形生成」的一部分而不是插件。接触生成时就把点、法向、深度留下来给绘制。事后从形状重新推断,会和求解器看到的数据不一致,你会修一个并不存在的偏差。 本条对应「03-接触流形生成」第 1 号备忘,和相邻条目不要合并成一句空话,分开验收。
固定时间步是「03-接触流形生成」所有数值实验的尺子。尺子在抖,你无法比较积分器,无法调 beta,无法谈确定性。谁把渲染帧时间直接喂进物理,谁就在用弹性的尺子量钢梁。 本条对应「03-接触流形生成」第 2 号备忘,和相邻条目不要合并成一句空话,分开验收。
「03-接触流形生成」里出现 NaN 要在源头断言:未归一化法向、零长度边当轴、除以过小的有效质量、四元数没归一。把 NaN 传到求解器再查,整场速度都会脏,回溯成本高一个数量级。 本条对应「03-接触流形生成」第 3 号备忘,和相邻条目不要合并成一句空话,分开验收。
和美术、策划交接「03-接触流形生成」参数时,用测试盒而不是形容词。说清楚二十厘米台阶能上、八十厘米不能,比说手感更跟手有用。测试盒还能在引擎升级后当合同跑一遍。 本条对应「03-接触流形生成」第 4 号备忘,和相邻条目不要合并成一句空话,分开验收。
下一章用这些点计算冲量:法向分开、切向摩擦、再把关节当成另一类约束一起迭代。