跳到主要内容

3. MuJoCo 快速上手

开始之前,先说明本节需要的基础:

  • 具备 Python 基础,能够创建虚拟环境(如 venvConda)并运行脚本
  • 知道“模型文件描述一个场景/机器人”这件事;即使没系统学过 XML 或 URDF,也不影响跟着做完本节。

本节目标:

  1. 选对安装方式,把环境装起来。
  2. 理解 MJCF -> MjModel -> MjData -> mj_step() 这条最核心的工作流。
  3. 跑通一个最小模型,让一个盒子真正掉下来。
  4. 分清什么时候用 viewer,什么时候直接写 Python 脚本。

3.1 MuJoCo 简介

MuJoCo(Multi-Joint dynamics with Contact)是由 Google DeepMind 维护并开源的高性能通用物理引擎。

传统物理引擎大致分为两类:一类是机器人学与生物力学领域常用的引擎,它们在广义坐标(或关节坐标)下使用高效且精确的递归算法,但要么忽略接触动力学,要么沿用早期的弹簧-阻尼近似,因而往往需要极小的仿真步长;另一类是游戏引擎,采用更现代的思路,通过求解优化问题来计算接触力,但通常在笛卡尔坐标下以数值方式处理关节约束,面对复杂运动链时容易出现不准确与不稳定。MuJoCo 是第一个将两者结合起来的通用物理引擎——广义坐标建模 + 基于优化的接触动力学,在精度与稳定性之间取得了更好的平衡。

它的核心价值,是为机器人学、生物力学、图形动画和机器学习等领域,提供快速而精确的多刚体动力学与接触仿真。对机器人学习者来说,更直观的理解是:MuJoCo 擅长把"建模 → 施加控制 → 推进仿真 → 读取状态"这条闭环做得极其直接。

因此,它尤其适合控制器验证、接触丰富的运动实验、强化学习环境搭建,以及各类需要大量反复迭代的算法原型开发。

MuJoCo 与其他仿真平台的区别

第一次接触仿真时,一个常见误区是把所有平台都想成“只是界面不同”。其实它们的设计重心差别很大。

  • Isaac Sim 更像高保真机器人仿真工作台,强项是逼真的渲染、传感器、场景搭建和围绕工业/数字孪生工作流的集成。
  • Gazebo / Ignition 更像机器人系统集成测试场,和 ROS 生态贴得更近,适合验证感知、导航、控制节点如何在完整系统里协同。
  • PyBullet 更像轻量级、容易快速起步的物理仿真工具,做教学、小实验和快速验证很方便。
  • MuJoCo 则更像动力学、接触与控制实验底座,重点不是把 3D 世界“做得很大很真”,而是把仿真内核、模型组织和算法迭代这条链路做得高效、稳定、可编程。

相比 Isaac Sim 这类高保真平台,MuJoCo 通常对设备和图形环境的要求更低,更适合先把控制、接触和强化学习实验快速跑起来。

所以如果目标是下面这些方向,MuJoCo 往往会更合适:

  • 机械臂控制
  • 腿足机器人接触实验
  • 强化学习环境搭建
  • 模型预测控制、最优控制
  • 算法原型验证

反过来说,如果读者当前更关心的是高保真视觉传感器、复杂场景编辑、ROS 全系统联调,或者偏数字孪生式的工作流,那 Isaac Sim 或 Gazebo 一类平台通常会更自然。MuJoCo 的长板,不在“做一个很完整的仿真世界”,而在“把动力学实验这件事做得非常顺手”。

3.2 安装

3.2.1 安装方式选择

MuJoCo 支持 WindowsLinuxmacOS 等多平台安装,包含 pip 安装和下载官方预编译二进制两种方式。

pip 安装简单,适合新手第一次入门。如果更偏下面这些场景,再考虑下载官方预编译二进制:

  • 想直接体验原生 simulate 应用
  • 想看官方附带的模型集合和 C/C++ sample
  • 想做底层开发或自己编译

3.2.2 pip 安装

推荐使用 Conda 管理 Python 环境,安装命令如下:

conda create -n mujoco python=3.12 -y
conda activate mujoco
python -m pip install --upgrade pip
pip install mujoco==3.7.0

3.2.3 下载官方预编译二进制

如果目标是直接体验原生 simulate、查看官方模型和样例程序,或者后续需要做 C/C++ 层开发,可以直接从 GitHub Releases 下载官方预编译二进制。

基本步骤如下:

  1. 前往 GitHub Releases 下载对应平台的预编译包
  2. Windows.zipLinux.tar.gzmacOS.dmg
  3. Windows/Linux 解压即可,没有额外安装器
  4. macOS 可以直接双击 MuJoCo.app

3.3 GUI 显示

MuJoCo 常见的 GUI 入口主要有两类:

  1. Python 包自带的 mujoco.viewer
  2. 官方原生 GUI 程序 simulate

从功能上看,这两种方式都能打开图形界面并观察仿真;从使用场景上看,它们分别对应两条不同的工作流。

方式 A:mujoco.viewer

这条路线适合已经通过 pip install mujoco 安装了 Python 包的场景。官方 Python 文档明确说明,mujoco.viewer 是 Python 包自带的交互式 GUI viewer,并且它与二进制发布包中的 simulate 基于同一套代码基础。

最简单的启动方式是:

python -m mujoco.viewer

这个命令会打开一个空 viewer,可以直接拖入模型文件;如果已经有 MJCF 文件,也可以直接指定:

python -m mujoco.viewer --mjcf=hello.xml
macOS 用户:先用 python -m mujoco.viewer

python -m mujoco.viewer 是官方文档里的 standalone app 启动方式,macOS 上也应优先这样验证 viewer 能否打开。mjpython 不是给这个命令兜底用的;它主要用于你在自己的脚本里调用 mujoco.viewer.launch_passive(...),并且遇到 macOS GUI 主线程限制时。

python -m mujoco.viewer
python -m mujoco.viewer --mjcf=hello.xml

后面 3.5 节的 hello_box_viewer.py 用到了 launch_passive。如果在 macOS 上直接 python hello_box_viewer.py 报主线程或 GLFW 相关错误,再把启动命令换成 mjpython hello_box_viewer.py

这条路线更适合:

  • 已经在 Python 环境里工作
  • 想快速确认 GUI 能否正常打开
  • 想把 GUI 验证和后续 Python 脚本工作流接起来

方式 B:simulate

这条路线适合已经下载官方预编译二进制的场景。simulate 是官方原生 GUI 程序,通常和模型文件、样例程序、头文件、动态库一起出现在二进制发布包中。

如果是 Windows/Linux,官方文档给出的最小启动方式是从 bin 子目录运行:

./bin/simulate ./model/humanoid/humanoid.xml

下面动图展示的是执行 ./bin/simulate ./model/humanoid/humanoid.xml 后的界面效果:

官方原生 simulate 打开 humanoid.xml 的界面效果
官方原生 simulate 打开 humanoid.xml 的界面效果

如果只是为了先确认 GUI 能不能正常打开,优先使用 mujoco.viewer 往往更简单;如果已经下载了官方预编译二进制,或者想直接体验原生 simulate,那么这条路线会更直接。

3.4 MuJoCo 的核心心智模型

MuJoCo 上手最快的方法,不是先背 XML 标签,而是先记住这 5 个核心概念。

1. MJCF

MuJoCo 的原生模型描述格式,本质上就是 XML。可以把它理解成“世界、刚体、关节、碰撞体、执行器”这些东西的声明式描述。

2. MjModel

这是编译后的“静态模型”。它描述的是模型结构和参数,本身通常不在仿真过程中频繁改动。

3. MjData

这是仿真运行时的“动态状态”。位置、速度、接触、控制输入、中间计算结果,主要都在这里。

4. mj_step

这是推进物理仿真的核心函数。可以把它理解成“把系统往前走一个时间步”。

5. viewer

viewer 负责把仿真状态可视化出来,但 viewer 本身不是场景编辑器。它更像一个观察、调试和交互窗口。

真正最值得记住的一条主线是:

MJCF 文件 -> 编译成 MjModel -> 创建 MjData -> 循环调用 mj_step()

3.5 第一个模型:让一个盒子掉下来

最好的第一个实验,不是上来就找机械臂,而是先做一个会掉下来的盒子。

因为它会一次性建立三个最重要的直觉:

  • 地面也是 geom
  • 刚体要靠 body + joint
  • 物理是通过 mj_step() 推进的

这个 demo 可以拆成三步:

  1. 先写出最小模型 hello.xml
  2. viewer 直接打开模型,确认 GUI 显示正常
  3. 再分别用无渲染脚本和带 viewer 的脚本推进仿真

编写最小模型 hello.xml

新建一个 hello.xml

<!-- 根节点:声明这是一个 MuJoCo 模型,model 名字会显示在 viewer 标题和调试信息里 -->
<mujoco model="hello_box">
<!-- 仿真步长:0.002 秒,也就是 500 Hz。mj_step 每调用一次,仿真时间前进 2 ms -->
<option timestep="0.002"/>

<!-- worldbody 是世界坐标系下的场景根节点;所有直接放在这里的物体都以世界坐标为参考 -->
<worldbody>
<!-- 灯光:pos 是灯的位置,dir 是照射方向,diffuse 控制漫反射亮度 -->
<light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>

<!-- 地面:type="plane" 表示无限平面;size 前两个数影响显示范围,第三个数影响接触边界厚度 -->
<geom name="ground" type="plane" size="1 1 0.1" rgba=".9 .9 .9 1"/>

<!-- 一个刚体:pos="0 0 1" 表示盒子初始质心在世界坐标 z=1 的位置 -->
<body name="box" pos="0 0 1">
<!-- freejoint 给这个刚体 6 自由度;没有它,box 会被固定在世界坐标系中,不会下落 -->
<freejoint/>

<!--
盒子的几何体:
- type="box" 表示长方体
- size 是半尺寸,所以实际长宽高分别是 0.2、0.4、0.6
- rgba 分别是红、绿、蓝、透明度,取值范围 0 到 1
-->
<geom
name="box_geom"
type="box"
size=".1 .2 .3"
rgba="0.1 0.6 0.2 1"
/>
</body>
</worldbody>
</mujoco>

直接用 viewer 打开模型

有了 hello.xml 之后,就可以直接用 Python 包自带 viewer 打开:

python -m mujoco.viewer --mjcf=hello.xml

下面动图展示的是执行 python -m mujoco.viewer --mjcf=hello.xml 后的界面效果:

mujoco.viewer 打开 hello.xml 的界面效果
mujoco.viewer 打开 hello.xml 的界面效果

通常会看到一个悬空盒子掉到平面上。

这一步最重要的认知是:

  • MuJoCo viewer 能很好地“看仿真”
  • 但它不是 Isaac Sim 那种以 GUI 编辑为中心的工作台
  • 主要工作流仍然会是“写模型 + 写脚本”

如果打开的是空 viewer,也没关系。官方文档明确写了它支持先启动空窗口,再拖放模型进去。

先不渲染,只跑物理

先把图形拿掉,是最稳的入门方式。

新建 hello_box_headless.py

import mujoco

# 从 MJCF/XML 文件编译出静态模型。
# model 里保存的是结构和参数,比如 body、joint、geom、质量、时间步等。
model = mujoco.MjModel.from_xml_path("hello.xml")

# 创建和 model 对应的运行时状态。
# data 里保存的是会随仿真变化的量,比如时间、位置、速度、接触和中间计算结果。
data = mujoco.MjData(model)

# 每次 mj_step 会按 model.opt.timestep 推进一个仿真步。
# hello.xml 里 timestep=0.002,所以这里会运行大约 1000 步,直到仿真时间到 2 秒。
while data.time < 2.0:
mujoco.mj_step(model, data)

print("sim time:", data.time)

# 按 body 名称读取 box 的世界坐标位置。
# xpos 是 MuJoCo 在前向计算后写入 data 的结果;copy() 用来拷贝一份当前数值,避免后续仿真覆盖。
print("box position:", data.body("box").xpos.copy())

执行:

python hello_box_headless.py

执行结果示例:

sim time: 2.0000000000000013
box position: [-1.09938295e-19 8.84563957e-18 2.99892245e-01]

这里最值得关注的是 box position 的第三个分量大约是 0.3。这是因为盒子的半高就是 0.3,当盒子落到地面并稳定下来之后,质心高度会接近这个值。

前两个分量非常接近 0,说明盒子基本停在世界坐标系原点上方;之所以不是严格的 0,主要是浮点数计算带来的微小数值误差。

这一步特别值钱,因为它把“MuJoCo 会不会用”和“viewer 能不能正常开窗”拆开了。

边步进边看 viewer

已经能跑通无渲染脚本后,再进入交互 viewer 会更稳。

新建 hello_box_viewer.py

import time

import mujoco
import mujoco.viewer

# 和无渲染版本一样,先编译模型,再创建运行时状态。
model = mujoco.MjModel.from_xml_path("hello.xml")
data = mujoco.MjData(model)

# launch_passive 会打开一个交互 viewer,但物理仿真仍然由当前脚本控制。
# 进入 with 代码块后,viewer 负责显示;离开 with 代码块后,窗口资源会自动释放。
with mujoco.viewer.launch_passive(model, data) as viewer:
# viewer.is_running() 会在窗口关闭后变成 False。
# data.time < 10.0 让脚本最多运行 10 秒,避免无限循环。
while viewer.is_running() and data.time < 10.0:
# 记录这一轮循环开始的真实时间,用来把仿真速度压到接近实时。
step_start = time.time()

# 推进一个物理步;hello.xml 里 timestep=0.002,所以一次前进 2 ms 仿真时间。
mujoco.mj_step(model, data)

# 把最新的 data 同步到 viewer。
# 如果不调用 sync(),窗口可能不会及时显示当前仿真状态。
viewer.sync()

# 计算这一轮物理步和渲染同步已经花了多久。
# 如果还没用完一个 timestep,就 sleep 一下,避免仿真跑得比真实时间快太多。
time_until_next_step = model.opt.timestep - (time.time() - step_start)
if time_until_next_step > 0:
time.sleep(time_until_next_step)

执行:

python hello_box_viewer.py
# macOS:
mjpython hello_box_viewer.py

下面动图展示的是执行 python hello_box_viewer.py(macOS 上是 mjpython hello_box_viewer.py)后的界面效果:

hello_box_viewer.py 运行时的 viewer 界面效果
hello_box_viewer.py 运行时的 viewer 界面效果

这个版本和前面的 python -m mujoco.viewer --mjcf=hello.xml 不同:前者更像“直接打开模型看结果”,这里则是脚本一边调用 mj_step() 推进仿真,一边通过 viewer.sync() 把状态同步到 GUI。

3.6 从盒子到机器人

当“让一个盒子掉下来”这件事已经能用 MJCF 和 Python 各做一遍时,就已经跨过 MuJoCo 入门里最关键的一步了。

接下来通常有三条路线:

1. 走控制方向

  • 给模型加 joint
  • 加 actuator
  • 在循环里写控制输入
  • 观察轨迹、误差和稳定性

2. 走强化学习方向

  • 把 MuJoCo 任务封装成环境
  • Gymnasium
  • 再接 PPO、SAC、TD3 之类的训练代码

3. 走机器人建模方向

  • 先从简单机械臂开始
  • 再导入自己的 URDF
  • 逐步补碰撞、惯量、执行器和传感器

MuJoCo 官方 overview 也明确写到,模型既可以来自 MJCF,也可以来自 URDF。所以对机器人开发者来说,常见工作流可以理解成:

MJCF / URDF -> MjModel -> MjData -> 控制器 / 优化 / RL

3.7 本节小结

这一节最重要的结论有四个:

  • MuJoCo 是一个偏动力学、控制和强化学习实验的物理引擎
  • 对第一次上手的人,Python 3.12 + pip install mujoco==3.7.0 是截至 2026-04-18 最稳妥的起点
  • 真正需要建立的核心心智模型是 MJCF -> MjModel -> MjData -> mj_step()
  • viewer 很重要,但它是观察和调试窗口,不是完整场景编辑器

当“让一个盒子掉下来”能同时用 viewer 和 Python 脚本各做一遍时,就已经真正迈过 MuJoCo 的入门门槛了。

参考资料