安装试用
1. 预编译尝鲜
如果没打算对 MuJoCo 的源码进行开发和修改的的话,我们可以直接下载官方预编译的版本。 在 GitHub Releases Page上,分别为 Windows, Linux 和 macOS 提供了 x86_64 和 arm64 架构上的预编译库。 本系列文章采用的 MuJoCo 版本是 3.1.2。
下下来的预编译版本就是一个压缩包,不需要运行什么安装脚本,直接解压就能用。下面左侧是该软件解压之后的目录结构,注释部分已经详细解释了各个子目录的作用。 我们进入到其中的 bin 目录下,运行 simulate 程序,就可以看到右侧的界面,有一个小人,从站立的状态逐渐趴下。
|
MuJoCo 支持以 MJCF 或者 URDF 格式描述的 xml 文件加载模型。在 MuJoCo 中,这些模型最后都会转换成一个 mjModel 的 C 语言结构体对象。 官方教程中,提供了一个极简的例子。 下面左侧是一个通过 MJCF 格式描述的仿真模型。其中第3行中,定义了光照环境,包含扩散系数、光照位置和方向,用于照亮目标物体并投影。接着在世界中固定了一个 plane 的几何体。 最后在第5-8行中,定义了一个长宽高分别是 0.1, 0.2, 0.3 的自由盒子。如果用 simulate 加载就可以看到下面右侧所示的场景。
|
下面左侧是用来加载模型文件并进行仿真的最小 C 语言代码,没有 GUI 界面。大体的套路是,通过 mj_loadXML 加载模型文件得到 mjModel 对象,然后通过接口 mj_makeData 得到具体的仿真数据。 接下来通过 mj_step 逐帧推演仿真结果。最后通过 mj_deleteData 和 mj_deleteModel 释放仿真数据和模型对象。下面右侧的代码,是我在本地编译运行该示例程序的 CMakeLists.txt, 重点在于其中的第8-10行,指定了 MuJoCo 的头文件、库文件的搜索路径以及需要链接的动态库 mujoco。如果能够正常编译运行,说明我们的环境已经搭建完毕了。
|
|
2. 源码编译趟坑
既然我们要研究 MuJoCo 的源码,自然要从源码编译一遍。我们需要 CMake 和 C++17 的编译器。套路如下。
git clone https://github.com/deepmind/mujoco.git # 从 github 克隆源码
# git clone https://github.com/gaoyichao/mujoco.git
cd mujoco # 切换到代码仓库中
mkdir build && cd build # 创建一个 build 目录并切换进去
cmake .. # 通过 cmake 解除依赖,在 build 目录中生成 Makefile
make -j10 # 执行编译
在运行 cmake 的过程中,MuJoCo 会通过FetchContent下载依赖的第三方库。尽管如此, 在编译的过程中仍然有一些依赖没有处理,我们可以通过如下的语句,使用 apt-get 安装这些依赖。如果一切顺利,安装了这些依赖之后,就可以正常编译 mujoco 了。
sudo apt-get install libx11-dev libxrender-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev mesa-common-dev
下面是 mujoco 源码的目录组织结构。
mujoco
├── build # 用于编译,由我手动创建的目录
├── cmake # mujoco 的 CMake 目录,其中保存了 MujocoOptions, MujocoDependencies 等Cmake脚本
├── CMakeLists.txt # mujoco 的 CMake 编译脚本
├── dist # 里面存放的应该是用于打包发布的资源文件和脚本吧
├── doc # 用于生成 Sphinx 文档相关的资源文件
├── include # mujoco 的头文件目录
├── introspect #
├── model # mujoco 收集的一些模型文件
├── plugin # mujoco 的插件系统
├── python # This package is the canonical Python bindings for the MuJoCo physics engine
├── sample # 例程以及编译他们的 makefile 脚本
├── simulate # 一个加载了 mujoco 的完整特性的应用程序
├── src # 我们将要关注的源码目录
├── test # 单元测试工程
└── unity # Unity 插件,可以在 Unity 编辑器以及 runtime 环境下使用 MuJoCo 物理引擎。
3. 编译系统
cmake_minimum_required(VERSION 3.16)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0072 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(MSVC_INCREMENTAL_DEFAULT ON)
左侧是 mujoco 源码根目录下的 CMakeLists.txt 的代码片段。一开始,它指定了编译 mujoco 的最低 CMake 版本为 3.16。然后通过 set 指令开启了一些cmake的策略(CMAKE_POLICY)。
cmake-policies 是一种向前兼容的机制,每当 cmake 引入了新的策略之后, 都会增加一些警告,提示开发者尽快采用新策略。但是每个策略还可以通过 CMAKE_POLICY_DEFAULT_CMP<NNNN> 变量显式设置为 NEW 或 OLD 行为,来开启或者关闭相应的警告。 最后通过变量 MSVC_INCREMENTAL_DEFAULT 开启 MSVC 的增量编译功能,这个主要是 windows 下的编译设置。下面罗列了这里涉及的四个特性:
- CMP0063:这是一个从 3.3 版本开始引入的一个特性。说是要尊重所有目标类型的可见性属性。Honor visibility properties for all target types. 其实主要是在讲在构建动态链接库的时候,是否需要把一些符号隐藏起来。此策略的旧行为是忽略静态库、对象库和不导出的可执行文件的可见性属性。其新行为是尊重所有目标类型的可见性属性。
- CMP0069:这是一个从 3.9 版本开始引入的一个特性。此特性用于启用过程间优化(IPO, InterProcedual Optimization)时加上 ipo 标志。 此策略的旧行为是仅为 Linux 上的英特尔编译器添加 IPO 标志。新行为是为当前编译器添加 IPO 标志,如果 CMake 不知道这些标志,则会产生错误。
- CMP0072:这是一个从3.11 版本开始引入的一个特性。通过 FindOpenGL 查找 opengl 库的时候,更倾向于使用 GLVND。 即,当旧版 GL 库(例如 libGL.so)和适用于 OpenGL 和 GLX 的 GLVND 库(例如 libOpenGL.so 和 libGLX.so)均可用时,优先选择 libOpenGL.so 和 libGLX.so。
- CMP0077:这是一个从3.13 版本开始引入的一个特性。说是 option() 指令可以支持一般变量。主要是用于命令行选项参数的设置的。 源码注释中说是要防止避免 BUILD_SHARED_LIBS 被 ccd 中的 option() 覆盖。Avoid BUILD_SHARED_LIBS getting overridden by an option() in ccd.
project(mujoco VERSION 2.3.8
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
enable_language(C)
enable_language(CXX)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
通过变量 CMAKE_MODULE_PATH 把源码目录下的 cmake 子目录添加到 cmake 的搜索路径下,这样在后面就可以访问该目录下的 *.cmake 文件。 这里的变量 ${PROJECT_SOURCE_DIR} 是通过 project 定义项目名称时生成的,它表示源码的根目录。
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
接下来通过 option 指令增加了一些编译选项,表示需要编译例程、simulate、单元测试用例以及 python binding。
我们可以根据需要修改对应项为 OFF 关闭它们,也可以在命令行中通过 -DMUJOCO_BUILD_EXAMPLES=OFF
的形式关闭。
option(MUJOCO_BUILD_EXAMPLES "Build samples for MuJoCo" ON)
option(MUJOCO_BUILD_SIMULATE "Build simulate library for MuJoCo" ON)
option(MUJOCO_BUILD_TESTS "Build tests for MuJoCo" ON)
option(MUJOCO_TEST_PYTHON_UTIL "Build and test utility libraries for Python bindings" ON)
include(MujocoOptions)
include(MujocoMacOS)
include(MujocoDependencies)
接着通过 include 指令加载并执行了位于 ${PROJECT_SOURCE_DIR}/cmake 目录下的三个 *.cmake 文件。 其中 MujocoOptions.cmake 通过 set 或者 option 的指令还定义了一些关于编译特性的变量,MujocoMacOS.cmake 是苹果上的一些配置跟我们没什么关系, MujocoDependencies.cmake 主要是用来解除一些依赖,它通过FetchContent下载依赖的第三方库。
下面这段代码,主要是定义变量 MUJOCO_HEADERS 记录头文件。官方对这些头文件的功能做了详细介绍。 这里我们以注释的形式翻译一下。
set(MUJOCO_HEADERS
include/mujoco/mjdata.h # 定义了C结构体 mjData
include/mujoco/mjexport.h # 用于控制动态库中暴露符号表,不应该直接包含在用户代码中
include/mujoco/mjmacro.h # 一些有用的 C 语言宏定义
include/mujoco/mjmodel.h # 定义了C结构体 mjModel
include/mujoco/mjplugin.h # 关于插件的数据结构
include/mujoco/mjrender.h # 关于 OpenGL 渲染器的一些结构体和类型定义
include/mujoco/mjtnum.h # MuJoCo 的浮点数类型 mjtNum 用于选择使用 double 或者 float 进行数值仿真。
include/mujoco/mjui.h # 关于 UI 框架的一些结构体和类型定义
include/mujoco/mjvisualize.h # 关于可视化的一些结构体和类型定义
include/mujoco/mjxmacro.h # 定义了 X Macros 的扩展,可以自动将 mjModel 和 mjData 转换到脚本语言中。
include/mujoco/mujoco.h # 它定义了所有的 API 接口和全局变量,引用了除 mjxmacro.h 之外的所有头文件。
)
然后通过 add_library 声明将要编译的动态库 libmujoco.so。 指令 target_include_directories, 用于声明编译 mujoco 时的头文件目录。该指令中出现的 PUBLIC 和 PRIVATE 主要是在讲 src 中出现的头文件只在编译 libmojoco.so 的时候有用,在公开发布 libmojoco.so 的时候不需要它们。 很多时候,编译时的头文件和发布后的头文件的目录不是同一个,这里通过 BUILD_INTERFACE 和 INSTALL_INTERFACE 来分别描述它们。
|
|
上面右侧的代码片段中,执行了各个子目录中的 CMakeLists.txt。这里涉及到的三个 plugin 下的目录于 libmujoco.so 的生成没有关系,它们将分别产生对应的 so 文件。 src 下的这5个子目录分别对应着 mujoco 的6个模块:
- src/engine: 对应 Engine,物理引擎的实现,将是我们后续的研究重点
- src/user: 大概是 Abstract visualizer 的实现。提供抽象的模型可视化接口。
- src/xml: 看命名应该对应 Parser 和 Compiler 两个模块吧。用于解析 xml 文件生成 C++ 的结构 mjCModel,再将之编译成C结构体 mjModel。
- src/render: 对应 OpenGL renderer,负责图形渲染,这个大概率可以替换成其他渲染引擎。
- src/ui:对应 UI 框架。
下面的代码通过指令 target_compile_definitions 定义了一些宏。
很多时候,为了跨平台或者代码优化,人们会在代码中通过宏定义设置一些开关,来进行条件编译。最后那个 -DMC_IMPLEM_ENABLE
是删除该宏定义。
target_compile_definitions(mujoco PRIVATE _GNU_SOURCE CCD_STATIC_DEFINE MUJOCO_DLL_EXPORTS -DMC_IMPLEM_ENABLE)
if(MUJOCO_ENABLE_AVX_INTRINSICS)
target_compile_definitions(mujoco PUBLIC mjUSEPLATFORMSIMD)
endif()
下面前两条指令,分别设定了 libmujoco.so 的编译选项和链接选项。这些选项的变量基本是由之前 include 的 MujocoOptions.cmake 和 MujocoMacOS.cmake 定义的。 最后设置了链接动态库 libmujoco.so 所需的其它三方库。
target_compile_options(mujoco
PRIVATE ${AVX_COMPILE_OPTIONS} ${MUJOCO_MACOS_COMPILE_OPTIONS} ${EXTRA_COMPILE_OPTIONS} ${MUJOCO_CXX_FLAGS})
target_link_options(mujoco PRIVATE ${MUJOCO_MACOS_LINK_OPTIONS} ${EXTRA_LINK_OPTIONS})
target_link_libraries(mujoco PRIVATE ccd lodepng qhullstatic_r tinyobjloader tinyxml2)
后面都是一些如何编译例程、测试、安装相关的指令了,与生成 libmujoco.so 的关系不大,就不再展开了。