本节摘要:质点弹簧加显式积分会在刚度上升时爆炸。位置基动力学把“边长应等于 L”直接投影到顶点位置上,XPBD 再用柔度参数让刚度不跟迭代次数绑死。软体还要体积约束防塌缩、碰撞用粒子对刚体的单向接触。先写一根边的投影,再组网格。
阅读完本节,你应当能够:
质点弹簧:F = -k * (length - rest)。k 一大,频率高,第 2 章的显式积分立刻炸。隐式积分能吃,但要组矩阵。游戏果冻、肉块、软垫,要的是“捏得动、不炸、60Hz”,不是杨氏模量准确。位置投影更听话:把两个点沿连线拉开或推近到目标长度,按质量倒数分配位移。一次投影就是一次约束满足,刚度用投影次数和柔度调。
function projectDistance(p1, p2, w1, w2, rest, compliance, dt): d = p2 - p1 len = length(d) if len == 0: return C = len - rest alpha = compliance / dt^2 dLambda = -C / (w1 + w2 + alpha) n = d / len p1 -= n * dLambda * w1 p2 += n * dLambda * w2
w 是质量倒数。静态锚点 w=0,只动另一端。这就是 XPBD 距离约束的核心。compliance=0 时尽量硬,迭代越多越接近不可伸长;compliance>0 时即使迭代很多也保持可伸长,这是相对经典 PBD 的关键改进——经典 PBD 的 k 乘在位置修正上,迭代 20 次和 5 次“看起来的杨氏模量”完全不同,美术无法调参。
| 方法 | 刚度含义 | 大刚度 | 碰撞 |
|---|---|---|---|
| 质点弹簧显式 | k 牛顿/米 | 炸 | 难 |
| 质点弹簧隐式 | k | 解系统 | 可 |
| 经典 PBD | 迭代次数 | 越迭代越硬 | 位置接触 |
| XPBD | 柔度 compliance | 不炸,偏软 | 位置接触 |
| FEM 有限元 | 本构 | 准,贵 | 接触复杂 |
游戏软体用 XPBD。医疗仿真才上 FEM。不要用错场。
只有边约束的四面体网格会压扁:体积变成薄饼,边长仍对。加体积约束:四面体体积与静止体积之差投影到四个顶点。布料用弯曲约束(二面角)而不是体积。自碰撞:顶点对表面或边对边,宽相用网格。自碰撞漏了,软体会自己穿过自己打结。

循环:
x_pred = x + v*dt + g*dt*dt 重复 n 次: 投影所有距离、体积、碰撞 v = (x - x_prev) / dt x_prev = x 或按 Verlet 存储约定
碰撞投影:若顶点进入刚体,沿接触法向推到表面。刚体若动态,还要把冲量还回去,否则软体推不动箱子。还回的冲量用顶点质量与位移 / dt 当速度变化,施加到刚体接触点。单向(只推软体)会导致软体被运动平台吞没。
💡 关键直觉:PBD 把“解微分方程”换成“每帧把形状掰回可接受集合”。迭代是掰的次数,柔度是允许偏离多少。
阻尼:在速度上乘 0.99 会把整体平移也刹住,果冻在空中减速,像有空气但过强。更好的是只阻尼相对运动:约束投影后的相对速度,或用 XPBD 的阻尼项。整体刚体模式(形状不变的平移旋转)应尽量保留,否则扔出去的肉块会在空中停住。
四面体要尽量均匀。退化四面体体积约束会 NaN。美术给三角面,运行时四面体化,或用体素生成。不要对一个三角形面片网格只加边约束就称为软体,那是布料,没有体积,侧向一压就没厚度。
⚠️ 常见坑:预测阶段用了半隐式刚体积分,投影阶段又把顶点当 Verlet,两套时间积分抢位置。软体顶点不要进刚体表。
与刚体接触的皮肤半径同样适用。顶点数量多,宽相用网格。不要每个顶点对每个刚体 SAT。
睡眠:整块软体动能低于阈,停投影。被刚体撞要整块醒。部分顶点睡、部分动,会撕开网格。
测试:悬吊一块四面体立方,应下垂而不无限拉长(compliance 给一个有限伸长);放地上应略扁再回弹或按材料坐下;戳一下应振荡衰减。振荡不衰减就加点约束阻尼;完全不弹就 compliance 太大或迭代太少。
性能:约束可按颜色分组并行,相邻边不要同色。第 6 章再做。现在先把串行投影写对。过早并行会掩盖投影顺序导致的不对称——PBD 本身顺序相关,这是它的原罪,XPBD 减轻但不消灭。需要完全顺序无关,得上全局求解,又回到矩阵。
不要把一千个刚体盒子焊成软体。那是错误抽象:自由度是 6N 而不是顶点数,接触数量爆炸。软就是顶点云加约束。
把 compliance 暴露成“拉伸硬度、体积硬度”滑条,内部转换成每约束 alpha。不要暴露迭代次数当硬度——那正是 XPBD 要避免的。迭代次数当质量/性能档:低档 4 次,高档 10 次,硬度应大致不变,只是更跟手或更抖。若低档明显变软,你还在用经典 PBD 的 k 乘修正,没把 lambda 累加做对。
碰撞还回刚体:顶点位移 delta,速度增量 delta/dt,冲量 j = m * dv,施加到刚体接触点。符号使刚体被推离软体。漏乘质量,轻软体会推动重卡车。打印两边动量变化,应近似相反。
不要在投影循环里删除顶点。撕裂在 step 末尾改拓扑,下一帧重建约束列表。循环中途改拓扑,下标全乱。第一版不撕裂,只把超伸长边画成红色警告。
颜色分组并行前,用串行两次不同约束顺序对比最终形状,差异应可接受。差异巨大说明约束冲突强,先加迭代或改网格,不要先并行把差异变成竞态。
经典的硬度绑迭代次数,低档变软。XPBD 用柔度,迭代不够时偏软但不跟次数死绑。美术滑条应调柔度。若低档明显变软,lambda 累加没做对。
体积可以变成薄片而边长仍对。四面体要体积约束。三角面片只有布,没有厚度,别叫软体。
抛出的肉块会在空中停住。只阻尼相对变形,保留质心运动。扔出去应能飞。
加迭代、改善网格、加体积约束。不要先并行。并行会把顺序差异变成帧间闪烁。
打印软体与刚体动量变化,应近似相反。漏乘质量,轻软体会推走卡车。
还在用经典 PBD。检查 lambda 累加。
体积约束 NaN。网格质量是参数。
下标全乱。步末改。
软体被吞。动态刚体要还冲量。
接触爆炸。软就是顶点云。
并行后不对称当竞态查。先接受 PBD 顺序相关。
美术无法调。暴露拉伸硬度和体积硬度。
空中停住。只阻尼相对变形。
「01-软体与PBD形变」的默认值要进协议版本。改默认迭代次数、默认 dt、默认皮肤半径,旧回放会分叉。版本头拒绝错版本,比静默错乱重放更负责任。 本条对应「01-软体与PBD形变」第 1 号备忘,和相邻条目不要合并成一句空话,分开验收。
先写 2D 再写 3D 适用于「01-软体与PBD形变」里所有几何麻烦事。二维金字塔、二维绳、二维铰链能看见点的增减。三维裁剪 bug 极难看,会让你怀疑人生。二维过了再加一根轴。 本条对应「01-软体与PBD形变」第 2 号备忘,和相邻条目不要合并成一句空话,分开验收。
「01-软体与PBD形变」不要在热路径调用脚本。求解器迭代十次回调十次,缓存和确定性一起死。标记事件,步末再发。这是布局问题也是架构问题。 本条对应「01-软体与PBD形变」第 3 号备忘,和相邻条目不要合并成一句空话,分开验收。
对账数字比观感先行。静止支撑冲量是否接近质量乘重力乘 dt,自由落体一秒是否接近四点九米,两球对心是否交换速度。「01-软体与PBD形变」相关实现先过对账,再谈好看。 本条对应「01-软体与PBD形变」第 4 号备忘,和相邻条目不要合并成一句空话,分开验收。
「01-软体与PBD形变」出抖动时按清单:时间步是否固定、流形点是否闪、法向是否跳、恢复系数是否在静止时仍生效、bias 是否过大、迭代是否其实在清零热启动。不要先把摩擦系数调到十。 本条对应「01-软体与PBD形变」第 5 号备忘,和相邻条目不要合并成一句空话,分开验收。
空间结构必须在世界系。把「01-软体与PBD形变」绑到摄像机或屏幕格子,逻辑和确定性都会在转视角时崩。渲染剔除不能代替宽相,看不见的物体仍能被撞到。 本条对应「01-软体与PBD形变」第 6 号备忘,和相邻条目不要合并成一句空话,分开验收。
「01-软体与PBD形变」的预分配策略:按关卡上限留容量,超出打日志并降级,不要在 step 中途默默扩容。扩容是卡顿尖峰,也是回放时分配顺序不同导致的潜在分叉源。 本条对应「01-软体与PBD形变」第 7 号备忘,和相邻条目不要合并成一句空话,分开验收。
把「01-软体与PBD形变」相关的失败做成最小复现:关掉渲染特效、关掉音频、只留固定 dt 的 step 和一份输入日志。能在十秒内重放出来的 bug 才叫被抓住。不能重放就先补录制,再谈修。很多人在这一步省时间,后面用几天陪着偶发抖动。 本条对应「01-软体与PBD形变」第 8 号备忘,和相邻条目不要合并成一句空话,分开验收。
在「01-软体与PBD形变」路径上加计数器要进 step 末尾快照,不要在热循环里格式化字符串。计数器本身若分配内存,你会优化一个被测量污染的世界。发布版可用宏剥掉绘制,但计数器的定义要保留,方便线上开一个极轻的统计开关。 本条对应「01-软体与PBD形变」第 9 号备忘,和相邻条目不要合并成一句空话,分开验收。
下一节换粒子:没有网格拓扑时,用邻居估计密度,才能做出能流动的东西。