1. MuJoCo 初识:从零开始认识这个物理引擎
大家好,我是你们这门课的老朋友。做了十几年机器人仿真,我接触过的物理引擎少说也有五六个。但 MuJoCo 是我个人最偏爱的一个——不是因为它完美无缺,而是因为它「懂」机器人。
这一章,咱们先不急着写代码。先聊聊 MuJoCo 到底是什么,为什么值得你花时间学它,以及物理引擎里那些绕不开的核心概念。最后,我会带你跑起第一个仿真程序。
MuJoCo 是什么?
MuJoCo 的全称是 Multi-Joint dynamics with Contact,直译过来就是「多关节动力学与接触」。它是由 Roboti LLC 开发,后来被 Google DeepMind 收购的开源物理引擎。
说白了,它是一个专门用来模拟机器人、生物力学和物理交互的软件工具。你给它一个机器人模型,告诉它关节怎么连、地面摩擦力多大,它就能算出下一秒机器人会怎么动。
我最早接触 MuJoCo 是在 2018 年,当时做一个四足机器人的步态规划。用别的引擎算接触力,动不动就穿透地面,调参数调到怀疑人生。换了 MuJoCo 之后,第一次跑通仿真,那个接触稳定性让我印象特别深。
为什么选择 MuJoCo?
市面上物理引擎不少,Bullet、ODE、PhysX、DART……为什么我推荐 MuJoCo?
| 特性 | MuJoCo | 其他引擎(典型) |
|---|---|---|
| 接触建模 | 基于凸优化,稳定不穿透 | 惩罚法,容易穿透或抖动 |
| 计算速度 | 极快,支持实时仿真 | 中等,复杂场景可能掉帧 |
| 精度 | 解析梯度,适合控制与优化 | 数值梯度,精度有限 |
| Python 接口 | 原生支持,API 简洁 | 需要额外绑定 |
| 开源 | Apache 2.0 协议 | 部分引擎商用受限 |
我个人最看重的是两点:接触稳定和解析梯度。做机器人控制的人都知道,接触力算不准,后面的规划全是白搭。而解析梯度意味着你可以把 MuJoCo 嵌入到优化算法里,做轨迹优化、参数校准,效率高一个数量级。
核心优势一句话总结: MuJoCo 在「精度」和「速度」之间找到了一个极好的平衡点,尤其适合机器人领域的仿真与控制。
物理引擎的核心概念
在动手之前,咱们得先搞懂三个基础概念。你想想看,物理引擎本质上就是在解一个「物体怎么动」的问题。那它需要知道三件事:
自由度(Degrees of Freedom, DoF)
自由度就是描述物体运动状态所需的独立变量个数。一个自由漂浮的刚体有 6 个自由度:3 个平移 + 3 个旋转。一个机械臂的每个关节通常贡献 1 个自由度(旋转或平移)。
MuJoCo 里,自由度是建模的基础。你定义好关节类型,引擎自动计算系统的总自由度。我记得有一次帮学生调模型,他死活跑不通,最后发现是关节类型写错了,自由度对不上。嗯,这种坑踩过一次就记住了。
约束(Constraints)
约束就是限制物体运动的规则。关节是约束,接触也是约束。MuJoCo 把约束统一处理成「等式或不等式条件」,然后用优化方法求解。
举个例子:你让一个球掉到地上,球不能穿透地面——这就是一个不等式约束。MuJoCo 用凸优化来保证约束严格满足,不会出现「穿模」的情况。
积分器(Integrator)
积分器是物理引擎的「时间推进器」。它根据当前状态和受力,算出下一时刻的状态。MuJoCo 支持多种积分器:
| 积分器类型 | 特点 | 适用场景 |
|---|---|---|
| Euler | 简单、快,但精度低 | 快速预览、低精度需求 |
| RK4 | 精度高,计算量稍大 | 需要精确轨迹的场景 |
| Implicit | 稳定,适合刚性系统 | 机器人接触、抓取 |
我个人习惯在机器人仿真中优先用 Implicit 积分器。它虽然计算量稍大,但稳定性好,尤其在处理接触时不会出现「弹簧效应」——就是那种物体明明该停住,却还在微微抖动的现象。
小技巧: 刚开始调试模型时,先用 Euler 积分器快速验证逻辑,确认没问题后再切到 Implicit 做精确仿真。这样能省不少时间。
物理引擎工作流程
下面这张图展示了 MuJoCo 仿真一个时间步的核心流程。我建议你仔细看看,理解了这个流程,后面调参就心里有数了。
安装与第一个仿真程序
好了,理论说够了,咱们来点实际的。安装 MuJoCo 其实很简单,我推荐用 Python 接口,方便调试。
安装步骤
- 安装 MuJoCo 本体:从 GitHub 下载最新版,或者直接用 pip 安装 Python 绑定。
- 安装 mujoco-py:
pip install mujoco(注意,新版本已经统一为mujoco包)。 - 验证安装:运行一个简单的测试脚本。
注意: 如果你用的是 Windows,建议用 WSL2 或者直接上 Linux。MuJoCo 在 Windows 上也能跑,但有些底层优化在 Linux 上更稳定。我曾经在 Windows 上折腾了半天,最后切到 Ubuntu 一次搞定。
第一个仿真程序
下面这个程序会创建一个简单的摆锤模型,然后仿真 5 秒钟。代码不长,但包含了 MuJoCo 仿真的核心步骤。
import mujoco
import numpy as np
# 创建模型 XML
xml = """
<mujoco>
<worldbody>
<body name="pendulum" pos="0 0 0">
<joint name="hinge" type="hinge" pos="0 0 0" axis="0 1 0"/>
<geom name="rod" type="capsule" fromto="0 0 0 0 0 -0.5" size="0.02" rgba="0.8 0.2 0.2 1"/>
<geom name="mass" type="sphere" pos="0 0 -0.5" size="0.05" rgba="0.2 0.2 0.8 1"/>
</body>
</worldbody>
</mujoco>
"""
# 创建模型和数据
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
# 仿真 5 秒,步长 0.01 秒
duration = 5.0
steps = int(duration / model.opt.timestep)
for i in range(steps):
mujoco.mj_step(model, data)
if i % 100 == 0: # 每 100 步打印一次
print(f"Time: {data.time:.2f}s, Angle: {data.qpos[0]:.3f} rad")
print("仿真完成!")
这段代码做了几件事:
- 用 XML 定义了一个摆锤模型,包含一个铰链关节和一个杆+重物
- 创建
MjModel和MjData对象——这是 MuJoCo 的核心数据结构 - 循环调用
mj_step推进仿真 - 每 100 步输出一次当前角度
你运行之后会看到角度在变化,这就是物理引擎在计算摆锤的摆动。简单吧?但背后涉及了自由度计算、约束求解、积分推进——就是咱们刚才讲的那一套。
调试建议: 第一次跑仿真时,把 model.opt.timestep 打印出来看看。默认是 0.002 秒,如果你觉得仿真太慢或太快,可以调整这个值。但注意,步长越大,精度越低。
本章小结
这一章咱们走了一遍 MuJoCo 的入门流程。你知道了它是什么、为什么选它、核心概念怎么理解,还亲手跑起了第一个仿真。别小看这个摆锤,它包含了物理引擎最核心的要素:自由度、约束、积分器。