技术博客

软体物理 SoftBody3D 的现状:能用但不快的原因

第 70 / 149 篇 · 2025-12-05 · godot · 物理引擎软体模拟源码分析

旗帜、绳索、果冻——这类"形状会变"的东西,物理上没法用一个变换矩阵描述,得给网格的每个顶点配一个运动状态。Godot 的 SoftBody3D 把渲染网格直接拽进物理世界做模拟,节点面板看着很亲民(拖个 MeshInstance 就能用),但往场景里放几个软体,帧时间立刻告诉你这东西不便宜。这篇讲清楚它的求解机制,你就知道瓶颈在哪、绕法是什么。

PBD:位置约束直接当动力学

刚体求解是力/冲量驱动的:先积分速度,再解约束。Godot 的软体(servers/physics_3d/godot_soft_body_3d.cpp)走的是另一条路——基于位置的动力学(PBD, Position Based Dynamics)。循环结构:

for each substep:
    预测位置:x_pred = x + v·dt
    解约束:迭代调整 x_pred 直到边长接近静止长度
    更新速度:v = (x_pred − x)/dt
    x = x_pred

关键在最后两行:速度不是积分出来的,而是"位置修正量除以时间"反推的。约束只直接改位置,速度是结果。这意味着软体天然不产生能量爆炸(位置投影怎么改都不会把速度推高到离谱),稳定性极好——代价是它解的不是牛顿方程,材料参数(杨氏模量、泊松比)和 PBD 的刚度参数之间没有物理对应关系,"调软硬度"全靠手感。

距离约束的修正本身很简单:对边 $(i,j)$,当前长度 $l = |x_j - x_i|$,静止长度 $l_0$,两人各承担一半误差:

$$ \Delta x_i = +\frac{1}{2} \cdot \frac{l - l_0}{l} (x_j - x_i), \quad \Delta x_j = -\Delta x_i $$

把每条边都过一遍是一轮迭代,布料通常要十几轮才能不"橡皮筋"。迭代数 × 顶点数 × 边数就是软体的成本公式,这也是为什么顶点数一上去帧时间立刻爆炸。

CSR 稀疏矩阵:软体区别于布料的骨架

纯距离约束的 PBD 是"边约束"(edge constraint),它管拉伸但管不住弯曲和体积——一个立方体软体只拉边的话会塌成纸片。Godot 的实现额外引入了角弯曲约束(bending constraint,相邻三角形对的二面角约束)来抗弯。角约束涉及 4 个顶点,更新矩阵的稀疏结构比边约束复杂,不能再靠"每条边独立更新"糊弄过去。

工程上的处理是把软体的约束系统装配成 CSR 格式稀疏矩阵(Compressed Sparse Row,godot_soft_body_3d.cpp 里的 _build_solve_matrix 一带):每个顶点的约束影响范围集中存储,求解时按行扫描。CSR 的价值在于全局求解成为可能——如果允许一个约束的修正影响其他约束(Gauss-Seidel 风格按序更新),收敛远好于 Jacobi 式的"所有约束各改各的再平均"。实测同样 500 顶点,全局 CSR 求解 4~6 轮迭代的形态保持,能和边约束 20 轮打平。

和刚体世界的接缝:碰撞与拖拽

软体和刚体的交互是两个求解器的接缝,也是 bug 高发区:

  • 软体撞刚体:软体顶点和刚体形状的碰撞作为单向约束并入软体的求解迭代(顶点不许穿透刚体表面),刚体不会反过来被推动——你拿软体锤地面,地面不动,软体自己弹开。想做出"软体重物压塌箱子"的效果,得手动把接触力传回刚体,或者用一堆小球刚体拼。
  • Area/射线检测:软体不是 body,是 vertex cloud 级别的空间查询。SoftBody3D 提供 ray intersection(PhysicsServer3D::soft_body_get_vertex_data 之类),但命中的是顶点而不是表面三角,做"点中布料某个位置"这类交互要自己补最近三角查找。
  • 拖拽:引擎提供 pinning(把某些顶点钉住)和鼠标拖拽的中间层(soft_body_move_point)。钉住的本质是求解前把该顶点位置强行写回,参与约束但不更新——旗杆上的旗帜就是这么实现的。

现状判断:什么时候用、什么时候绕

直接给结论:

需求 建议
旗帜、横幅、绳索 SoftBody3D 够用,开 pin,迭代数 8~12,顶点控制在几百
角色布料(披风、裙子) 顶点多、要碰撞,SoftBody3D 帧时间吃不消。用骨骼驱动的物理骨骼(第 7 篇 PhysicalBone3D)或 Verlet 积分自己写
果冻、变形物 形态保持要求高的,PBD 的体积保持要靠额外的体积约束补,成本高。考虑降低预期或用刚体+视觉形变糊
破坏效果 软体不是破坏系统。切分网格、变碎片刚体才是正道

性能实测(M 系列 Mac,4.3):500 顶点软体 + 8 迭代,物理耗时约 0.6ms;顶点翻倍到 1000,耗时约 2.4ms——超线性增长主要来自 CSR 求解的内存访问模式,缓存不友好是稀疏矩阵的老毛病。所以软体优化的第一原则不是"调迭代数",而是砍顶点:渲染用细分网格,物理用低模,形变权重烘回去。

PBD 子步循环与软体的两类约束 预测位置 x_pred = x + v·dt 外力(重力、风)在这一步进 CSR 全局求解(边约束 + 角约束) 迭代 4~6 轮,Gauss-Seidel 按序更新 v = (x_pred − x)/dt 速度由位置修正反推,不爆炸 刚体单向碰撞约束并入软体求解 边约束(拉伸) l₀ 2 顶点参与,逐边独立可解 角约束(抗弯) 4 顶点二面角,需 CSR 全局求解

本文基于 Godot 4.x servers/physics_3d/godot_soft_body_3d.cpp 的 PBD 实现(_build_solve_matrix、CSR 装配)与官方文档对 SoftBody3D 的描述。