本节摘要:MuJoCo 眼里的任何机器都是一棵运动学树:根是世界体,每个 body 是一个节点,关节是节点与父体之间的运动接口,geom 负责空间占位,inertial 记录质量分布。树的形状不是格式偏好,而是引擎求解动力学的组织方式——第三章的质量矩阵、雅可比矩阵都按这棵树装配。本节讲清三个实体(body、joint、geom)各自承担的契约、自由度的数法、以及惯性参数为什么必须认真填。
上一节拆完了文件的四层外壳,本节进入 worldbody 内部,看引擎世界观的骨干——运动学树。把这一节读透,第三章的动力学公式才有着落点:那些方程里的广义坐标 q,就是这棵树上所有关节变量的总清单。
MJCF 强制所有 body 组成一棵以世界体为根的树。这不是为了好看,而是求解的刚需:多刚体动力学的最高效算法沿树递归——从根向叶把运动逐级传递(正运动学),从叶向根把力逐级汇聚(逆动力学),复杂度与刚体数成正比。树一旦有环,递归就失效,所以引擎在编译期直接拒绝闭环拓扑(闭环要用 equality 约束拼,下一节讲)。
看一段最小但完整的树:
<worldbody> <geom name="floor" type="plane" size="2 2 0.1"/> <body name="pedestal" pos="0 0 0.05"> <geom type="box" size="0.05 0.05 0.05" mass="2"/> <body name="upper_arm" pos="0 0 0.1"> <joint name="shoulder" type="hinge" axis="0 1 0" range="-1.5 1.5"/> <geom type="capsule" fromto="0 0 0 0.25 0 0" size="0.03" mass="1"/> <body name="forearm" pos="0.25 0 0"> <joint name="elbow" type="hinge" axis="0 1 0" range="-2.3 0.2"/> <geom type="capsule" fromto="0 0 0 0.2 0 0" size="0.025" mass="0.6"/> </body> </body> </body> </worldbody>
三个细节值得逐个咀嚼。其一,坐标是相对的:forearm 的 pos 是相对 upper_arm 末端而言,不是世界坐标。这与人搬东西的直觉一致——你描述手的位置时说「在肘上方三厘米」,不会说「在东经多少度」。相对描述让机械臂加长一个连杆时,后续连杆的坐标一行都不用改。其二,body 不动,joint 才动:body 只是「挂」在父体上的一袋质量与几何,没有 joint 的 body 与父体焊成一体,相对位姿永远不变。其三,axis 写在 body 的局部系里:shoulder 的旋转轴是父体系下的 Y 轴,父体转了,轴也跟着转——这正是机构学里「轴随件走」的常识。

body:一袋惯性。 body 的本职是携带质量分布:质量多少、质心在哪、惯量张量朝什么方向。你不写 inertial 块时,引擎会从 geom 的几何与 mass 推算——规则几何没问题,mesh 推算则会「实心化」处理,把本该空心的壳算成实心柱,惯量可能偏差数倍。所以凡是用 mesh 建模的关键连杆,都应当从 CAD 或实测把惯量张量填进 inertial:mass、质心偏移 ipos、主惯量 diaginertia 三件套。惯量错了的症状很隐蔽:模型「手感发飘」或「转不动」,动力学方程完全成立,只是不对应你桌上那台机器。
joint:自由度的来源。 关节类型一共几种,对应机构学的基本运动副:hinge 转轴一个自由度、slide 滑轴一个自由度、ball 球铰三个、free 自由体六个。数一台机器的自由度,就是把树上所有关节的变量数加起来——上图的二连杆是 2,加一个 free 关节的浮动基座就是 8。几个配置细节决定了「手感」:range 是限位(配 autolimits 自动生效);damping 是关节阻尼,RL 建模里几乎是必填项,没有阻尼的铰链会让策略学出一秒钟甩十圈的抽风动作;armature 是电机转子折算到关节的等效惯量,加一点点能显著压住数值噪声,真机有减速器的关节尤其该加。
geom:空间占位与接触面。 geom 负责碰撞与视觉:它占哪里、表面摩擦多少、参与哪些碰撞配对。建模的核心取舍是「碰撞几何要简化」——渲染用精细 mesh 没问题,碰撞检测请用 primitive(box、capsule、sphere、cylinder)。原因在第三章展开,这里先给结论:primitive 之间的接触有解析解,又快又稳;任意 mesh 对 mesh 的接触既慢又容易出病态接触点。工程惯例是「一个视觉 geom 加一个隐藏的 primitive 碰撞体」,contype 与 conaffinity 控制谁和谁可能碰撞,把视觉件排除出碰撞派对。
把惯量这件事展开成一次真实事故。背景:某团队复刻一台四足机器人,连杆几何用厂家提供的 mesh,质量按官方表填,加载顺利、能跑,遂进入策略训练。操作:训练跳跃任务时,策略学会了「先快速摆动前腿再蹬地」——仿真里 reward 一路上涨。结果:部署到真机,前腿一摆整台机器侧翻,动作完全复现不出来。解读:逐项排查后锁定 mesh 惯量问题——厂家 mesh 是壳体网格,引擎实心化处理后大腿惯量为实测值的三倍多;仿真里「甩腿」产生的角动量被夸大的惯量偷偷稳住了,真机惯量小三倍,同样的力矩直接把机身带翻。修复是把每根连杆的实测惯量张量写进 inertial 块,重新训练后真机一次通过。变式:没有实测条件怎么办?可以用「等效几何」兜底——用密度乘以简化 primitive 体积反推惯量,量级至少对;再往上一档,给关节加 armature 抵消未建模的转子惯量。惯量估计的精度要求与任务速度成正比:静态演示粗估即可,高速动态任务必须较真。
free 关节值得单独一段。机械臂固定在桌面,根 body 焊死在世界体上即可;但人形、四足这类「没有底座」的机器,躯干必须在空间里自由运动——给躯干挂一个 free 关节,它就获得六个自由度,整台机器悬浮在重力场里。这是 MJCF 表达浮动基座的标准姿势,也是它比很多引擎干净的地方:不需要「动态基底」之类的特殊机制,free 关节就是一个普通关节,第六章会讲它的数值细节。新手常见的误操作是给每个腿部 body 都挂 free 关节——记住口诀:一台机器只有一个 free,挂在根刚体上,其余全是 hinge 与 slide。
树有了骨架,还缺动力。下一节给关节配上执行器、给机构补上约束,让这台机器真正能被驱动。