
简介一份面向机器人学、运动控制与强化学习开发者的Mujoco CMake项目资源包展示了如何通过CMake配置Mujoco库并完成基础动力学仿真计算。包内共217个文件压缩后约26.29MB涵盖cmake配置脚本、cpp示例代码、h头文件、xml/urdf模型描述、stl几何网格、json数据及构建中间产物如o、out、bin等目录结构与构建流程紧密对应适合初学者对照学习构建配置与模拟步骤也可供进阶者参考项目组织方式。资源已获得311人学习关注。内容预览显示其中包含编译产物与依赖库以及CMake编译器检测文件说明该资源整合了完整的构建与依赖信息。通过阅读代码示例和模型文件可以快速理解Mujoco的mjLoadXML、mj_step等核心API调用方式以及CMakeLists.txt中find_package与target_link_libraries的衔接逻辑从而掌握从源码到可执行程序的完整链路具有较强的实操参考价值。1. mujoco 使用cmake 的简单动力学计算为什么你按文档写了 CMakeLists 还是编不过物理仿真里做动力学计算MuJoCo 几乎是绕不开的名字。它能把一个多关节机器人模型丢进去算前向动力学给力矩求加速度、逆向动力学给轨迹求力矩而且数值稳定性比一般刚体引擎好不少。但很多人在第一步就卡住了——不是不会写动力学代码而是不知道怎么用 CMake 把这个库干净地链接进自己的项目。网上搜“mujoco cmake”出来的多半是官方仓库里的测试构建抄过来一编就报找不到头文件、链接不到 glfw甚至干脆不认识 mujoco 这个 target。这事本质上不是动力学难而是 CMake 的库发现机制没理顺。这篇就把从源码拿到能跑通 mj_forward 的最小工程讲清楚适合已经装好 CMake、想把自己刚写的控制算法接进 MuJoCo 的开发者也适合被官方构建脚本绕晕的新手。2. 构建 MuJoCo 的两种路径源码编译和预编译库CMake 怎么选2.1 为什么不能只靠 find_packageMuJoCo 的官方仓库本身是用 CMake 构建的但它并没有提供类似find_package(MuJoCo REQUIRED)的开箱即用配置。这一点和很多纯头文件库不一样你写了这一句CMake 大概率会直接报Could not find a package configuration file。常见做法是走两条路路径 A把 MuJoCo 作为 CMake 子目录加进你的工程用add_subdirectory直接编源码路径 B用官方发布的预编译包自己在 CMake 里手动指定头文件路径和库文件路径。我一般建议本地验证用路径 A因为 CMake 会把库的 target 连同依赖一起导出来省掉手动找 glfw、fontconfig 这些传递依赖的麻烦。路径 A 的最小工程结构长这样my_control/ ├── CMakeLists.txt ├── src/ │ └── main.cpp └── third_party/ └── mujoco/ # 克隆官方仓库放这里对应的 CMakeLists.txt 第一版cmake_minimum_required(VERSION 3.16) project(my_control CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(third_party/mujoco) add_executable(my_control src/main.cpp) target_link_libraries(my_control PRIVATE mujoco)这里的mujoco就是 MuJoCo 仓库里定义的 target。你不需要手动写include_directories因为add_subdirectory会把库的 include 路径以 interface 属性挂在 target 上链接时自动带上。如果你的项目同时要渲染可视化还需要 linkglfw这个后面避坑章细说。2.2 第一次编译必然遇到的三个问号源码编译不是一次过的第一次跑cmake .. cmake --build .你至少会遇到三类问题。第一类是 CMake 版本太老MuJoCo 源码里用了较新的 CMake 语法版本低于 3.16 基本直接报语法错误第二类是缺依赖MuJoCo 在 Linux 上编译会去找X11、glfw和fontconfig第三类是下载测试资源失败仓库里带了很多模型文件如果网络不好构建会在 fetch 阶段卡很久。我通常的做法是先关掉测试和渲染只编核心库cmake -S . -B build \ -DMUJOCO_BUILD_EXAMPLESOFF \ -DMUJOCO_BUILD_TESTSOFF \ -DMUJOCO_BUILD_SIMULATEOFF \ -DCMAKE_BUILD_TYPEReleaseMUJOCO_BUILD_EXAMPLES关掉官方示例MUJOCO_BUILD_TESTS关掉单元测试MUJOCO_BUILD_SIMULATE关掉仿真可视化程序。这三个开关能省掉大半编译时间因为 simulate 会拉 glfw 和相关资源tests 会拉一堆模型 XML。只留核心库的话一般两三分钟就能编完。2.3 预编译库路径什么时候该放弃源码编译源码编译虽然干净但有两个硬伤一是编译耗时二是你想给别的机器分发的时候源码构建链太长。这时候用预编译库更实际。预编译库的结构通常是mujoco/ ├── include/ │ └── mujoco/ │ └── mujoco.h ├── lib/ │ ├── libmujoco.so │ └── cmake/ │ └── MujocoConfig.cmake └── bin/ └── simulate注意这里的lib/cmake目录说明预编译包是带了 CMake config 文件的可以find_package。但前提是你得告诉 CMake 去哪里找这个目录。常见做法是设置环境变量或直接传参数cmake -S . -B build -DMujoco_DIR/path/to/mujoco/lib/cmake然后在 CMakeLists.txt 里写find_package(Mujoco REQUIRED) target_link_libraries(my_control PRIVATE Mujoco::mujoco)这里有个非常容易翻车的点预编译包里的MujocoConfig.cmake可能在版本命名上有差异有的写Mujoco有的写mujoco大小写敏感。找不到时先确认路径下有没有这个文件没有就直接用add_subdirectory源码编别跟 CMake 的命名的玄学较劲。3. 最小动力学计算程序用 mj_forward 算一次前向动力学3.1 从模型 XML 到 mjData写动力学计算第一步是把模型描述文件加载成 MuJoCo 的mjModel。模型描述用 MJCF 格式和 URDF 比MJCF 写起来更紧凑而且一个文件能同时描述几何、惯量、执行器和接触参数。我这里用一个最简单的单摆模型两根杆一个关节mujoco modelsimple_pendulum option gravity0 0 -9.81/ worldbody light pos0 0 3/ body namelink1 pos0 0 0 freejoint namej0/ geom typecapsule size0.05 fromto0 0 0 0 0 1 mass1/ /body /worldbody /mujocofreejoint表示这个 body 相对于世界坐标系有 6 个自由度3 平移 3 旋转配合mj_forward就能算出重力作用下的加速度。注意geom的mass要显式给否则 MuJoCo 会按几何体积和默认密度推一个惯量算出来的加速度会和你预期差很多。3.2 完整可编译的 main.cpp加载模型后前向动力学只需三步mj_makeData创建数据mj_forward计算读m-qacc取加速度。完整代码如下#include mujoco/mujoco.h #include cstdio #include cstring int main() { char error[1024] {0}; mjModel* m mj_loadXML(simple_pendulum.xml, nullptr, error, 1024); if (!m) { std::fprintf(stderr, load model failed: %s\n, error); return 1; } mjData* d mj_makeData(m); // 把关节角速度设为零只让重力起作用 for (int i 0; i m-nv; i) { d-qvel[i] 0.0; } // 前向动力学输入状态和力矩输出加速度 mj_forward(m, d); // 打印广义坐标对应的加速度 for (int i 0; i m-nv; i) { std::printf(qacc[%d] %f\n, i, d-qacc[i]); } mj_deleteData(d); mj_deleteModel(m); return 0; }mj_loadXML的第一个参数是模型文件路径第二个是默认配置一般传nullptr。error缓冲区必须给够1024 字节是常见的做法。mj_makeData会根据m-nq和m-nv分配内存不用手动管理最后用mj_deleteData释放即可。mj_forward内部做的计算包括运动学递推、惯性矩阵组装、科氏力和重力项计算、接触求解、前向动力学求解。对自由度为 6 的单摆qacc会输出 6 个值前 3 个是线加速度后 3 个是角加速度。如果只想要绕关节轴的角加速度要自己按模型定义去索引MuJoCo 不会帮你把广义坐标拆成“关节 浮动基座”分开输出。3.3 从 qpos 到动力学结果初始化状态的正确姿势很多人算出来的加速度不对不是 MuJoCo 的问题是qpos和qvel没配对。MuJoCo 的qpos对 freejoint 存的是 7 维四元数 位置而qvel存的是 6 维角速度 线速度维度不一样。如果你直接往qpos里填欧拉角mj_forward算出来的东西一定错。正确做法是用现成接口// 把四元数设为单位四元数位置设为 (0, 0, 0) d-qpos[0] 1.0; // w d-qpos[1] 0.0; // x d-qpos[2] 0.0; // y d-qpos[3] 0.0; // z d-qpos[4] 0.0; // x pos d-qpos[5] 0.0; // y pos d-qpos[6] 0.0; // z pos这段代码的价值在于让你意识到动力学计算的结果对初始状态极度敏感。四元数顺序是w, x, y, z不是x, y, z, w写反了整个动力学就全乱。这种错误编译器查不出来只能靠打印d-qacc里的值肉眼判断——如果你发现力没作用也能算出一堆离谱的加速度十有八九是这里写反了。4. CMake 构建避坑链接错误、头文件找不到、glfw 依赖的 3 个常见问题4.1 现象fatal error: mujoco/mujoco.h: No such file or directory这是最典型的翻车现场。头文件找不到核心原因是 CMake 没有把 include 路径传给编译器。如果你用的是add_subdirectory方式这个错误极少出现因为 target 的 interface include 路径已自动传播。但如果你用了target_include_directories手动指定路径写错就会报这个错。原因通常是两种一是路径写到了mujoco内层目录如/path/to/mujoco/include/mujoco而实际应该在include这一层二是你把include_directories写在了add_subdirectory之前而 MuJoCo 的头文件是在调用后才生成的源码构建时需要mujoco.h由 CMake 配置阶段生成。解决优先依赖 target 传播不要手动 include。实在要自定义用target_include_directories(my_control PRIVATE ${CMAKE_SOURCE_DIR}/third_party/mujoco/include)此刻你不需要额外的/mujoco后缀因为代码里写的是#include mujoco/mujoco.h编译器会在include目录下找mujoco/mujoco.h这个相对路径。4.2 现象undefined reference tomj_loadXML头文件找到了但链接报错。原因MuJoCo 核心库没被传到链接器或者链接顺序不对。静态库的链接顺序在 CMake 里通常不用管因为target_link_libraries会自动处理依赖顺序。但如果库本身没编出来那自然找不到符号。最常见的原因是你开了MUJOCO_BUILD_EXAMPLESON或者SIMULATEON编译到一半出错了核心库还没编完就停了。解决先单独编核心库cmake --build build --target mujoco -j4这里mujoco是核心库 target 的名字。编完后再编你的可执行文件。如果还是报这个错误检查你链接的库名字源码构建编出来的是libmujoco.soLinux或libmujoco.dylibmacOS不是别的名字。Linux 下还有一类特殊问题如果你自己find_library找到的是系统里的老版本库比如之前apt install过 libmujoco那 CMake 可能链接到了错误的位置。排查方法是在 CMakeLists 里打印message(STATUS Mujoco library: ${Mujoco_LIBRARIES})对比输出的路径是不是你构建目录里的那个。4.3 现象链接 glfw 时报告glfw: No such file or directory这不是 MuJoCo 的问题是 simulate 演示程序需要窗口环境而你的机器没有 glfw 开发包。如果你只是想算动力学根本不需要 glfw。正确做法是把 simulate 关掉只编译核心库。如果你确实需要可视化渲染那在 Ubuntu 系上需要sudo apt install libglfw3-dev libxinerama-dev libxcursor-dev libxrandr-dev libxi-dev然后重新配置构建。macOS 上通常已经自带 glfw 或者通过 Homebrew 安装。Windows 上则是头文件和库路径需要加到系统环境变量。这里有个小坑MuJoCo 源码构建时对 glfw 的检测是找find_package(glfw3)如果系统里的 glfw 版本比较新或比较旧版本号不匹配也会跳过渲染组件表现就是编译不受影响但simulatetarget 没生成。这时耐心查CMakeCache.txt里的glfw3_DIR变量指向不对就手动改。4.4 现象编译 Megabytes 级报错Eigen头文件冲突MuJoCo 内部用了 Eigen如果你自己的工程也引入了 Eigen两套头文件相遇可能爆出成片模板报错。本质原因是 Eigen 版本不匹配。解决方式有两种一种是让你的工程只用 MuJoCo 导出的 EigenMuJoCo 源码里自带了一个 Eigen 副本另一种是统一用系统 Eigen 并确保版本一致。我向来建议做法是别在自己工程的公共头文件里混用两套 Eigen。MuJoCo 的 Eigen 封装在mujoco/mjxmacro.h内部不需要你直接 include所以只要不在自己的头文件里#include Eigen/Dense就不会有冲突。真需要 Eigen 就统一用 MuJoCo 自带的那个编译时把 include 顺序调成 MuJoCo 优先target_include_directories(my_control BEFORE PRIVATE third_party/mujoco/_deps/eigen-src)注意BEFORE关键字它能让 MuJoCo 自带的 Eigen 优先于系统路径里的版本被找到。5. 参数怎么调自由度、时间步长和数值稳定性5.1 模型参数对动力学结果的决定性影响写完程序跑通之后你会面对更头疼的问题算出来的加速度对不对这里有几个关键参数会直接影响数值结果而且它们不在代码里在 MJCF 文件里。第一个是option.gravity写错方向重力加速度就全反了。第二个是geom的density和mass不指定mass时 MuJoCo 会用几何体体积乘密度推算质量同一个几何形状密度差一倍质量差一倍角加速度也差一倍。第三个是option.timestep它决定了数值积分的步长虽然mj_forward本身不积分但在整个仿真循环里步长过大会导致高频振荡步长过小则计算量暴涨。下面是一组我用过比较稳的参数适合做机器人控制验证option timestep0.002 solverNewton iterations50 tolerance1e-10/solver有多个选项其中Newton是精确但对接触多的模型较慢PGS快但精度低。iterations控制迭代次数tolerance是收敛判据。只做动力学不接触仿真时这两个值影响不大。5.2 最小时间步长不是越小越好有个反直觉的结论步长太小可能导致静摩擦仿真时间过长或者造成约束求解的数值误差累积。你该做的第一件事不是调小步长而是打印能量残差来看数值稳定性// 仿真循环中打印动能和势能变化 for (int step 0; step 1000; step) { mj_step(m, d); double ke mj_kineticEnergy(m, d); double pe mj_potentialEnergy(m, d); if (step % 100 0) { std::printf(step %d: KE%f PE%f total%f\n, step, ke, pe, ke pe); } }如果总能量在涨说明步长偏大或求解器不收敛。如果总能量单调下降说明阻尼参数或摩擦系数设得不对。这两种情况都不是调步长能解决的——一个要减小步长一个要查模型参数。这个诊断手段比盯加速度数值靠谱得多。5.3 把动力学包成可复用的 CMake 静态库你不想每次都重新编主程序时可以把动力学部分抽成一个静态库add_library(pendulum_dynamics STATIC src/dynamics.cpp) target_link_libraries(pendulum_dynamics PUBLIC Mujoco::mujoco) target_include_directories(pendulum_dynamics PUBLIC include) # 主程序只链接自己的库 add_executable(run_sim src/main.cpp) target_link_libraries(run_sim PRIVATE pendulum_dynamics)核心点是PUBLIC和PRIVATE的语义PUBLIC意思是链接方也会继承 MuJoCo 的 include 和库依赖PRIVATE意思是只有这个库自己用到。把 MuJoCo 写成PUBLIC应用层就不用再操心 MuJoCo 的链接细节这也是做大项目时最常见的一种封装习惯。如果你发现下游编译时还是找不到 MuJoCo多半是这里写成PRIVATE了。6. 进阶验证用 CMake 跑通单摆周期测试确认动力学没写错模型和程序都跑通之后真正检验你动力学算得对不对的方法是用物理规律做闭环验证——而不是盯着屏幕上打印的数值觉得“看起来差不多”。单摆的线性近似周期是T 2π√(L/g)对小角度摆动误差在 1% 以内。用 MuJoCo 跑仿真测量过零点的时间间隔然后和理论周期对比误差在 2% 以内就说明你的模型参数和计算链路是靠谱的。这个测试用 CMake 的add_test跑起来以后每次改模型参数都能自动回归。先抽出周期测量的逻辑// 测量过零点的时刻返回两次经过 qpos0 的时间差 double measure_period(mjModel* m, mjData* d, double initial_qpos) { d-qpos[0] initial_qpos; // 注意 freejoint 下 qpos 偏移 const double target 0.0; const double timestep 0.001; double last_time -1.0; double first_cross -1.0; for (int i 0; i 20000; i) { mj_step(m, d); double qpos d-qpos[0]; // 检测从正到负的过零 if (last_time 0 qpos target d-qpos_prev[0] target) { if (first_cross 0) { first_cross d-time; } else { return d-time - first_cross; } } last_time d-time; } return -1.0; }注意这里有个被很多人忽略的点d-qpos_prev存的是上一步的 qpos必须用它来判断过零方向否则你会在轻微数值抖动处误触发多次。初始角度给0.2 rad左右既不算小到噪声干扰判断又不算大到超出线性近似范围。CMake 里注册这个测试add_executable(test_period tests/test_period.cpp) target_link_libraries(test_period PRIVATE pendulum_dynamics) add_test(NAME pendulum_period COMMAND test_period)然后跑ctest就能看到周期测试的结果。这里有个建议把初始角度、期望周期、容差上限都作为命令行参数传进去方便后面做参数扫描比如验证0.1、0.2、0.5三个角度下周期的变化趋势是否符合大角度摆的周期拉长规律——这比单个角度测试更能暴露模型参数错误。验证时常见的坑是测出来的周期完全不对但数值稳定、不报错。这时先检查重力方向、质量和杆长而不是代码逻辑。我自己的血泪经验是有一次周期偏大到两倍查了半天发现是freejoint让杆同时获得了平动自由度重心往下掉了单摆变成了双摆。把freejoint换成hinge固定转轴周期才回到理论值。这种错误只有用物理规律验证才抓得到看数值对错是看不出来的。最后提醒一句无论你是把 MuJoCo 当控制系统里的动力学预测器还是当机器人强化学习的仿真环境CMake 这一层的工程习惯值得一次做对——把模型文件路径、库依赖、测试注册都写进同一个 CMakeLists换了机器一条命令就能复现。这比什么都重要。希望帮到你。本文还有配套的精品资源点击获取