第 1 章 第一次可信的 MuJoCo 仿真

发布时间:2026/8/3 9:47:55
第 1 章 第一次可信的 MuJoCo 仿真 第 1 章 第一次可信的 MuJoCo 仿真本书示例代码仓库libing403/mujoco_tutorial一个仿真程序“跑起来”并不等于结果可信。本章不从复杂人形模型开始而是用单摆回答五个最基本的问题MuJoCo 接收什么、计算什么、状态放在哪里、一步如何推进、怎样判断输出不是一串没有意义的数字。1.1 学习目标完成本章后你应该能够独立编译并运行一个无窗口 C 仿真程序解释 MJCF、mjModel、mjData和mj_step的职责区分模型参数、仿真状态、控制输入和派生结果说明 MuJoCo 在研究、控制、强化学习、虚拟硬件和 sim-to-real 中的工程位置正确处理模型编译错误和对象释放通过改变初态、阻尼和时间步验证单摆结果的物理合理性。前置知识只有 C/C 指针、命令行和最基本的角度/角速度概念。1.2 MuJoCo 究竟做什么MuJoCo 是多关节接触动力学引擎。它不是机器人中间件也不会替你生成轨迹或决定电机目标。应用程序给它模型、当前状态和输入它计算加速度、约束力、传感器量并把状态积分到下一个时刻。解析与编译MJCF/URDF 文件mjModel动力学流水线mjData: qpos qvel ctrlqacc contact sensordata数值积分对机器人系统最常见的闭环是读取 qpos/qvel/sensordata ↓ 控制器计算 ctrl ↓ MuJoCo 计算力、接触和加速度 ↓ 积分得到下一时刻状态渲染并不在这个闭环中。无窗口仿真更适合算法开发、性能测试和服务器部署因此本书先建立数值主线后面再加入可视化。1.3 MuJoCo 在研究机构与机器人公司中的位置在教学示例中MuJoCo 可能看起来只是“读入 XML然后调用mj_step”。但在研究机构和头部机器人公司中它更常处在模型、算法与真实硬件之间承担“可计算的虚拟机器人”角色。log 回放与参数反标CAD / URDF / 标定数据MJCF 可执行模型MuJoCo 动力学与接触控制 / MPC / 状态估计并行 rollout / 强化学习真实机器人CI 回归与故障注入研究机构更关心快速验证新算法、进行大规模对照实验并保证论文可复现公司则更关心模型与硬件接口一致、控制器上机前验证、故障注入、版本回归和 sim-to-real。两者的工具链不同但对“模型可信”的要求完全一致。1.3.1 Google DeepMind从单机动力学到 GPU 训练和在线 MPCGoogle DeepMind 是 MuJoCo 开源项目的主要维护者也是公开工具链最完整的使用者之一层次公开工具典型用途单机精确仿真MuJoCo C/Python建模、接触、控制器、回归测试任务与研究环境dm_control统一 observation、action、reward 和评估GPU 批量仿真MJX / MuJoCo Warp大量并行环境和策略训练机器人学习工具链MuJoCo Playground四足、人形、操作和 sim-to-real滚动时域规划MuJoCo MPCiLQG、预测采样、实时重规划其典型流程是先在 MuJoCo Menagerie 中维护高质量机器人模型再在 MJX 的大量并行环境中训练回到 CPU MuJoCo 做交叉检查最后部署到真实机器人。MuJoCo Playground 已公开四足、双足、机械臂、灵巧操作和视觉环境其中包括本书第 37 章使用的 Unitree G1 生态。MuJoCo MPC 则展示了另一条路线不一定先离线训练一个神经网络也可以在运行期不断使用 MuJoCo rollout 评估候选动作。Google 公开的 Language to Rewards 工作将语言生成的奖励函数与 MJPC 连接用于四足和灵巧操作并对真实机械臂任务进行了验证。1.3.2 Unitree用与真机一致的 SDK 消息连接仿真Unitree 公开的unitree_mujoco体现了公司中常见的“虚拟硬件”架构。MuJoCo 进程不直接内嵌业务控制器而是通过 Unitree SDK2/DDS订阅LowCmd并映射到仿真 actuator发布LowState、IMU、机身位置和速度保持电机编号、单位和控制周期与硬件一致让同一个控制程序切换仿真与真实机器人后端。控制器进程 MuJoCo 进程 read LowState ----- DDS ------ qpos/qvel/IMU 运行原有控制算法 write LowCmd ------ DDS ----- ctrl - mj_step这与“另外打开一个simulate窗口”不同这里有明确的进程间通信协议因此控制器确实能操作仿真进程中的机器人。企业还会在这一层注入网络延迟、丢包、传感器噪声、执行器饱和和故障状态。1.3.3 OpenAI 的历史案例批量仿真与 domain randomizationOpenAI 早期机器人研究曾将mujoco-py描述为核心工具并专门增强批量仿真和高性能纹理随机化。Dactyl 灵巧手代表了一种影响深远的 sim-to-real 思路在仿真中生成远超实机可承受的交互数据随机化质量、摩擦、视觉、延迟和外部扰动不要求策略只在某一套“完美参数”下成功将在大量可能世界中都稳健的策略迁移到真实 Shadow Hand。这是公开的历史案例不能由此推断 OpenAI 当前内部所有机器人项目的技术栈。但“用参数分布代替单一参数拟合”的方法已经成为机器人学习的基础工程思路。1.3.4 Berkeley 等研究机构sim-to-real 与跨仿真器复核高校实验室常用 MuJoCo 建立可复现的控制和学习 benchmark。Berkeley/Google 公开的 ROBEL 平台在 MuJoCo 中训练 D’Claw 灵巧手和 D’Kitty 四足策略并将具有外部扰动和随机地形的策略迁移到硬件。另一种更谨慎的做法是跨仿真器复核在 Isaac Gym/Isaac Lab 中训练再不经重新训练地将策略放入 MuJoCo。若策略在数值积分、接触和求解器实现不同的引擎中仍然稳定它更可能没有利用单一引擎的数值漏洞。Berkeley Humanoid 的公开研究就包含这类 sim-to-sim 评估。1.3.5 头部团队如何验收一个 MuJoCo 模型业界不会把“画面像真”当成模型通过的证据。常见验收至少包含五层层次关键问题典型可执行检查结构joint/frame/方向/限位是否与硬件一致name、ID、address、range 快照惯性质量、CoM、主惯量是否合理总质量、分段质量、惯量正定性执行器减速比、饱和、带宽和延迟是否接近真机阶跃、扫频、力矩—速度包络接触collision 和摩擦是否支持任务穿入、支撑力、滑移、solver 统计行为控制任务是否在扰动下仍成立站立、行走、抓取、跌倒和故障恢复因此高精度 visual mesh、简化 collision geom 和明确 inertial 通常是三套不同的数据。对控制结果影响最大的往往不是外观而是inertial collision actuator sensor delay solver1.3.6 MuJoCo 不是公司里唯一的仿真器成熟团队通常使用多仿真器和多保真度技术栈。MuJoCo 擅长刚体动力学、关节机器人、接触、导数、控制和快速 rollout但高保真摄影机数据、工厂级场景、流体、电磁、电池和精细软体往往需要其他工具。常见组合是MuJoCo 做控制、动力学、接触和学习主循环Unity、Unreal 或 Isaac Sim 做复杂场景和视觉数据ROS 2、DDS 或厂商 SDK 保持控制软件与真机接口一致实机日志回放和参数辨识不断修正 MuJoCo 模型。所以学习 MuJoCo 的目标不是认住一组 API而是建立一套可验证的机器人实验方法。本书从本章的单摆开始逐步走到模型审计、接触、控制、LQR/EKF、并行 rollout、MJX 策略优化、机械臂和 Unitree G1。1.3.7 公开案例与延伸阅读Google DeepMindOpening up a physics simulator for roboticsGoogle DeepMindMuJoCo PlaygroundGoogle ResearchLanguage to Rewards for Robotic Skill SynthesisUnitree Roboticsunitree_mujocoOpenAIFaster Physics in PythonOpenAILearning DexterityGoogle Research / BerkeleyROBELBerkeley HumanoidLearning Smooth Humanoid Locomotion上述内容只描述可以从公开仓库、论文和机构文章验证的用法。公司的完整内部技术栈通常不公开不应将一项历史论文或开源模型外推为某公司当前的唯一方案。1.4 两个核心对象1.4.1 mjModel编译后的模型mjModel保存模型拓扑、质量与惯量、几何、关节、执行器、传感器、求解器参数以及大量预计算索引。它不是 XML 文本的简单镜像而是为运行时计算组织好的低层数据结构。典型字段包括字段含义单摆示例nbodybody 数包含 world2njntjoint 数1nq广义位置数组长度1nv广义速度和力空间维度1nu控制输入数0opt.timestep物理时间步0.002 s模型通常在仿真期间保持不变。修改部分 option 是允许的但不能把mjModel当成每步随意重建的对象。1.4.2 mjData一个仿真实例mjData保存时间、状态、输入、接触、传感器输出以及动力学流水线的中间量。同一个 model 可以创建多个 data它们描述同一种机器人处在不同状态适合轨迹采样和并行 rollout。字段类型角色是否由用户直接写入time仿真时间重置/恢复状态时可写qpos广义位置可以qvel广义速度可以ctrl执行器输入控制器写入qacc广义加速度forward dynamics 计算xposbody 世界位置由运动学计算sensordata传感器拼接输出由流水线计算qpos改变后xpos不会神奇地同步改变。必须调用mj_forward或执行仿真步派生量才重新计算。这种数据导向设计避免了隐藏计算也是 MuJoCo 高性能的重要原因。1.5 阅读第一个 MJCF打开examples/01_hello/model.xmlmujocomodelpendulumoptiontimestep0.002gravity0 0 -9.81/worldbodylightpos0 -2 3/bodynamelinkpos0 0 1.5jointnamehingetypehingeaxis0 1 0damping0.05/geomtypecapsulefromto0 0 0 0 0 -1size0.05mass1//body/worldbody/mujoco逐行理解worldbody是静止世界ID 总是 0。body的原点位于世界坐标(0,0,1.5)也是关节默认位置。hinge 轴为 body 坐标系的 Y 轴因此摆在 X–Z 平面转动。capsule 从 body 原点延伸到局部(0,0,-1)长度约 1 m。geom 指定mass1编译器据几何形状推断质心和转动惯量。damping0.05产生与关节速度反向的黏性阻尼力矩。时间、长度、质量分别按 s、m、kg 理解运行时角度使用 rad。这里没有 actuator所以它是被动系统。初始位置如果恰好竖直向下重力力矩为零程序会一直输出零运动。示例在 C 中把初始角设为 0.7 rad主动打破平衡。1.6 阅读完整 main.cc本书每个示例只有一个 C 源文件。模型加载代码不封装目的是让生命周期一眼可见intmain(intargc,char**argv){if(argc!2){std::fprintf(stderr,用法: %s model.xml\n,argv[0]);returnEXIT_FAILURE;}charerror[1024]{0};mjModel*mmj_loadXML(argv[1],NULL,error,sizeof(error));if(!m){std::fprintf(stderr,无法加载 %s:\n%s\n,argv[1],error);returnEXIT_FAILURE;}mjData*dmj_makeData(m);d-qpos[0]0.7;mj_forward(m,d);while(d-time2.0){mj_step(m,d);}mj_deleteData(d);mj_deleteModel(m);}加载和编译mj_loadXML同时解析 XML 和编译模型。第二个参数是可选虚拟文件系统本例从磁盘读取所以传NULL。失败时返回NULL并把元素名称、行号或属性问题写进 error buffer。不要只打印“加载失败”而丢掉这段诊断。创建状态mj_makeData(m)根据 model 的维度分配状态和工作内存。生产程序必须检查返回值。本例模型极小为保持核心流程紧凑只做简单检查。设置初态和 forward写入qpos[0]只改变主状态。mj_forward重新计算运动学、势能、加速度和传感器等派生量但不推进time。如果下一步立即调用mj_step某些计算会再次发生这里保留 forward 是为了明确“设置状态后使数据一致”的通用规则。步进和释放mj_step完成一次正动力学和积分d-time增加timestep。data 必须先于 model 删除因为其布局和内部指针依赖 model。1.7 独立构建与运行cdexamples/01_hello cmake-S.-Bbuild-DCMAKE_BUILD_TYPERelease cmake--buildbuild-j./build/demo model.xml输出前五行类似t 0.10 q0.65403 dq-0.89118 t 0.20 q0.52386 dq-1.66939 t 0.30 q0.32596 dq-2.23353 t 0.40 q0.08646 dq-2.49511 t 0.50 q-0.16156 dq-2.40852如何读这些数初始q0.7且dq0重力使角度向零减小速度变负通过最低点后角度变负阻尼逐渐消耗机械能振幅应越来越小。如果角度单调发散、出现nan或振幅无缘无故增长就要检查模型和积分设置。可视化运行与效果../../mujoco-3.11.0/bin/simulate model.xml该命令使用发布包自带的官方simulate界面可暂停、单步、施加扰动并开启接触等可视化标志。01_hello 的真实 MuJoCo 原生渲染结果。实验完整源码以下文件与examples/01_hello/中可直接编译的版本一致。模型文件model.xmlmujocomodelpendulumoptiontimestep0.002gravity0 0 -9.81/worldbodylightpos0 -2 3/bodynamelinkpos0 0 1.5jointnamehingetypehingeaxis0 1 0damping0.05/geomtypecapsulefromto0 0 0 0 0 -1size0.05mass1//body/worldbody/mujoco程序源码main.cc#includecstdio#includecstdlib#includemujoco/mujoco.hintmain(intargc,char**argv){if(argc!2){std::fprintf(stderr,用法: %s model.xml\n,argv[0]);returnEXIT_FAILURE;}charerror[1024]{0};mjModel*mmj_loadXML(argv[1],NULL,error,sizeof(error));if(!m){std::fprintf(stderr,无法加载 %s:\n%s\n,argv[1],error);returnEXIT_FAILURE;}mjData*dmj_makeData(m);if(!d)returnEXIT_FAILURE;if(m-nq0){d-qpos[0]0.7;// 给摆一个可观察的初始偏角mj_forward(m,d);}while(d-time2.0){mj_step(m,d);if(mju_abs(d-time*10.0-mju_round(d-time*10.0))m-opt.timestep/2){std::printf(t%5.2f q%.5f dq%.5f\n,d-time,d-qpos[0],d-qvel[0]);}}mj_deleteData(d);mj_deleteModel(m);returnEXIT_SUCCESS;}构建文件CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(01_hello LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) add_executable(demo main.cc) set(MUJOCO_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../mujoco-3.11.0) target_include_directories(demo PRIVATE ${MUJOCO_ROOT}/include) target_link_directories(demo PRIVATE ${MUJOCO_ROOT}/lib) target_link_libraries(demo PRIVATE mujoco) set_target_properties(demo PROPERTIES BUILD_RPATH ${MUJOCO_ROOT}/lib)1.8 三组对照实验实验 A改变初始角将d-qpos[0]改为0.1、0.7、2.8。小角度下周期接近线性单摆公式[ T ≈ 2 π L c g , ] \ [ T\approx 2\pi\sqrt{\frac{L_c}{g}}, \ ][T≈2πgLc​​​,]其中实际周期还取决于刚体转动惯量和质心距离不能机械地把 capsule 当成质点。大角度下小角近似失效周期会变长。这个实验提醒我们仿真结果不是公式的“错误”两者可能建模假设不同。实验 B改变阻尼把damping设为0、0.05、1.00理想情况下总机械能近似守恒但离散积分仍有误差0.05欠阻尼振荡并逐渐静止1.0衰减明显是否过阻尼取决于惯量和重力线性化刚度。不要用很大 damping 掩盖控制器振荡。真实关节阻尼、摩擦和电机闭环是不同物理机制。实验 C改变 timestep分别使用0.0005、0.002、0.02s记录 2 s 时的q。不同时间步给出不同离散轨迹过大时间步可能产生明显相位误差甚至不稳定。判断时间步是否足够小应逐次减半并观察任务指标是否收敛而不是只看动画是否流畅。1.9 常见错误与诊断现象可能原因检查方法模型加载返回 NULLXML 拼写、非法属性、惯量错误完整打印 error buffer状态改了但位置没变派生量尚未更新调用mj_forward后再读xpos程序找不到共享库构建方式绕过了示例 CMake检查 CMake RPATH 和仓库 SDK摆完全不动初态正好在平衡点给 qpos 非零偏角结果快速爆炸时间步过大、质量/惯量异常缩小步长并检查编译 warning运行越久内存越大循环内反复创建 data/model初始化一次退出时配对释放MuJoCo warning 不是“可以忽略的日志”。nan、约束缓冲或惯量相关 warning 应保存当时状态并构造最小复现。1.10 从单摆迁移到机器人单摆的一个qpos会扩展成机械臂的关节向量或人形机器人的自由基座加关节状态但生命周期不变加载一个 model、为每个仿真实例创建 data、写输入、步进、读输出。真正变化的是数组索引、坐标系、约束和控制维度。机械臂通常固定基座且nq≈nv浮动基座人形的 free joint 使用 7 个位置数和 6 个速度自由度所以nq ! nv。从第一天就避免把 qpos 和 qvel 当作同长度数组是后面控制算法正确性的基础。1.11 本章小结MJCF 是高层模型源mjModel是编译后的只读运行表示。mjData保存一个实例的状态、输入、输出和工作区。修改主状态后用 forward 更新派生量mj_step才推进时间。第一次可信验证应包含初态、阻尼和时间步对照而不只是成功运行。每个 data 必须在对应 model 之前释放。1.12 练习将重力改为月球近似值-1.62 m/s²预测周期怎样变化再运行验证。删除mass增加density1000解释编译器如何得到质量和惯量。在每一步计算并记录qpos最大绝对值验证阻尼系统的峰值包络是否下降。把初始角改成 30。你预期它是 30 rad 还是 30 degree运行时 C API 的单位是什么故意把 geom size 改成负数观察并抄录完整编译错误。1.13 参考答案小角周期与1/√g成正比月球重力下周期约为地球的√(9.81/1.62)≈2.46倍刚体摆的绝对周期需用实际惯量计算。compiler 根据 geom 体积乘密度得到质量再由 capsule 几何计算质心和惯量结果可从body_mass/body_inertia检查。无外部驱动且阻尼为正时总能量整体下降离散采样的角度峰值包络也应下降单步角度绝对值不必单调。C API 运行时角度为 rad30 rad 是多圈大角度不是 30°XML 的 angle 设置不改变运行时 API 单位。loader 返回 NULLerror buffer 指向 geom 的非法 size准确文本以 3.11.0 编译器输出为准。