首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

安装试用

1. 预编译尝鲜

如果没打算对 MuJoCo 的源码进行开发和修改的的话,我们可以直接下载官方预编译的版本。 在 GitHub Releases Page上,分别为 Windows, Linux 和 macOS 提供了 x86_64 和 arm64 架构上的预编译库。 本系列文章采用的 MuJoCo 版本是 3.1.2。

下下来的预编译版本就是一个压缩包,不需要运行什么安装脚本,直接解压就能用。下面左侧是该软件解压之后的目录结构,注释部分已经详细解释了各个子目录的作用。 我们进入到其中的 bin 目录下,运行 simulate 程序,就可以看到右侧的界面,有一个小人,从站立的状态逐渐趴下。

        mujoco-2.3.7/
        ├── bin                 # 保存了 mujoco 的各个工具对应的可执行程序
        ├── include             # 用于二次开发的 mujoco 的头文件
        ├── lib                 # mujoco 的动态链接库
        ├── model               # mujoco 收集的一些模型文件
        ├── sample              # 例程以及编译他们的 makefile 脚本
        ├── simulate            # 一个加载了 mujoco 的完整特性的应用程序
        └── THIRD_PARTY_NOTICES # 第三方开源软件的声明
    
    cd mujoco-2.3.7/bin
    ./simulate ../model/humanoid/humanoid.xml

MuJoCo 支持以 MJCF 或者 URDF 格式描述的 xml 文件加载模型。在 MuJoCo 中,这些模型最后都会转换成一个 mjModel 的 C 语言结构体对象。 官方教程中,提供了一个极简的例子。 下面左侧是一个通过 MJCF 格式描述的仿真模型。其中第3行中,定义了光照环境,包含扩散系数、光照位置和方向,用于照亮目标物体并投影。接着在世界中固定了一个 plane 的几何体。 最后在第5-8行中,定义了一个长宽高分别是 0.1, 0.2, 0.3 的自由盒子。如果用 simulate 加载就可以看到下面右侧所示的场景。

        <mujoco>
          <worldbody>
            <light diffuse=".5 .5 .5" pos="0 0 3" dir="0 0 -1"/>
            <geom type="plane" size="1 1 0.1" rgba=".9 0 0 1"/>
            <body pos="0 0 1">
              <joint type="free"/>
              <geom type="box" size=".1 .2 .3" rgba="0 .9 0 1"/>
            </body>
          </worldbody>
        </mujoco>

下面左侧是用来加载模型文件并进行仿真的最小 C 语言代码,没有 GUI 界面。大体的套路是,通过 mj_loadXML 加载模型文件得到 mjModel 对象,然后通过接口 mj_makeData 得到具体的仿真数据。 接下来通过 mj_step 逐帧推演仿真结果。最后通过 mj_deleteData 和 mj_deleteModel 释放仿真数据和模型对象。下面右侧的代码,是我在本地编译运行该示例程序的 CMakeLists.txt, 重点在于其中的第8-10行,指定了 MuJoCo 的头文件、库文件的搜索路径以及需要链接的动态库 mujoco。如果能够正常编译运行,说明我们的环境已经搭建完毕了。

        #include <mujoco/mujoco.h>
        #include <iostream>
        char error[1000];
        mjModel* m;
        mjData* d;
        int main()
        {
            m = mj_loadXML("hello.xml", NULL, error, 1000);
            if(!m) {
                printf("%s\n", error);
                return 1;
            }
            // make data corresponding to model
            d = mj_makeData(m);
            // run simulation for 10 seconds
            while(d->time < 10)
               mj_step(m, d);
            // free model and data
            mj_deleteData(d);
            mj_deleteModel(m);
            return 0;
        }
        cmake_minimum_required(VERSION 3.1.0)
        project(learning_mujoco)
        set(PACKAGE_VERSION "0.0.1")
        
        add_compile_options(-std=c++17)
        set(CMAKE_BUILD_TYPE DEBUG)
        
        set(MuJoCo_Include_Dir /home/gyc/Project/mujoco-2.3.7/include)
        set(MuJoCo_Library_Dir /home/gyc/Project/mujoco-2.3.7/lib)
        set(MuJoCo_Libraries mujoco)
        
        include_directories(
            ${PROJECT_SOURCE_DIR}/include
            ${MuJoCo_Include_Dir}
        )
        
        link_directories(
            ${MuJoCo_Library_Dir}
        )
        
        add_executable(test ./test.cpp)
        target_link_libraries(test ${MuJoCo_Libraries})

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 下的编译设置。下面罗列了这里涉及的四个特性:

        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 来分别描述它们。

        add_library(mujoco shared ${mujoco_resource_files})

        target_include_directories(
          mujoco
          public $<build_interface:${cmake_current_source_dir}/include>
                 $<install_interface:${cmake_install_includedir}/>
          private src
        )
        add_subdirectory(plugin/elasticity)    
        add_subdirectory(plugin/sensor)
        add_subdirectory(plugin/sdf)
        add_subdirectory(src/engine)
        add_subdirectory(src/user)
        add_subdirectory(src/xml)
        add_subdirectory(src/render)
        add_subdirectory(src/ui)

上面右侧的代码片段中,执行了各个子目录中的 CMakeLists.txt。这里涉及到的三个 plugin 下的目录于 libmujoco.so 的生成没有关系,它们将分别产生对应的 so 文件。 src 下的这5个子目录分别对应着 mujoco 的6个模块

在 src 的这些子目录中的 CMakeLists.txt 并没有过多内容,以 engine 为例,其中定义了变量 MUJOCO_ENGINE_SRCS 收集编译需要的源文件, 并通过指令 target_sources 附加到 libmujoco.so 的编译项中。

下面的代码通过指令 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 的关系不大,就不再展开了。

4. 完




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1