3.1 MJCF 元素一览
3. MuJoCo 快速上手 用 hello.xml 让一个盒子掉了下来。这一节把 MJCF 文件的整体结构梳理清楚——之后读机械臂、四足、人形的 MJCF 都会用同一套心智模型。
3.1.1 MJCF 的两层结构
MJCF 是 MuJoCo 自己的机器人描述格式,相比 URDF "扁平 link 列表 + parent/child 拼图" 的写法,MJCF 设计上更紧凑:body 直接靠 XML 嵌套表达运动学树,joint 写在 body 内部,geom 一份同时承担渲染和碰撞。
一份能跑的 MJCF 文件在 <mujoco> 根节点下分两层:worldbody 内部用嵌套 body 把机器人 / 场景的运动学树搭起来;worldbody 外部还要几个顶层并列块,让模型能被编译、能被驱动、能记下初始姿态。一个典型骨架是这样:
<mujoco model="...">
<!-- ① worldbody 外部: 编译选项 / 默认值 / 资源 / 驱动 / 初始姿态 / 传感器 -->
<compiler .../> <!-- 角度单位 / mesh 目录 / 自动 limits -->
<option .../> <!-- 求解器: cone / impratio / timestep -->
<default>...</default> <!-- 全局默认值 (joint armature, geom 碰撞 mask, ...) -->
<asset>...</asset> <!-- mesh / texture / material -->
<keyframe>...</keyframe> <!-- 录像式的初始 qpos / ctrl 快照 -->
<sensor>...</sensor> <!-- IMU / 力 / 速度等传感器 -->
<actuator>...</actuator> <!-- 驱动器, 不写 data.ctrl 长度为 0 -->
<!-- ② worldbody 内部: 嵌套 body 描述运动学树 -->
<worldbody>...</worldbody>
</mujoco>
下面把这两层的核心元素分别铺一遍。
3.1.2 worldbody 内:5 个搭骨架的元素
| 元素 | 一句话理解 |
|---|---|
<body> | 一段刚体;子 body 嵌在父 body 里,嵌套即运动学树,不需要 parent / child 字段 |
<joint> | 写在 body 内部,描述这段 body 相对父体怎么动;hinge / slide / free 三种基本类型 |
<inertial> | 质量 + 惯性张量;不写要么 NaN,要么"质量等于 0 飞天" |
<geom> | 同时承担视觉和碰撞;用 group / class / contype / conaffinity 把两者拆开 |
<site> | 无质量、不参与碰撞的标记点;挂传感器 / IK 末端 / 触地检测都靠它 |
一个最小的"机身 + 一段腿"骨架,5 个元素一次串起来:
<worldbody>
<body name="base_link" pos="0 0 0.18">
<inertial pos="0 0 0" mass="1.5"
diaginertia="0.01 0.02 0.02"/>
<geom type="box" size="0.05 0.07 0.13" class="collision"/>
<geom type="mesh" mesh="body_mesh" group="1"
contype="0" conaffinity="0"/>
<body name="leg_hip" pos="0.10 0.05 0">
<inertial pos="0 0 0" mass="0.05"
diaginertia="1e-4 1e-4 1e-4"/>
<joint name="leg_hip" type="hinge"
axis="1 0 0" range="-0.8 0.8"/>
<geom type="capsule" fromto="0 0 0 0.05 0 0" size="0.01"/>
<site name="leg_hip_marker" pos="0.05 0 0"/>
</body>
</body>
</worldbody>
3.1.3 worldbody 外:让模型跑得动的并列块
worldbody 描述了"机器人长什么样、怎么动",但还需要几个顶层并列的块才能真正跑起来:
| 块 | 必需性 | 作用 |
|---|---|---|
<actuator> | 必需(要驱动) | 给 joint 配电机;不写 data.ctrl 长度为 0 |
<asset> | 用 mesh 时必需 | 注册 mesh / texture / material |
<default> | 几乎必需 | 给 <joint> / <geom> / <actuator> 指定全局默认值(armature / 碰撞 mask 等) |
<compiler> | 几乎必需 | angle="radian" / meshdir=... / autolimits=... 等编译选项 |
<keyframe> | 可选但常用 | 录像式记录一组 qpos / ctrl;用 mj_resetDataKeyframe 切过去 |
<sensor> | 可选 | 挂在 site / body 上读 IMU / 力 / 速度 |
<option> | 可选 | 求解器参数:接触锥类型、积分步长 |
<actuator> 的具体配置(<motor> / <position> / <velocity> / <general>、内嵌 PD 的位置伺服等),等到具体机器人章节再展开。
3.1.4 几件常见的坑
<body>嵌套搞错:MJCF 不像 URDF 那样靠 parent / child 字段拼图,而是直接靠 XML 嵌套——子 body 写在父 body 里。如果把腿的根 body 写到 torso 外面,它就直接挂到 worldbody 上,腿和身体没有运动学关系,仿真里会看到一条"飞着的"腿。<joint>没写或写错 type:joint 必须写在 body 内部,描述"当前 body 相对父 body 怎么动"。常见类型:hinge(绕轴转)、slide(沿轴移)、free(6 DoF 浮动 base,可用<freejoint/>简写)。漏写 joint 表示这段 body 和父体焊死——floating base 模型忘加<freejoint/>就会被钉在原点。<inertial>缺失或写错:仿真要么直接 NaN,要么"机器人质量等于 0"飞天。MJCF 编译器在不写 inertial 时会从 geom 自动估算,但 mesh 估出来的惯量经常离谱。一个朴素自检:把所有 body 的mass求和,应当和实物秤出来的总重在 ±10% 以内。<geom>同时承担 visual 和 collision:这是 MJCF 区别于 URDF 的一处关键设计——一个 geom,渲染和碰撞两件事一起做。要把两者分开,用contype/conaffinity两个掩码(设为 0 就只渲染、不参与碰撞),或在<default>里给两类 geom 定义不同 class。腿足机器人的常见做法:精细 mesh 设contype="0"(只看不碰),再额外加几个 capsule / box / sphere 当碰撞体——前者好看,后者碰撞检测快 10 倍以上。<site>名字写错:site 名字是 sensor / 末端 IK 等块的引用目标。比如 sensor 块里有<framequat objtype="site" objname="body_imu_site"/>,那body_imu_site这个 site 就必须在 worldbody 里挂着,编译器找不到会直接报错。<actuator>必须显式写:MJCF 的 joint 只描述运动学,不带"电机"。每个想驱动的关节都要在<actuator>块里挂一个驱动器,否则data.ctrl长度为 0,写控制也没用。axis和pos/quat:joint 的axis是当前 body 局部坐标系下的方向,body 上的pos/quat是它相对父 body 原点的偏移与旋转。如果看着 MJCF 想"这关节怎么转的方向反了",多半是 body 上的quat/euler把局部坐标系旋了一下,记得把axis和父 body 的quat一起读。
参考资料
- MuJoCo 官方文档 · MJCF Reference
- MuJoCo 官方文档 · Modeling overview
- mujoco_menagerie · MuJoCo 官方维护的标准模型库(含多款四足、机械臂、人形)