本节摘要:物理引擎每一步在做的事,是对每个刚体解"牛顿-欧拉方程"的数值积分——质量与惯性张量决定它抗拒运动的方式,步长决定积分的精度与稳定性。工程上最重要的两件事:核对惯性张量量级(sim-to-real 失败案例头名)、按"步长小于最快周期十分之一"选步长。
把一个刚体的运动方程摊开,就是两句话:合外力决定质心加速度,合外力矩决定角加速度。引擎每个步长做的事:
听起来平凡,但每一项都是一本账。重力的账最简单;关节与接触的账最糊涂(3.3 展开);而惯性张量这本账,是新手错得最多的地方。
<!-- 一个 10 kg、尺寸 0.3×0.3×0.1 m 的扁平箱体 --> <inertial> <mass>10.0</mass> <inertia> <ixx>0.0833</ixx> <iyy>0.0833</iyy> <izz>0.1500</izz> <ixy>0</ixy> <ixz>0</ixz> <iyz>0</iyz> </inertia> </inertial>
量级自检的粗算方法:均匀箱体绕质心的惯量量级 ≈ m×L²/12。本例 izz ≈ 10×0.3²/6 ≈ 0.15,与所写一致。如果 URDF 导出时单位错了(把 g 当 kg),惯量差三个量级,症状非常戏剧化:机器人落地像纸片一样乱飘、或者像灌铅一样纹丝不动。核对动作:对每个 link 算一遍 m×L²/12 与所填值比对数量级,三十秒一个模型。
⚠️ 常见坑:URDF 里
<origin>的惯性参考系与碰撞几何参考系不重合,导出工具有时填错。症状是静止悬浮的物体缓慢旋转(本应只受重力的物体出现自转)——遇到"幽灵自转",先查惯性张量的对角项与非对角项。

数值积分用离散步进近似连续时间。步长 dt 越小越准、越慢。经验准则:
# 用命令行验证 SDF 并检查物理配置 gz sdf -p model.sdf | head -40
拿导读的落体世界,分别用 dt=1ms、10ms、50ms 跑同一高度落体,记录第一次触地时刻:1ms 接近理论值 0.452s;10ms 开始漂移;50ms 可能穿过地面(连续碰撞检测未开时)。穿透是"步长大于几何尺度变化"的直接后果——一步之内物体跨过了整个薄板。这就是 3.3 症状表里"穿透"的头号病因。
| 量 | 真实世界 | 仿真账本 | 核对动作 |
|---|---|---|---|
| 质量 | 称重 | <mass> 一行数 |
与 BOM 表对照 |
| 惯性张量 | CAD 软件算 | <inertia> 六个数 |
量级 ≈ mL²/12 比对 |
| 重力 | 9.81 依赖纬度 | <gravity> 可改 |
做山地/月球实验前记得改回来 |
最后一行不是玩笑:把重力改成月球值(1.62)做过实验的世界文件,忘了改回来就投入下一轮测试——这类"环境污染"在团队协作里真实发生过,值得在世界文件版本化时用 CI 检查兜底。
💡 关键直觉:物理参数错误的反馈是即时的、症状是夸张的。这其实是好事——学会读症状(飘、沉、抖、爆),比背参数表有用得多。
手工逐个 link 算 mL²/12 在小模型上可行,上百个 link 的机器人底盘就需要脚本兜底。思路:解析 SDF 里每个 <inertial>,用质量与所在碰撞盒尺寸的粗算值比对所填惯量,偏差超过一个数量级即报警:
# inertia_lint.py:对目录下所有 SDF 做惯性量级核对 import re, glob, sys XML = re.compile(r"<ixx>([0-9.e+-]+)</ixx>.*?<izz>([0-9.e+-]+)</izz>", re.S) MASS = re.compile(r"<mass>([0-9.e+-]+)</mass>") BOX = re.compile(r"<size>([0-9.]+) ([0-9.]+) ([0-9.]+)</size>") bad = 0 for f in glob.glob(sys.argv[1] + "/**/*.sdf", recursive=True): text = open(f, encoding="utf-8").read() for i, (m_grp, inert) in enumerate(zip(MASS.findall(text), XML.findall(text))): mass = float(m_grp) boxes = BOX.findall(text) if not boxes: continue x, y, z = map(float, boxes[min(i, len(boxes)-1)]) rough = mass * max(x, y, z)**2 / 12 # 量级粗算 for label, val in zip(("ixx", "izz"), inert): ratio = float(val) / rough if rough else 0 if ratio < 0.05 or ratio > 20: bad += 1 print(f"{f} link#{i}: {label} 与 mL^2/12 粗算差 " f"{ratio:.3f} 倍,疑似单位或来源错误") print(f"核对完成:{bad} 处可疑")
脚本故意只查量级不查精确值——CAD 导出的精确惯量允许与均匀箱体粗算差数倍(材料分布不均匀),但差十倍以上几乎必然是单位错(克当千克)或参考系错(非对角项挪窝)。把它挂进 CI,配一条"可疑数为零"的门禁,惯性类 sim-to-real 事故就挡在了合入之前。这份脚本与正文的手工口诀共用一条根:精确值可以交给工具,量级感觉必须长在工程师自己身上。
单个刚体的账清楚了。机器人是刚体加关节的组合——下一节看误差最集中的地方:约束求解。