Wiki2026-08-19
📖参考资料: Mujoco官网:MuJoCo 官方文档
学习连接: MuJoCo 学习参考
学习视频链接【教程】2025新版mujoco建模与仿真
一、环境安装
MuJoCo 常见有三种安装方式,按使用场景选择即可:
| 安装方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 官方 Release 二进制 | 安装简单,包含 simulate、compile 等工具,适合 C/C++ 开发 | 需要手动配置环境变量;平台和架构必须匹配 | 使用官方示例、C/C++、命令行工具 |
Python pip 包 | 安装最快,自动携带 MuJoCo 动态库;便于与 Gymnasium、强化学习库结合 | 不提供完整的源码和桌面工具;主要面向 Python | Python 仿真、强化学习、快速验证 |
| 源码编译 | 可修改源码、启用调试和自定义编译选项 | 编译时间长,依赖多,维护成本高 | 修改 MuJoCo 内核、底层开发、贡献代码 |
1.1 下载官方Release版本
MuJoCo 的官方二进制 Release 可从 GitHub Releases 下载。以下以 Linux x86_64 为例(版本号请替换为 Release 页面中的最新版本):
bash
# Ubuntu/Debian 依赖
sudo apt update
sudo apt install -y libglfw3 libglew2.2 libosmesa6 libxrandr2 libxinerama1 libxcursor1
# Fedora 依赖(与上面的 apt 命令二选一)
sudo dnf install -y glfw glew libglvnd-glx mesa-libOSMesa libXrandr libXinerama libXcursor
# 下载并解压官方 Release
cd ~/Applications
wget https://github.com/google-deepmind/mujoco/releases/download/3.7.0/mujoco-3.7.0-linux-x86_64.tar.gz
tar -xzf mujoco-3.7.0-linux-x86_64.tar.gz
# 配置环境变量(也可将下面三行写入 ~/.bashrc)
export MUJOCO_HOME="$HOME/Applications/mujoco-3.7.0"
export PATH="$MUJOCO_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$MUJOCO_HOME/bin:${LD_LIBRARY_PATH:-}"
# 4. 验证安装:查看版本并启动官方模拟器
compile -h
simulate
如果希望每次打开终端都自动生效,将环境变量追加到 shell 配置文件:
bash
cat >> ~/.bashrc <<'EOF'
export MUJOCO_HOME="$HOME/Applications/mujoco-3.7.0"
export PATH="$MUJOCO_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$MUJOCO_HOME/bin:${LD_LIBRARY_PATH:-}"
EOF
source ~/.bashrc
说明:Release 压缩包已经包含 MuJoCo 的运行库和 simulate、compile 等工具,不需要再编译源码。若使用 ARM64、macOS 或 Windows,请在 Releases 页面选择对应平台的压缩包,并相应调整解压目录和动态库环境变量。
1.2 使用 Python pip 安装
MuJoCo 官方 Python 绑定会自动下载并管理对应的 MuJoCo 二进制库,通常不需要手动设置 MUJOCO_HOME:
bash
python3 -m venv .venv
source .venv/bin/activate # Fedora/Ubuntu
python -m pip install --upgrade pip
python -m pip install mujoco
# 验证
python -c "import mujoco; print(mujoco.__version__)"
如果使用 Gymnasium 等强化学习环境,可一并安装:
bash
python -m pip install "gymnasium[mujoco]"
1.3 从源码编译
源码编译适合需要修改 MuJoCo 内核或进行底层调试的场景。先安装 Git、CMake、C/C++ 编译器及图形依赖:
bash
# Ubuntu/Debian
sudo apt update
sudo apt install -y git cmake build-essential libglfw3-dev libglew-dev libosmesa6-dev
# Fedora
sudo dnf install -y git cmake gcc-c++ glfw-devel glew-devel mesa-libOSMesa-devel
然后从官方仓库编译:
bash
git clone https://github.com/google-deepmind/mujoco.git
cd mujoco
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
# 可选:运行测试
ctest --test-dir build --output-on-failure
源码构建完成后,可将 build(或安装后的 bin、lib 目录)加入 PATH 和 LD_LIBRARY_PATH;具体目录以当前版本的 CMake 输出为准。
二、搭建机器人模型
2.1 使用SW转URDF,URDF转MJCF
- SW ->URDF
详情请见Vinvi战队ROS2教程ROS2机器人操作系统教程
730 - URDF->MJCF
bash
#调用
/mujoco安装路径/bin/compile /you_URDF_file_path /MJCF_path
#示例
/home/xiaofang/Applications/mujoco-3.7.0/bin/compile /you_URDF_file_path /MJCF_path
三、使用C++开发
main 运行方式
1. 使用官方的 bassic.cc
打开模型
c++
//模型打开
char error[1000] = "Could not load binary model";
m = mj_loadXML("../models/car.xml", 0, error, 1000);
2. 使用AI改造的main.cc (更加简洁,只有基本的配置)
c++
//maoi.cc
#include "simulator/simulator.h"
#include "control/control.h"
int main()
{
Simulator sim;
// 1. 加载模型
if (!sim.load_model("../models/model.xml"))
return 1;
// 2. 初始化窗口和可视化
if (!sim.init_visual(1200, 900, "WheelLeg Demo"))
return 1;
// 3. 设置相机跟踪
sim.set_camera_track("base_link", 2.0);
// 4. 初始化控制器(手柄/IMU/PID)
CarController controller;
controller.Init(sim.model(), sim.data());
// 5. 进入主循环
sim.run([&](mjModel* m, mjData* d) {
controller.Main_Control_Update();
});
return 0;
}
simulator.cpp
c++
#include "simulator/simulator.h"
#include <iostream>
Simulator::~Simulator()
{
// 确保 OpenGL 上下文活跃再释放 MuJoCo 渲染资源
if (window_) glfwMakeContextCurrent(window_);
if (visual_ready_) mjr_freeContext(&con_);
if (visual_ready_) mjv_freeScene(&scn_);
if (d_) mj_deleteData(d_);
if (m_) mj_deleteModel(m_);
if (window_) glfwDestroyWindow(window_);
// Linux + NVIDIA 下 glfwTerminate 可能崩溃,仅 macOS/Windows 调用
#if defined(__APPLE__) || defined(_WIN32)
if (window_) glfwTerminate();
#endif
}
// ==================== 模型加载 ====================
bool Simulator::load_model(const char* xml_path)
{
char error[1000] = "Could not load binary model";
m_ = mj_loadXML(xml_path, nullptr, error, sizeof(error));
if (!m_) {
std::cerr << "[ERROR] 模型加载失败: " << error << std::endl;
return false;
}
d_ = mj_makeData(m_);
return true;
}
// ==================== 可视化初始化 ====================
bool Simulator::init_visual(int width, int height, const char* title)
{
if (!glfwInit()) {
std::cerr << "[ERROR] GLFW 初始化失败" << std::endl;
return false;
}
window_ = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!window_) {
std::cerr << "[ERROR] 窗口创建失败" << std::endl;
return false;
}
glfwMakeContextCurrent(window_);
glfwSwapInterval(1);
// 通过 user pointer 绑定实例,供静态回调转发
glfwSetWindowUserPointer(window_, this);
// 注册回调
glfwSetKeyCallback(window_, key_cb);
glfwSetMouseButtonCallback(window_, mouse_cb);
glfwSetCursorPosCallback(window_, move_cb);
glfwSetScrollCallback(window_, scroll_cb);
// MuJoCo 可视化初始化
mjv_defaultCamera(&cam_);
mjv_defaultOption(&opt_);
mjv_defaultScene(&scn_);
mjr_defaultContext(&con_);
mjv_makeScene(m_, &scn_, 2000);
mjr_makeContext(m_, &con_, mjFONTSCALE_150);
visual_ready_ = true;
return true;
}
// ==================== 相机 ====================
void Simulator::set_camera_track(const char* body_name, double distance)
{
cam_.type = mjCAMERA_TRACKING;
cam_.trackbodyid = mj_name2id(m_, mjOBJ_BODY, body_name);
cam_.distance = distance;
}
// ==================== 主循环 ====================
void Simulator::run(std::function<void(mjModel*, mjData*)> update)
{
while (!glfwWindowShouldClose(window_))
{
// 仿真:每帧跑 1/60 秒
mjtNum sim_start = d_->time;
while (d_->time - sim_start < 1.0 / 60.0)
{
if (update) update(m_, d_);
mj_step(m_, d_);
}
// 渲染
mjrRect viewport = {0, 0, 0, 0};
glfwGetFramebufferSize(window_, &viewport.width, &viewport.height);
mjv_updateScene(m_, d_, &opt_, nullptr, &cam_, mjCAT_ALL, &scn_);
mjr_render(viewport, &scn_, &con_);
glfwSwapBuffers(window_);
glfwPollEvents();
}
}
// ==================== 键盘回调 ====================
void Simulator::on_key(int key, int act)
{
// 转发给外部处理器
if (key_handler_) key_handler_(key, act);
// Backspace 重置仿真
if (act == GLFW_PRESS && key == GLFW_KEY_BACKSPACE) {
mj_resetData(m_, d_);
mj_forward(m_, d_);
}
}
// ==================== 鼠标按键回调 ====================
void Simulator::on_mouse_button(int button, int act)
{
btn_left_ = (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS);
btn_middle_ = (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS);
btn_right_ = (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS);
glfwGetCursorPos(window_, &lastx_, &lasty_);
}
// ==================== 鼠标移动回调 ====================
void Simulator::on_mouse_move(double xpos, double ypos)
{
if (!btn_left_ && !btn_middle_ && !btn_right_) return;
double dx = xpos - lastx_;
double dy = ypos - lasty_;
lastx_ = xpos;
lasty_ = ypos;
int width, height;
glfwGetWindowSize(window_, &width, &height);
bool shift = (glfwGetKey(window_, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
glfwGetKey(window_, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS);
mjtMouse action;
if (btn_right_) action = shift ? mjMOUSE_MOVE_H : mjMOUSE_MOVE_V;
else if (btn_left_) action = shift ? mjMOUSE_ROTATE_H : mjMOUSE_ROTATE_V;
else action = mjMOUSE_ZOOM;
mjv_moveCamera(m_, action, dx / height, dy / height, &scn_, &cam_);
}
// ==================== 滚轮回调 ====================
void Simulator::on_scroll(double xoffset, double yoffset)
{
mjv_moveCamera(m_, mjMOUSE_ZOOM, 0, -0.05 * yoffset, &scn_, &cam_);
}
simulator.h
c++
#pragma once
#include <functional>
#include <mujoco/mujoco.h>
#include <GLFW/glfw3.h>
class Simulator {
public:
Simulator() = default;
~Simulator();
// 禁止拷贝
Simulator(const Simulator&) = delete;
Simulator& operator=(const Simulator&) = delete;
/** 加载模型,失败返回 false */
bool load_model(const char* xml_path);
/** 初始化窗口和可视化 */
bool init_visual(int width, int height, const char* title);
/** 设置相机跟踪的 body */
void set_camera_track(const char* body_name, double distance = 2.0);
/** 注册额外的按键回调(在 Backspace 重置之后调用) */
void set_key_handler(std::function<void(int key, int act)> handler) { key_handler_ = handler; }
/** 进入主循环,每帧调用 update(m, d) */
void run(std::function<void(mjModel*, mjData*)> update = nullptr);
/** 模型和数据访问 */
mjModel* model() { return m_; }
mjData* data() { return d_; }
private:
// 实例回调(由静态函数转发)
void on_key(int key, int act);
void on_mouse_button(int button, int act);
void on_mouse_move(double xpos, double ypos);
void on_scroll(double xoffset, double yoffset);
/** 获取当前 viewport */
mjrRect get_viewport();
// 静态 GLFW 回调,通过 user pointer 转发到实例
static void key_cb(GLFWwindow* w, int k, int s, int a, int m) {
auto* self = static_cast<Simulator*>(glfwGetWindowUserPointer(w));
self->on_key(k, a);
}
static void mouse_cb(GLFWwindow* w, int b, int a, int m) {
auto* self = static_cast<Simulator*>(glfwGetWindowUserPointer(w));
self->on_mouse_button(b, a);
}
static void move_cb(GLFWwindow* w, double x, double y) {
auto* self = static_cast<Simulator*>(glfwGetWindowUserPointer(w));
self->on_mouse_move(x, y);
}
static void scroll_cb(GLFWwindow* w, double xo, double yo) {
auto* self = static_cast<Simulator*>(glfwGetWindowUserPointer(w));
self->on_scroll(xo, yo);
}
bool visual_ready_ = false;
mjModel* m_ = nullptr;
mjData* d_ = nullptr;
mjvCamera cam_{};
mjvOption opt_{};
mjvScene scn_{};
mjrContext con_{};
GLFWwindow* window_ = nullptr;
// 外部回调
std::function<void(int, int)> key_handler_;
// 鼠标状态
bool btn_left_ = false, btn_middle_ = false, btn_right_ = false;
double lastx_ = 0, lasty_ = 0;
// 暂停 & 拖拽加力
bool paused_ = false;
bool perturb_active_ = false;
mjvPerturb pert_{};
};
3. 使用官方的simulator(拥有官方的simulator界面按钮)
- 将simulator文件夹下的所有文件复制到自己的工程文件下面
src/除了cmake/文件夹之外的文件598 - 更改
CMakeListst.txt
cmake
cmake_minimum_required(VERSION 3.20)
project(工程名 LANGUAGES CXX)
# C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# MuJoCo 安装目录(此处替换为自己的安装目录)
set(MUJOCO_PATH "/home/xiaofang/Applications/mujoco-3.7.0")
include_directories(
${MUJOCO_PATH}/include
)
link_directories(
${MUJOCO_PATH}/lib
)
set(MUJOCO_LIB
${MUJOCO_PATH}/lib/libmujoco.so
)
# 官方 Simulator 源码
file(GLOB SIMULATOR_SRC
${CMAKE_CURRENT_SOURCE_DIR}/src/simulator/*.cc
)
# 用户控制代码
set(USER_SRC
src/control/control.cpp
src/control/pid.cpp
src/sensors/imu.cpp
)
# 最终程序
add_executable(WheelLeg
${SIMULATOR_SRC}
${USER_SRC}
)
target_include_directories(WheelLeg PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src/simulator
${MUJOCO_PATH}/include
)
target_link_libraries(WheelLeg
${MUJOCO_LIB}
glfw
GL
GLU
)
- 更改官方main.cc
c++
void PhysicsLoop(mj::Simulate& sim) {
...
#在这里面加入用户循环控制程序入口 例如
carController.Main_Control_Update(m, d);
mj_step(m, d);
...
}
void PhysicsThread(mj::Simulate* sim, const char* filename){
...
mj_forward(m, d);
//在这里加入用户控制器的初始化
carController.Init(m);
...
}
- 启动方式 (此处使用fish)
新建
scripts/run.fish写入以下内容
bash
#!/usr/bin/env fish
cd ../build
echo "========== Build =========="
make
or begin
echo "编译失败"
exit 1
end
echo "========== Run =========="
#启动程序,设置环境变量以启用 NVIDIA GPU 的渲染加速
env \
__NV_PRIME_RENDER_OFFLOAD=1 \
__GLX_VENDOR_LIBRARY_NAME=nvidia \
MUJOCO_GL=egl \
../build/WheelLeg ../models/model.xml
问题专项
一、解决方针启动卡顿问题
(此处使用fish,bash等其他shell同理)
- 原因:没有使用nvidia显卡启动仿真
- 新建run.fish在里面写运行程序的脚本
bash
#!/usr/bin/env fish
env \
__NV_PRIME_RENDER_OFFLOAD=1 \
__GLX_VENDOR_LIBRARY_NAME=nvidia \
MUJOCO_GL=glx \
../build/demo编译后生成的可执行文件