刚体动力学的数值积分要三样东西:质量 $M$、质心 $c$、惯量张量 $I$。新手常以为这些是手调的参数,实际在默认配置(mass = 0 自动模式)下,它们全部从碰撞形状的体积 × 密度推导出来。理解这套推导有两个用处:调手感时知道每个旋钮在拧什么;排查"为什么这个刚体转起来怪怪的"时有据可依。
推导第一步:从形状到质量分布
每个 CollisionShape3D 在质量自动模式下的贡献是 $m_i = V_i \cdot \rho$,$V_i$ 是形状体积,$\rho$ 是密度(来自 physics_material_override 的 density,或直接取 body 的默认密度)。总质量和质心:
$$ M = \sum_i m_i, \qquad c = \frac{1}{M} \sum_i m_i \, c_i $$
质心是各形状质心的加权平均——把两个箱子拼成一个刚体,质心自然偏向重的那只。
推导第二步:惯量与平行轴定理
惯量张量描述"绕某轴转动有多难"。基本形状的公式有解析解,拿 box 和 sphere 举例:
$$ I_x^{\text{box}} = \frac{m}{12}\left(h^2 + d^2\right), \qquad I^{\text{sphere}} = \frac{2}{5} m r^2 $$
box 的 $x$ 轴惯量由另外两条边的平方和决定——绕 x 转动的质量分布离 x 轴的距离由 $h$、$d$ 刻画,这就是 $r^2$ 的物理含义:惯量对质量到转轴的距离是平方敏感的,所以同样的质量,摊得越平越难转。
多形状合成时,每个形状的惯量要先搬到公共质心 $c$ 下,这一步是平行轴定理:
$$ I = I_{cm} + m \, d^2 $$
$d$ 是形状质心到整体质心的距离。注意 $I_{cm}$ 必须是形状关于自身质心的惯量,而公式表给的都是这个——所以流程是"形状局部惯量 → 平行轴搬到整体质心 → 全部相加",Godot 的 _update_mass_properties()(servers/physics_3d/godot_body_3d.cpp)就是这条流水线的字面实现。
Godot 的近似:只存对角
完整惯量是 3×3 对称张量,有 6 个独立分量;Godot Body 只存 inertia: Vector3 三个数——假设刚体的主轴恰好与世界轴对齐。配合条件是:合成时把各形状的非对角项丢掉,只留对角和。
这个近似在什么时候失真:刚体斜着摆——L 形、T 形组合体,主轴明显歪。丢掉非对角项等于把质量分布向坐标轴投影,绕某个轴转的时候"借力"到别的轴的效应(陀螺进动)就没了。看角速度积分公式最清楚:
$$ \omega_{t+1} = \omega_t + I^{-1}\left(\tau - \omega_t \times (I\omega_t)\right)\Delta t $$
括号里第二项是陀螺项。对角 $I$ 下 $I\omega$ 只是逐分量乘,叉乘 $\omega \times I\omega$ 的三个分量是 $\omega_y\omega_z(I_z - I_y)$ 循环——只有当两轴惯量相等时才恒为零。不对称刚体旋转时会真实地自转轴漂移(网球拍定理),Godot 的对角假设把这个效应抹掉了。对游戏大多数箱子、球、胶囊,无所谓;对斜抛的不规则组合体,转速衰减和进动会显得"假"。
想要完整行为,两条路:换 Jolt(JPH 存 3×3 逆惯量张量,陀螺项保留,这是两个引擎行为差异清单上实打实的一条);或者 custom integrator——_integrate_forces(state) 接管角速度积分,自己在 state.inertia 的基础上补项。
实务调节地图
| 需求 | 入口 | 本质 |
|---|---|---|
| 整体变重/轻 | mass(非 0 时关闭自动) |
直接覆盖 $M$,质心惯量按缩放重推 |
| 只改转动手感 | inertia(Vector3 覆盖) |
绕三轴的"难转程度"各拧一个数 |
| 质心故意偏移 | center_of_mass_mode = CUSTOM |
不倒翁、悬挂物的做法 |
| 力/冲量自定义 | _integrate_forces |
接管积分,引擎只保碰撞求解 |
| 密度来源 | physics_material_override.density |
调整 $m_i = V_i\rho$ 里的 $\rho$ |
一个小提醒:mass = 0 不是"无质量",是"自动算"。真正零质量的 body 是静态/运动学体,它们压根不进 solver 的质量系统(分类后面细讲)。
本文推导对应 servers/physics_3d/godot_body_3d.cpp 的 _update_mass_properties() 与 godot_shape_3d.cpp 各形状的 get_volume / 惯量计算;Jolt 侧为 JPH::MassProperties(3×3 逆惯量)。