第 1 课

装好 MuJoCo 4.0,写第一个 MJCF 模型并渲染出第一张图

用 pip 安装 mujoco 4.0.0 并核对版本,认识 mujoco-core、mujoco-studio 与经典渲染依赖三部分;用 MJCF 写红方块加绿球模型,用按名字访问读 MjModel,看到 MjData 在 mj_kinematics 前后的变化;用 MUJOCO_GL=egl 离屏渲染,理解为什么不先 mj_forward 画面是全黑的。

图文20 分钟MuJoCo 官方文档 ↗

课程目录第 1 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课对照的官方页面:Python 绑定文档 、官方入门 Colab 教程 、4.0.0 发布说明 。测试环境:2026-11-11,Asia/Shanghai,Linux x86_64 云主机(无独立显卡),Python 3.12.15,mujoco 4.0.0(PyPI 发布于 2026-11-06)。下面所有「对照结果」都是这台机器的真实输出,只做了节选。

MuJoCo 是 Google DeepMind 维护的开源物理引擎,机器人、强化学习和具身智能研究几乎都会用到它。本课先把 4.0.0 装好,弄清 4.0 新拆出来的三个包,再用官方教程里的「红方块 + 绿球」模型认识 MuJoCo 最核心的两个对象:描述「是什么」的 MjModel 和记录「此刻怎样」的 MjData,最后离屏渲染出第一张图。

第 1 步:建一个虚拟环境并安装 MuJoCo

官方推荐直接从 PyPI 安装,引擎本体已经打包在 wheel 里,不用另外下载编译。PyPI 上 4.0.0 要求 Python 3.11 或更高。

mkdir -p ~/mj-lab && cd ~/mj-lab
python3 -m venv .venv
source .venv/bin/activate
pip install mujoco

对照结果(节选):

Collecting mujoco
  Downloading mujoco-4.0.0-py3-none-any.whl.metadata (232 kB)
Collecting mujoco-core==4.0.0 (from mujoco)
Collecting mujoco-studio==4.0.0 (from mujoco)
Collecting websockets>=13 (from mujoco-studio==4.0.0->mujoco)
Successfully installed glfw-2.11.0 mujoco-4.0.0 mujoco-core-4.0.0
  mujoco-studio-4.0.0 numpy-2.5.4 pyopengl-3.1.10 websockets-17.4

Windows 上把 source .venv/bin/activate 换成 .venv\Scripts\activate。本课只在 Linux 上实测过。

第 2 步:认识 4.0 的三个包,确认版本

从 4.0.0 起,PyPI 上的 mujoco 变成了一个「总包」,它会装上三样东西:

包名装了什么
mujoco-coreC 引擎与最小 Python 绑定,只依赖 numpy
mujoco-studio新的 Studio 查看器与 Filament 渲染器
glfw、pyopengl经典 OpenGL 渲染要用的依赖

官方 Python 文档写明:只想在服务器上跑仿真、训练强化学习、不需要任何画面时,可以只装 pip install mujoco-core(它只依赖 numpy)。确认版本:

pip list | grep -i mujoco
python -c "import mujoco; print(mujoco.__version__)"

对照结果:

mujoco        4.0.0
mujoco-core   4.0.0
mujoco-studio 4.0.0
4.0.0

第 3 步:用 MJCF 写第一个模型,读 MjModel

MuJoCo 的模型用一种叫 MJCF 的 XML 来写。最小的合法模型就是 <mujoco/>;所有物体都放在 <worldbody> 里。新建 hello_mujoco.py,内容如下(模型来自官方教程):

import mujoco

xml = """
<mujoco>
  <worldbody>
    <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
    <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
  </worldbody>
</mujoco>
"""
model = mujoco.MjModel.from_xml_string(xml)
print('geom 数量:', model.ngeom)
print('所有 geom 名字:', [model.geom(i).name for i in range(model.ngeom)])
print('green_sphere 的 id:', model.geom('green_sphere').id)
print('green_sphere 的颜色:', model.geom('green_sphere').rgba)

try:
    model.geom('blue_cone')
except KeyError as e:
    print('写错名字时:', e)

data = mujoco.MjData(model)
print('计算前 geom_xpos:\n', data.geom_xpos)
mujoco.mj_kinematics(model, data)
print('计算后 geom_xpos:\n', data.geom_xpos)
print('按名字读位置:', data.geom('green_sphere').xpos)

两个细节:red_box 没写位置,默认就在原点;green_sphere 没写 type,默认类型就是球。MJCF 的每个属性都有默认值,完整列表在官方 XML Reference 里。

第 4 步:读 MjData,理解为什么要先「算一下」

运行上一步的脚本:

python hello_mujoco.py

对照结果:

geom 数量: 2
所有 geom 名字: ['red_box', 'green_sphere']
green_sphere 的 id: 1
green_sphere 的颜色: [0. 1. 0. 1.]
写错名字时: "Invalid name 'blue_cone'. Valid names: ['green_sphere', 'red_box']"
计算前 geom_xpos:
 [[0. 0. 0.]
 [0. 0. 0.]]
计算后 geom_xpos:
 [[0.  0.  0. ]
 [0.2 0.2 0.2]]
按名字读位置: [0.2 0.2 0.2]

怎么读这段输出:

  • MjModel 里是不随时间变化的东西,比如有几个 geom、什么颜色、多大。model.geom('名字') 这种「按名字访问」是 Python 绑定提供的便利写法,名字写错时会把所有合法名字列出来。
  • MjData 里是「此刻的状态」以及由状态算出来的量。刚创建时 geom_xpos(每个 geom 在世界坐标里的位置)全是 0,因为还没算。调用 mj_kinematics 做一次运动学计算后,绿球才出现在 (0.2, 0.2, 0.2)。
  • 平时更常用的是 mj_forward,它把整条计算流水线跑到加速度为止,做的事比 mj_kinematics 多,但不容易漏算。下一步渲染就用它。

第 5 步:离屏渲染出第一张图

先装官方教程里用来存图和存视频的 mediapy(存视频还要系统里有 FFmpeg):

pip install mediapy

新建 first_image.py。这次给模型加一盏灯,否则画面很暗:

import mujoco
import mediapy as media

xml = """
<mujoco>
  <worldbody>
    <light name="top" pos="0 0 1"/>
    <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
    <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
  </worldbody>
</mujoco>
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)

with mujoco.Renderer(model) as renderer:
    pixels = renderer.render()
    print('不调用 mj_forward 时的平均亮度:', round(float(pixels.mean()), 1))
    mujoco.mj_forward(model, data)
    renderer.update_scene(data)
    pixels = renderer.render()
    print('图像尺寸:', pixels.shape, pixels.dtype)
    print('调用 mj_forward 后的平均亮度:', round(float(pixels.mean()), 1))
    media.write_image('first_image.png', pixels)
print('已保存 first_image.png')

在没有显示器的 Linux 服务器上,要先告诉 MuJoCo 用 EGL 做离屏渲染(官方 Colab 教程也是这样设置的);有桌面的电脑可以跳过这一行:

export MUJOCO_GL=egl
python first_image.py

对照结果:

不调用 mj_forward 时的平均亮度: 0.0
图像尺寸: (240, 320, 3) uint8
调用 mj_forward 后的平均亮度: 16.5
已保存 first_image.png

第一次渲染是全黑的(平均亮度 0),原因和第 4 步一样:没有先计算,场景里什么都没有。调用 mj_forward 并用 update_scene 把状态同步给渲染器之后,就能看到红方块和绿球。Renderer 默认输出 240×320 的 RGB 数组,可以用 mujoco.Renderer(model, 480, 640) 指定高和宽。

常见坑

  • 装完 import 报找不到模块:多半是没激活虚拟环境,或者 pip 和 python 不是同一个环境。用 python -m pip install mujoco 可以避免装错地方。
  • 从 3.x 升级上来后 import 出错:4.0 把包拆分了,官方发布说明建议先卸载再装:pip uninstall -y mujoco && pip install mujoco。
  • 服务器上渲染报 OpenGL 相关错误:先确认设置了 MUJOCO_GL=egl。本课测试机上 MUJOCO_GL=osmesa 会报 'NoneType' object has no attribute 'glGetError',原因是系统没装 OSMesa 库,换成 egl 即可。
  • 读到的位置全是 0、渲染全黑:创建 MjData 或改了状态之后,记得先调用 mj_forward(或 mj_kinematics)。
  • 把数组存进列表后数值全一样:MjData 里的数组是直接指向引擎内存的视图,会被后续计算原地改写。要保存某一时刻的值,写 data.qpos.copy()。

小结

这一课装好了 MuJoCo 4.0.0,知道了 mujoco 总包由 mujoco-core、mujoco-studio 和经典渲染依赖组成;用 MJCF 写了第一个模型,分清了 MjModel(模型描述)和 MjData(状态与计算结果),也知道读位置、渲染之前要先 mj_forward。下一课给物体加上关节让它动起来,把仿真录成视频,并复现官方教程里会自己翻身的「翻转陀螺」。