概述#
引言#
MuJoCo 是 Multi-Joint dynamics with Contact(多关节动力学与接触)的缩写。它是一个通用物理引擎,旨在促进机器人学、生物力学、图形和动画、机器学习以及其他需要对与环境互动的铰接结构进行快速准确仿真的领域的研究和开发。它最初由 Roboti LLC 开发,于 2021 年 10 月被 DeepMind 收购并免费提供,并于 2022 年 5 月开源。MuJoCo 代码库可在 GitHub 上的 google-deepmind/mujoco 仓库中获取。
MuJoCo 是一个带有 C API 的 C/C++ 库,面向研究人员和开发者。运行时仿真模块经过优化以最大限度地提高性能,并在内置 XML 解析器和编译器预分配的低级数据结构上运行。用户使用原生的 MJCF 场景描述语言定义模型——这是一种 XML 文件格式,旨在尽可能地易于人类阅读和编辑。也可以加载 URDF 模型文件。该库包含使用原生 GUI 进行交互式可视化,通过 OpenGL 渲染。MuJoCo 还提供了大量用于计算物理相关量的实用函数。
MuJoCo 可用于实现基于模型的计算,例如控制合成、状态估计、系统辨识、机构设计、通过逆动力学进行数据分析以及用于机器学习应用的并行采样。它也可以作为更传统的仿真器使用,包括用于游戏和交互式虚拟环境。
主要特性#
MuJoCo 具有许多特性。在此我们概述最显著的几个。
- 广义坐标结合现代接触动力学
物理引擎传统上分为两类。机器人学和生物力学引擎使用广义坐标或关节坐标中的高效准确递归算法。然而,它们要么忽略接触动力学,要么依赖于需要非常小时间步长的早期弹簧-阻尼器方法。游戏引擎使用一种更现代的方法,通过求解优化问题来找到接触力。然而,它们通常诉诸于过度指定的笛卡尔表示,其中关节约束是数值施加的,当涉及复杂的运动学结构时,会导致不准确和不稳定。MuJoCo 是第一个结合了两者的优点(广义坐标仿真和基于优化的接触动力学)的通用引擎。其他仿真器最近已进行调整以使用 MuJoCo 的方法,但这通常与其所有功能不兼容,因为它们最初并非为此设计。习惯于游戏引擎的用户可能最初会觉得广义坐标违反直觉;请参阅下面的澄清部分。
- 软性、凸性且解析可逆的接触动力学
在现代接触动力学方法中,由摩擦接触产生的力或冲量通常定义为线性或非线性互补问题(LCP 或 NCP)的解,两者均为 NP-hard。MuJoCo 基于一种不同的接触物理公式,该公式可简化为凸优化问题,详情请参阅计算章节。我们的模型允许软接触和其他约束,并且具有唯一确定的逆,便于数据分析和控制应用。提供了多种优化算法选择,包括投影高斯-赛德尔方法(projected Gauss-Seidel method)的推广,可处理椭圆摩擦锥。求解器提供了摩擦接触(包括扭转摩擦和滚动摩擦)、无摩擦接触、关节和肌腱限制、关节和肌腱的干摩擦以及各种等式约束的统一处理方法。
- 肌腱几何
MuJoCo 可以模拟肌腱的 3D 几何形状——它们是遵守缠绕和经过点约束的最小路径长度弦。该机制类似于 OpenSim 中的机制,但实现了更受限的、封闭形式的缠绕选项集,以加快计算速度。它还提供了机器人学特有的结构,例如滑轮和耦合自由度。肌腱可用于驱动,也可用于对肌腱长度施加不等式或等式约束。
- 通用驱动模型
在使用与模型无关的 API 的同时设计一个足够丰富的驱动模型是一项挑战。MuJoCo 通过采用一种抽象驱动模型来实现此目标,该模型可以具有不同类型的传动、力生成和内部动力学(即,使总体动力学成为三阶的状态变量)。这些组件可以实例化,以便以统一的方式模拟电机、气缸和液压缸、PD 控制器、生物肌肉和许多其他执行器。
- 可重构计算流程
MuJoCo 有一个顶层步进函数 mj_step,它运行整个前向动力学并推进仿真状态。然而,在许多超出仿真的应用中,能够运行计算流程的选定部分是有益的。为此,MuJoCo 提供了大量标志,可以任意组合设置,允许用户根据需要重新配置流程,超出通过选项选择算法和算法参数。此外,许多底层函数可以直接调用。用户定义的回调函数可以实现自定义力场、执行器、碰撞例程和反馈控制器。
- 模型编译
如上所述,用户在一种称为 MJCF 的 XML 文件格式中定义 MuJoCo 模型。然后,该模型由内置编译器编译为低级数据结构 mjModel,该结构经过交叉索引和优化以用于运行时计算。编译后的模型也可以保存在二进制 MJB 文件中。
- 模型与数据的分离
MuJoCo 在运行时将仿真参数分为两个数据结构(C 结构体)
mjModel 包含模型描述,并且期望保持不变。其中嵌入了包含仿真和可视化选项的其他结构体,这些选项偶尔需要更改,但这由用户完成。
mjData 包含所有动态变量和中间结果。它用作一个暂存区,所有函数从此处读取输入并写入输出——这些输出随后成为仿真流程后续阶段的输入。它还包含一个预分配和内部管理的栈,以便运行时模块在模型初始化后无需调用内存分配函数。
mjModel 由编译器构建。mjData 在运行时根据 mjModel 构建。这种分离使得仿真多个模型以及每个模型的多个状态和控制变得容易,进而促进了采样和有限差分的多线程。顶层 API 函数反映了这种基本分离,其格式为
void mj_step(const mjModel* m, mjData* d);
- 交互式仿真与可视化
原生的3D 可视化器提供网格和几何图元的渲染、纹理、反射、阴影、雾、透明度、线框、天空盒、立体可视化(在支持四缓冲 OpenGL 的显卡上)。此功能用于生成 3D 渲染,帮助用户深入了解物理仿真,包括自动生成的模型骨架、等效惯量盒、接触位置和法线、可分解为法向和切向分量的接触力、外部扰动力、局部坐标系、关节和执行器轴以及文本标签等视觉辅助。可视化器需要一个带有 OpenGL 渲染上下文的通用窗口,从而允许用户选择自己喜欢的 GUI 库。MuJoCo 分发的代码示例 simulate.cc 展示了如何使用 GLFW 库来实现这一点。一个相关的可用性特性是能够“深入”仿真,推动物体并查看物理响应。用户选择将施加外部力和力矩的刚体,并实时查看扰动及其动态结果的渲染。这可用于视觉调试模型、测试反馈控制器的响应或将模型配置到所需姿势。
- 强大且直观的建模语言
MuJoCo 拥有自己的建模语言,称为 MJCF。MJCF 的目标是提供对 MuJoCo 所有计算能力的访问,同时使用户能够快速开发新模型并进行实验。这主要得益于广泛的默认设置机制,该机制类似于 HTML 中内联的层叠样式表 (CSS)。虽然 MJCF 具有许多元素和属性,但用户在任何给定模型中只需要设置极少的参数。这使得 MJCF 文件比许多其他格式更短、更易读。
- 复合柔性对象的自动生成
MuJoCo 的软约束可用于模拟绳索、布料和可变形 3D 对象。这需要大量常规刚体、关节、肌腱和约束协同工作。建模语言具有高级宏,这些宏由模型编译器自动扩展为必要的标准模型元素集合。重要的是,这些生成的柔性对象能够与仿真的其余部分完全交互。
模型实例#
在 MuJoCo 中有几种称为“模型”的实体。用户在 MJCF 或 URDF 编写的 XML 文件中定义模型。然后软件可以在不同的介质(文件或内存)和不同的描述级别(高或低)创建同一模型的多个实例。所有组合都是可能的,如下表所示
所有运行时计算均使用 mjModel 进行,该结构过于复杂,无法手动创建。这就是我们拥有两个建模级别的原因。高级别是为了用户方便而存在:其唯一目的是被编译成可执行计算的低级别模型。生成的 mjModel 可以加载并保存到二进制文件 (MJB) 中,但这些文件与版本相关且无法反编译,因此模型应始终维护为 XML 文件。
的 mjSpec C 结构体与 MJCF 文件格式一一对应。XML 加载器解释 MJCF 或 URDF 文件,创建相应的 mjSpec 并将其编译为 mjModel。用户可以编程方式创建 mjSpec,然后将其保存到 MJCF 或编译。程序化模型创建和编辑在模型编辑章节中有描述。
下图显示了获取 mjModel 的不同路径
(文本编辑器) → MJCF/URDF 文件 → (MuJoCo 解析器 → mjSpec → 编译器) → mjModel
(用户代码) → mjSpec → (MuJoCo 编译器) → mjModel
MJB 文件 → (模型加载器) → mjModel
示例#
这是一个 MuJoCo MJCF 格式的简单模型。它定义了一个固定在世界上的平面、一个用于更好地照明物体并投射阴影的光源,以及一个具有 6 个自由度的浮动盒子(这就是“自由”关节的作用)。
<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>
内置的 OpenGL 可视化器将此模型渲染为

如果对该模型进行仿真,盒子会落到地面上。不带渲染的被动动力学基本仿真代码如下所示。
#include "mujoco.h"
#include "stdio.h"
char error[1000];
mjModel* m;
mjData* d;
int main(void) {
// load model from file and check for errors
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;
}
这在技术上是一个 C 文件,但它也是一个合法的 C++ 文件。实际上,MuJoCo API 与 C 和 C++ 都兼容。通常用户代码会用 C++ 编写,因为它增加了便利性,而且不会牺牲效率,因为计算瓶颈在于仿真器,而仿真器已经高度优化。
函数 mj_step 是顶层函数,它将仿真状态向前推进一个时间步长。这个例子当然只是一个被动动力系统。当用户指定控制或施加力并开始与系统交互时,事情会变得更有趣。
接下来我们提供一个更详细的示例,说明 MJCF 的几个特性。考虑以下 example.xml
<mujoco model="example">
<default>
<geom rgba=".8 .6 .4 1"/>
</default>
<asset>
<texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
</asset>
<worldbody>
<light pos="0 1 1" dir="0 -1 -1" diffuse="1 1 1"/>
<body pos="0 0 1">
<joint type="ball"/>
<geom type="capsule" size="0.06" fromto="0 0 0 0 0 -.4"/>
<body pos="0 0 -0.4">
<joint axis="0 1 0"/>
<joint axis="1 0 0"/>
<geom type="capsule" size="0.04" fromto="0 0 0 .3 0 0"/>
<body pos=".3 0 0">
<joint axis="0 1 0"/>
<joint axis="0 0 1"/>
<geom pos=".1 0 0" size="0.1 0.08 0.02" type="ellipsoid"/>
<site name="end1" pos="0.2 0 0" size="0.01"/>
</body>
</body>
</body>
<body pos="0.3 0 0.1">
<joint type="free"/>
<geom size="0.07 0.1" type="cylinder"/>
<site name="end2" pos="0 0 0.1" size="0.01"/>
</body>
</worldbody>
<tendon>
<spatial limited="true" range="0 0.6" width="0.005">
<site site="end1"/>
<site site="end2"/>
</spatial>
</tendon>
</mujoco>
该模型是一个 7 自由度的手臂,它“握住”一根绳子,绳子的另一端连接着一个圆柱体。绳子被实现为具有长度限制的肌腱。肩部有一个球关节,肘部和腕部有一对铰链关节。圆柱体内部的盒子表示一个自由“关节”。XML 中的外部 body 元素是必需的 worldbody。请注意,在两个刚体之间使用多个关节不需要创建虚拟刚体。
MJCF 文件包含指定模型所需的最小信息。胶囊体由空间中的线段定义——在这种情况下,只需要胶囊体的半径。刚体坐标系的位置和方向由属于它们的 geoms 推断。在均匀密度假设下,惯性属性由 geom 形状推断。这两个 site 被命名是因为肌腱定义需要引用它们,而其他任何东西都没有命名。关节轴只为铰链关节定义,不为球关节定义。碰撞规则是自动定义的。摩擦特性、重力、仿真时间步长等都设置为默认值。顶部指定的默认 geom 颜色适用于所有 geoms。
除了以二进制 MJB 格式保存编译后的模型外,我们还可以将其保存为 MJCF 或人类可读的文本格式;分别参见 example_saved.xml 和 example_saved.txt。XML 版本与原始版本相似,而文本版本包含 mjModel
中的所有信息。将文本版本与 XML 版本进行比较,可以发现模型编译器为我们做了多少工作。
模型元素#
本节简要描述了 MuJoCo 模型中可以包含的所有元素。稍后我们将更详细地解释底层计算、MJCF 中元素的指定方式以及它们在 mjModel
中的表示。
选项#
每个模型都有以下列出的三组选项。它们总是包含在内。如果其值未在 XML 文件中指定,则使用默认值。这些选项的设计旨在允许用户在每个仿真时间步之前更改其值。但在一个时间步内,任何选项都不应更改。
mjOption
#
此结构包含影响物理仿真的所有选项。它用于选择算法并设置其参数,启用和禁用仿真流程的不同部分,以及调整重力等系统级物理属性。
mjVisual
#
此结构包含所有可视化选项。还有其他 OpenGL 渲染选项,但这些选项与会话相关,不属于模型的一部分。
mjStatistic
#
此结构包含由编译器计算的关于模型的统计信息:平均刚体质量、模型的空间范围等。包含此信息是为了提供信息,也因为可视化器使用它进行自动缩放。
资产#
资产本身不是模型元素。模型元素可以引用它们,在这种情况下,资产会以某种方式改变引用元素的属性。一个资产可以被多个模型元素引用。由于包含资产的唯一目的是引用它,而引用只能通过名称完成,因此每个资产都有一个名称(如果适用,可以从文件名推断出来)。相比之下,常规元素的名称可以保持未定义。
网格#
MuJoCo 可以从 OBJ 文件和二进制 STL 文件加载三角形网格。可以使用 MeshLab 等软件进行格式转换。虽然任何三角形集合都可以加载并可视化为网格,但碰撞检测器处理的是凸包。有编译时选项用于缩放网格,以及将原始几何形状拟合到网格。网格也可以用于自动推断惯性属性——通过将其视为三角锥体的并集并组合它们的质量和惯量。请注意,网格本身没有颜色,而是使用引用 geom 的材质属性进行着色。相比之下,所有空间属性都由网格数据确定。MuJoCo 支持 OBJ 和用于法线和纹理坐标的自定义二进制文件格式。网格也可以直接嵌入到 XML 中。
皮肤#
蒙皮网格(或皮肤)是形状可以在运行时变形的网格。它们的顶点连接到刚体(在此上下文中称为骨骼),每个顶点可以属于多个骨骼,从而实现皮肤的平滑变形。蒙皮纯粹是可视化对象,不影响物理,但它们可以显著增强视觉真实感。蒙皮可以从自定义二进制文件加载,或直接嵌入到 XML 中,类似于网格。自动生成复合柔性对象时,模型编译器也会为这些对象生成蒙皮。
高度场#
高度场可以从 PNG 文件加载(内部转换为灰度)或从稍后描述的自定义二进制格式文件加载。高度场是高程数据的矩形网格。编译器将数据归一化到 [0-1] 范围。高度场的实际空间范围由引用 geom 的尺寸参数确定。高度场只能从连接到 world body 的 geoms 引用。为了渲染和碰撞检测目的,网格矩形自动进行三角形划分,因此高度场被视为三角棱柱的并集。原则上,与此类复合对象的碰撞检测可以为单个 geom 对生成大量接触点。如果发生这种情况,仅保留前 64 个接触点。其基本原理是,高度场应用于模拟空间特征相对于仿真中其他对象较大的地形图,因此对于精心设计的模型,接触点的数量会很少。
纹理#
纹理可以从 PNG 文件加载,或由编译器根据用户定义的程序参数合成。还可以选择在模型创建时将纹理留空并在运行时稍后更改它——以便在 MuJoCo 仿真中渲染视频或创建其他动态效果。可视化器支持两种纹理映射类型:2D 和立方体。2D 映射适用于平面和高度场。立方体映射对于“收缩包装”纹理到 3D 对象而无需指定纹理坐标非常有用。它也用于创建天空盒。立方体贴图的六个面可以从单独的图像文件加载,或从一个复合图像文件加载,或通过重复同一图像生成。与所有其他直接从模型元素引用的资产不同,纹理只能从另一个资产(即 material)引用,然后该资产再从模型元素引用。
材质#
材质用于控制 geoms、sites 和肌腱的外观。这是通过从相应的模型元素引用材质来完成的。外观包括纹理映射以及与以下 OpenGL 光源交互的其他属性:RGBA、镜面反射、光泽度、发射。材质也可以用于使对象具有反射性。目前,反射仅在平面和盒子的 Z+ 面上渲染。请注意,模型元素也可以具有用于设置颜色的本地 RGBA 参数。如果同时指定了材质和本地 RGBA,则本地定义具有优先级。
运动学树#
MuJoCo 仿真一组刚体的动力学,其运动通常受到约束。系统状态以关节坐标表示,刚体明确组织成运动学树。除顶层“世界”刚体外,每个刚体都有一个唯一的父级。不允许运动学环;如果需要环关节,应使用等式约束进行建模。因此,MuJoCo 模型的主干是由嵌套的 body 定义形成的一个或多个运动学树;孤立的浮动体算作一个树。下面列出的其他几个元素在 body 中定义并属于该 body。这与稍后列出的不能与单个 body 关联的独立元素形成对比。
刚体 (Body)#
刚体具有质量和惯性属性,但没有任何几何属性。相反,几何形状(或 geoms)附加到刚体上。每个刚体有两个坐标系:用于定义它以及定位相对于它的其他元素的坐标系,以及一个以刚体质心为中心并与其主惯性轴对齐的惯性坐标系。因此,在该坐标系下,刚体惯性矩阵是对角的。在每个时间步,MuJoCo 递归计算前向运动学,得到全局笛卡尔坐标系中所有刚体的位置和方向。这为所有后续计算提供了基础。
关节 (Joint)#
关节在刚体内定义。它们在刚体及其父级之间创建运动自由度 (DOFs)。如果没有关节,刚体就会焊接到其父级。这与使用过度指定笛卡尔坐标的游戏引擎相反,游戏引擎中关节是移除自由度而不是增加自由度。关节有四种类型:球形、滑动、铰链以及创建浮动刚体的“自由关节”。一个刚体可以有多个关节。通过这种方式,复合关节被自动创建,而无需定义虚拟刚体。球形和自由关节的姿态分量表示为单位四元数,MuJoCo 中的所有计算都遵循四元数的属性。
关节参考位姿#
参考位姿是存储在 mjModel.qpos0
中的关节位置向量。它对应于模型处于初始配置时关节的数值。在我们之前的示例中,肘部以 90° 弯曲配置创建。但 MuJoCo 不知道肘部是什么,因此默认将其视为数值为 0 的关节配置。我们可以覆盖默认行为,使用 joint 的 ref 属性指定初始配置对应于 90°。所有关节的参考值被组装到向量 mjModel.qpos0
中。每当仿真重置时,关节配置 mjData.qpos
被设置为 mjModel.qpos0
。在运行时,关节位置向量相对于参考位姿解释。特别是,由关节施加的空间变换量为 mjData.qpos - mjModel.qpos0
。此变换是 mjModel
的 body 元素中存储的父子平移和旋转偏移之外的附加变换。ref 属性仅适用于标量关节(slide 和 hinge)。对于球形关节,保存在 mjModel.qpos0
中的四元数始终是 (1,0,0,0),对应于空旋转。对于自由关节,浮动刚体的全局 3D 位置和四元数保存在 mjModel.qpos0
中。
弹簧参考位姿#
这是所有关节和肌腱弹簧达到其静止长度的位姿。当关节配置偏离弹簧参考位姿时会产生弹簧力,并且力与偏离量呈线性关系。弹簧参考位姿保存在 mjModel.qpos_spring
中。对于滑动和铰链关节,弹簧参考位姿通过 springref 属性指定。对于球形和自由关节,弹簧参考位姿对应于初始模型配置。
自由度 (DOF)#
自由度 (DOF) 与关节密切相关,但并非一一对应,因为球形和自由关节具有多个自由度。可以认为关节指定位置信息,而自由度指定速度和力信息。更正式地说,关节位置是系统配置流形上的坐标,而关节速度是流形在当前位置的切空间上的坐标。自由度具有与速度相关的属性,例如摩擦损失、阻尼、电枢惯量。作用在系统上的所有广义力都以自由度空间表示。相比之下,关节具有与位置相关的属性,例如限制和弹簧刚度。自由度并非由用户直接指定。相反,它们是由编译器根据关节创建的。
几何体 (Geom)#
Geoms 是刚性连接到刚体的 3D 形状。多个 geoms 可以连接到同一个刚体上。考虑到 MuJoCo 仅支持凸 geom-geom 碰撞,以及创建非凸对象的唯一方法是将其表示为凸 geoms 的并集这一事实,这一点尤其有用。除了碰撞检测和随后的接触力计算之外,geoms 还用于渲染,以及在省略刚体质量和惯量时进行自动推断。MuJoCo 支持几种原始几何形状:平面、球体、胶囊体、椭球体、圆柱体、盒子。geom 也可以是网格或高度场;这通过引用相应的资产来实现。Geoms 具有许多影响仿真和可视化的材质属性。
定位点 (Site)#
Sites 本质上是轻量级的 geoms。它们代表刚体坐标系内的关注位置。Sites 不参与碰撞检测或惯性属性的自动计算,但它们可以用于指定其他对象的空间属性,例如传感器、肌腱路径和滑块曲柄端点。Sites 也可以用于指定用户感兴趣的点(或更确切地说是坐标系)。
相机#
可以在模型中定义多个相机。交互式可视化器中总有一个默认相机,用户可以使用鼠标自由移动它。然而,通常方便定义附加相机,这些相机要么固定在世界中,要么附加到某个刚体并随之移动。除了相机位置和姿态之外,用户还可以调整垂直视野和瞳距以进行立体渲染,以及创建立体虚拟环境所需的斜投影。在模拟具有不完美光学器件的真实相机时,可以分别为水平和垂直方向指定不同的焦距以及非中心主点。
光源#
光源可以固定在 world body 上,也可以附加到移动的刚体上。可视化器提供了 OpenGL(固定功能)中的完整光照模型,包括环境光、漫反射和镜面反射分量、衰减和截止、位置光和方向光、雾。光源,或者更确切地说,被它们照亮的物体,也可以投射阴影。然而,与材质反射类似,每个投射阴影的光源都会增加一个渲染通道,因此应谨慎使用此功能。详细记录光照模型超出了本章的范围;请参阅OpenGL 文档。请注意,除了用户在运动学树中定义的光源外,还有一个随相机移动的默认头灯。其属性通过 mjVisual 选项进行调整。
独立元素#
这里我们描述不属于单个刚体,因此在运动学树之外描述的模型元素。
肌腱 (Tendon)#
肌腱是标量长度元素,可用于驱动、施加限制和等式约束,或创建弹簧-阻尼器和摩擦损失。肌腱有两种类型:固定肌腱和空间肌腱。固定肌腱是(标量)关节位置的线性组合。它们对于建模机械耦合非常有用。空间肌腱定义为通过一系列指定 site(或经过点)或缠绕指定 geoms 的最短路径。仅支持球体和圆柱体作为缠绕 geoms,圆柱体在缠绕时被视为具有无限长度。为了避免肌腱从缠绕 geom 的一侧突然跳到另一侧,用户还可以指定偏好的侧面。如果肌腱路径中有多个缠绕 geoms,它们必须由 sites 分开,以避免需要迭代求解器。空间肌腱也可以使用滑轮分成多个分支。
执行器 (Actuator)#
MuJoCo 提供了一个灵活的执行器模型,包含三个可以独立指定的组件。它们共同决定了执行器如何工作。通过协调指定这些组件可以获得常见的执行器类型。这三个组件是传动、激活动力学和力生成。传动指定了执行器如何连接到系统的其余部分;可用类型有关节、肌腱和滑块曲柄。激活动力学可用于模拟气动或液压缸以及生物肌肉的内部激活状态;使用此类执行器会使整个系统动力学成为三阶的。力生成机制决定了作为执行器输入提供的标量控制信号如何映射到标量力,然后该力再通过从传动推断出的力臂映射到广义力。
传感器 (Sensor)#
MuJoCo 可以生成仿真传感器数据,这些数据保存在全局数组 mjData.sensordata
中。结果不用于任何内部计算;而是提供给用户,用户可能需要它进行自定义计算或数据分析。可用的传感器类型包括触摸传感器、惯性测量单元 (IMU)、力矩传感器、关节和肌腱位置和速度传感器、执行器位置、速度和力传感器、运动捕捉标记点位置和四元数以及磁力计。其中一些需要额外的计算,而另一些则从 mjData
的相应字段复制。还有一个用户传感器,允许用户代码将任何其他关注量插入传感器数据数组中。MuJoCo 还具有离屏渲染功能,可以轻松模拟彩色和深度相机传感器。这不包含在标准传感器模型中,而是需要通过编程方式完成,如代码示例 simulate.cc 中所示。
等式约束 (Equality)#
等式约束可以在运动学树结构和其中定义的关节/自由度已施加的约束之外施加额外的约束。它们可用于创建环关节,或一般地模拟机械耦合。施加这些约束的内部力与所有其他约束力一起计算。可用的等式约束类型有:在一点连接两个刚体(在运动学树外部创建球形关节);将两个刚体焊接在一起;固定关节或肌腱的位置;通过三次多项式耦合两个关节或两个肌腱的位置;约束 flex(即可变形网格)的边长与其初始长度相等。
柔性体 (Flex)#
Flexes 在 MuJoCo 3.0 中加入。它们代表可变形网格,可以是 1 维、2 维或 3 维的(因此其元素可以是胶囊体、三角形或四面体)。与刚性附加到单个刚体上的静态形状 geoms 不同,flex 的元素是可变形的:它们通过连接多个刚体构成,因此刚体的位置和姿态在运行时决定了 flex 元素的形状。这些可变形元素支持碰撞和接触力,并生成软性保持可变形实体形状的被动力和约束力。提供了自动化功能,可以从文件加载网格,构建对应于网格顶点的刚体,构建对应于网格面(或线或四面体,取决于维度)的 flex 元素,并获得相应的可变形网格。
接触对 (Contact pair)#
MuJoCo 中的接触生成是一个复杂的过程。检查接触的 geom 对可以来自两个来源:自动邻近测试和其他统称为“动态”的过滤器,以及模型中提供的显式 geom 对列表。后者是一种单独的模型元素类型。由于接触涉及两个 geoms 的组合,显式指定允许用户以动态机制无法实现的方式定义接触参数。它对于微调接触模型也很有用,特别是添加被激进过滤方案移除的接触对。接触机制现已扩展到 flex 元素,可以在两个以上的刚体之间创建接触交互。然而,此类碰撞是自动化的,无法使用接触对进行微调。
接触排除 (Contact exclude)#
这与接触对相反:它指定应从候选接触对生成中排除的刚体对(而不是 geoms 对)。它对于禁用因几何形状导致不希望的永久接触的刚体之间的接触非常有用。请注意,MuJoCo 还有其他机制来处理这种情况(特别是如果 geoms 属于同一个刚体或父子刚体,则它们不会发生碰撞),但有时这些自动化机制不够,显式排除变得必要。
自定义数值 (Custom numeric)#
在 MuJoCo 仿真中有三种方式输入自定义数值。首先,可以在 XML 中定义全局数值字段。它们有一个名称和一个实数值数组。其次,可以通过元素特定的自定义数组扩展某些模型元素的定义。这通过在 XML 元素 size
中设置属性 nuser_XXX
来完成。第三,存在一个数组 mjData.userdata
,它不用于任何 MuJoCo 计算。用户可以在其中存储自定义计算的结果;请记住,随时间变化的所有内容都应存储在 mjData
中,而不是 mjModel
中。
自定义文本 (Custom text)#
自定义文本字段可以保存在模型中。它们可用于自定义计算——指定关键字命令或提供其他文本信息。但不要将其用于注释;在编译后的模型中保存注释没有好处。XML 有其自己的注释机制(被 MuJoCo 的解析器和编译器忽略),这更适合。
自定义元组 (Custom tuple)#
自定义元组是 MuJoCo 模型元素的列表,可能包含其他元组。它们不被仿真器使用,但可用于指定用户代码所需的一组元素。例如,可以使用元组来定义用于自定义接触处理的刚体对。
关键帧 (Keyframe)#
关键帧是仿真状态变量的快照。它包含关节位置、关节速度、执行器激活(如果存在)以及仿真时间的向量。模型可以包含一个关键帧库。它们对于将系统状态重置到关注点很有用。请注意,关键帧不用于在模型中存储轨迹数据;为此目的应使用外部文件。
澄清#
读者可能拥有使用其他物理仿真器和相关约定,以及与 MuJoCo 不一致的通用编程实践经验。这可能会导致混淆。本节的目标是预先澄清最可能引起混淆的方面;它介于 FAQ 和精选主题教程之间。我们需要参考文档后面介绍的内容,但无论如何,下面的文本尽可能地独立和具有介绍性。
发散#
仿真的发散发生在状态元素迅速趋向无穷大时。在 MuJoCo 中,这通常表现为 mjWARN_BADQACC 警告。发散是所有物理仿真的固有问题,不一定表明模型有问题或仿真器有错误,而更像是提示对于给定的积分器选择,时间步长过大。在物理仿真中,速度(大时间步长)和稳定性(小时间步长)之间总是存在权衡。一个针对速度进行良好调优的模型具有可能的最大不发散时间步长,这通常意味着它在极端条件下可能会发散。从这个意义上说,罕见的发散情况实际上可能表明模型调优良好。在所有情况下,都应该可以通过减小时间步长和/或切换到更稳定的积分器来防止发散。如果这不起作用,那么原因就不同了。例如,在刚体初始化时发生穿透的模型中,大的排斥力可能会将它们推开并导致发散。
单位未指定#
MuJoCo 不指定基本的物理单位。用户可以选择他们认为合适的单位系统,只要它是一致的即可。为了理解这一点,考虑一个例子:一个重 1 千克、配备 1 牛顿推力器的 1 米飞船的动力学,与一个重 1 克、配备 1 达因推力器的 1 厘米飞船的动力学相同。这是因为 MKS 和 CGS 都是一致的单位系统。此特性允许用户根据需要缩放模型,这对于模拟非常小或非常大的物体、改进仿真的数值特性非常有用。
话虽如此,仍鼓励用户使用 MKS,因为 MuJoCo 在两个地方使用了类似 MKS 的默认值
gravity 的默认值为 (0, 0, -9.81),这对应于 MKS 单位制下的地球表面重力。请注意,这并非真正指定了 MKS 单位制,因为我们可能在 Enceladus(土卫二)上使用 CGS 单位制。
geom density(用于推断刚体质量和惯性)的默认值为 1000,这对应于 MKS 单位制下水的密度。
一旦选择了基本单位(长度、质量、时间)的一致系统,所有导出单位都对应于该系统,如量纲分析所述。例如,如果我们的模型被解释为 MKS,则力和力矩单位分别为牛顿和牛顿·米。
角度:虽然在 MJCF 中可以使用度数指定角度(实际上度数是默认值),但所有角量在 mjModel 和 mjData 中都以弧度表示。因此,例如,如果我们使用 MKS,则由陀螺仪报告的角速度单位为 rad/s,而铰链关节的刚度单位为 Nm/rad。
令人惊讶的碰撞#
MuJoCo 默认排除具有直接父子关系的刚体对所属 geoms 之间的碰撞。例如,考虑上面示例部分中的手臂模型:即使胶囊体 geoms 发生穿透,肘部也没有发生碰撞,因为前臂是上臂的直接子级。
然而,如果父级是静态刚体,即 world body 或相对于 world body 没有自由度的刚体,则此排除不适用。此行为在碰撞检测部分中有详细说明,可以防止物体穿过地板或墙壁。然而,这种行为经常导致以下情况
用户注释掉浮动底座模型的根关节,可能是为了防止它下落;现在底座刚体被视为静态,出现了以前不存在的新碰撞,用户感到困惑。有两种简单的方法可以避免这个问题
非面向对象#
面向对象编程是一种非常有用的抽象,构建在更基础(且更接近硬件)的数据结构与操作它们的功能的概念之上。对象是与一个语义实体对应的数据结构和函数的集合,因此它们之间比与应用程序其余部分具有更强的依赖关系。我们在此不使用它的原因是,依赖结构使得自然的实体是整个物理仿真器。我们没有对象,而是少数数据结构和大量操作它们的功能。
我们仍然使用一种分组方式,但它与面向对象的方法不同。我们将模型(mjModel
)与数据(mjData
)分开。它们都是数据结构。模型包含描述被建模物理系统恒定属性所需的一切,而数据包含随时间变化的状态以及内部计算的可重用中间结果。所有顶层函数都期望将指向 mjModel
和 mjData
的指针作为参数。通过这种方式,我们避免了污染工作空间并干扰多线程的全局变量,但我们这样做的方式与面向对象编程实现相同效果的方式不同。
软性与滑动#
正如我们将在计算章节中详细解释的那样,MuJoCo 基于接触和其他约束物理的数学模型。该模型本质上是软性的,即对约束施加更大的力总是会导致更大的加速度,因此逆动力学可以唯一确定。这是期望的,因为它产生了一个凸优化问题,并使得依赖于逆动力学的分析成为可能;此外,我们在实践中需要建模的大多数接触都具有一定的软性。然而,一旦我们允许软约束,我们就有效地创建了一种新的动力学——即变形动力学——现在我们必须指定这些动力学如何表现。这需要对接触和其他约束进行详细的参数化,涉及属性 solref 和 solimp,这些属性可以按约束设置,稍后将进行描述。
这种软模型的另一个常见令人困惑的方面是无法避免渐进接触滑动。类似地,摩擦关节在重力作用下会逐渐屈服。这并不是因为求解器无法阻止滑动(达到摩擦锥或摩擦损失极限),而是因为它最初并没有试图阻止滑动。回想一下,对给定约束施加更大的力必须导致更大的加速度。如果要完全抑制滑动,就必须违反这个关键属性。因此,如果您在仿真中看到渐进滑动,直观的解释可能是摩擦力不足,但这在 MuJoCo 中很少见。相反,需要调整 solref
和 solimp
参数向量以减小这种效应。增加约束阻抗(solimp
的前两个元素)以及全局 mjModel.opt.impratio
设置会特别有效。这种调整通常需要更小的时间步长来保持仿真稳定,因为它们使非线性动力学更难以数值积分。牛顿求解器通常更精确,也能减少滑动。
对于希望完全抑制滑动的情况,主求解器之后运行一个第二个 noslip
求解器。它通过忽略约束软性来更新摩擦维度的接触力。然而,当使用此选项时,MuJoCo 不再求解其设计的凸优化问题,仿真可能会变得不够鲁棒。因此,使用带有椭圆摩擦锥和较大 impratio
值的牛顿求解器是减少滑动的推荐方法。有关更详细的建议,请参阅建模章节中的防止滑动。
类型、名称、ID#
如前所述,MuJoCo 支持大量的模型元素。每种元素类型在 mjModel
中都有相应的部分列出其各种属性。例如,关节限制数据在数组中
mjtNum* jnt_range; // joint limits (njnt x 2)
每个数组的大小(在本例中为 njnt
)也在 mjModel
中给出。第一个关节的限制首先包含,然后是第二个关节的限制,依此类推。此排序反映了 MuJoCo 中所有矩阵均采用行主序格式的事实。
可用的元素类型在 mjmodel.h 文件中的枚举类型 mjtObj 中定义。这些枚举主要在内部使用。一个例外是 MuJoCo API 中的函数 mj_name2id 和 mj_id2name,它们将元素名称映射到整数 id,反之亦然。这些函数以元素类型作为输入。
在 XML 中命名模型元素是可选的。两个同类型(例如,两个关节)的元素不能有相同的名称。仅当需要在模型的其他地方引用某个元素时才需要命名;在 XML 中的引用只能通过名称进行。模型编译后,名称仍存储在 mjModel
中,以方便用户使用,但它们对仿真没有进一步影响。名称对于查找相应的整数 id 以及渲染非常有用:例如,如果您启用关节标签,则每个关节旁边将显示一个字符串(未定义名称的元素标记为“joint N”,其中 N 是 id)。
假设我们已经有了 mjModel* m
。要打印名为“elbow”的关节的范围,请执行
int jntid = mj_name2id(m, mjOBJ_JOINT, "elbow");
if (jntid >= 0)
printf("(%f, %f)\n", m->jnt_range[2*jntid], m->jnt_range[2*jntid+1]);
如果未找到名称,函数将返回 -1,这就是为什么应该始终检查 id >= 0 的原因。
刚体 (Bodies)、几何体 (geoms)、定位点 (sites)#
Bodies、geoms 和 sites 是 MuJoCo 元素,大致对应于物理世界中的刚体。那么为什么它们是分开的呢?原因在此解释,既有语义上的,也有计算上的。
首先是相似之处。Bodies、geoms 和 sites 都附有空间坐标系(尽管 bodies 还有一个坐标系,其中心位于刚体重心并与惯性主轴对齐)。这些坐标系的位置和姿态在每个时间步长通过前向运动学从 mjData.qpos
计算得出。前向运动学的结果在 mjData
中可用,bodies 的结果是 xpos、xquat 和 xmat,geoms 的结果是 geom_xpos 和 geom_xmat,sites 的结果是 site_xpos 和 site_xmat。
现在是不同之处。Bodies 用于构建运动学树,并作为其他元素(包括 geoms 和 sites)的容器。Bodies 具有空间坐标系、惯性属性,但不具有与外观或碰撞几何相关的属性。这是因为这些属性不影响物理(当然接触除外,但这单独处理)。如果你看过机器人学教科书中的运动学树图,刚体通常被画成无定形形状——为了说明它们的实际形状与物理无关。
Geoms(geometric primitive 的缩写)用于指定外观和碰撞几何。每个 geom 属于一个 body 并刚性附加到该 body。多个 geoms 可以附加到同一个 body。考虑到 MuJoCo 的碰撞检测器假定所有 geoms 都是凸的(如果网格不凸,它内部会用它们的凸包替换网格)这一事实,这一点特别有用。因此,如果你想建模一个非凸形状,你必须将其分解为凸 geoms 的并集并将它们全部附加到同一个 body 上。
Geom 也可以在 XML 中指定密度或质量值,模型编译器使用这些值计算父 body 的质量和惯性。质量要么直接指定,要么从 geom 的体积和density计算。惯性是根据质量、形状和均匀密度假设计算的。如果设置了shellinertia标志,质量被假定均匀分布在表面,density 被解释为单位面积质量,并相应计算对父 body 的惯性贡献。在实际仿真的 mjModel
中,geoms 不具有惯性属性。
Sites 是轻量级的 geoms。它们具有相同的外观属性,但不能参与碰撞,也不能用于推断 body 质量。另一方面,sites 可以做 geoms 做不到的事情:它们可以指定触摸传感器的体积、IMU 传感器的附着点、空间肌腱的路径、滑块曲柄执行器的端点。这些都是空间量,但它们不对应于应该具有质量或与其他实体碰撞的实体——这就是创建 site 元素的原因。Sites 也可以用于指定用户感兴趣的点(或者更确切地说是坐标系)。
以下示例说明了多个 sites 和 geoms 可以附加到同一个 body 上:在本例中,一个 body 上附加了两个 sites 和两个 geoms。
<mujoco>
<worldbody>
<body pos="0 0 0">
<geom type="sphere" size=".1" rgba=".9 .9 .1 1"/>
<geom type="capsule" pos="0 0 .1" size=".05 .1" rgba=".9 .9 .1 1"/>
<site type="box" pos="0 -.1 .3" size=".02 .02 .02" rgba=".9 .1 .9 1"/>
<site type="ellipsoid" pos="0 .1 .3" size=".02 .03 .04" rgba=".9 .1 .9 1"/>
</body>
</worldbody>
</mujoco>

此模型由 OpenGL 可视化器渲染为
注意红色的盒子。这是刚体惯性属性的等效惯量盒渲染,由 MuJoCo 内部生成。盒子位于 geoms 上方,但不在 sites 上方。这是因为仅使用 geoms(自动)推断了 body 的惯性属性。如果我们恰好知道后者,当然可以直接指定。但通常更方便的是让模型编译器根据附加到 body 的 geoms 推断这些 body 属性,使用均匀密度假设(geom 密度可以在 XML 中指定;默认是水的密度)。
关节坐标#
MuJoCo 与游戏引擎的关键区别之一在于 MuJoCo 在广义坐标或关节坐标下运行,而大多数游戏引擎在笛卡尔坐标下运行。这两种方法的区别可以概括如下
关节坐标
最适合复杂的运动学结构,例如机器人;
关节在默认情况下会焊接在一起的刚体之间增加自由度;
关节约束在表示中是隐式的,不能被违反;
仿真刚体的位置和姿态通过前向运动学从广义坐标获得,不能直接操纵(根刚体除外)。
笛卡尔坐标
最适合大量相互弹跳的刚体,例如分子动力学和盒子堆叠;
关节在默认情况下会自由浮动的刚体之间移除自由度;
关节约束是数值强制执行的,可以被违反;
仿真刚体的位置和姿态是显式表示的,并且可以直接操纵,尽管这可能会引入进一步的关节约束违反。
当处理属于包含运动学树的模型一部分的自由浮动刚体时,关节坐标可能会特别令人困惑。这将在下面澄清。
浮动对象#
在使用关节坐标时,您无法简单地将任意刚体的位置和姿态设置为您想要的任何值。要实现这种效果,您需要实现某种形式的逆运动学,它会计算一组(不一定唯一)的关节坐标,使得前向运动学将刚体放置在您想要的位置。
对于浮动物体(即通过自由关节与世界连接的物体)来说,情况则不同。这些物体的位置、方向以及线速度和角速度在 mjData.qpos
和 mjData.qvel
中明确表示,因此可以直接操作。
自由关节的语义如下。位置数据由7个数字组成(3D位置后跟单位四元数),而速度数据由6个数字组成(3D线速度后跟3D角速度)。自由关节的线性位置处于全局坐标系中,线速度也是如此。自由关节的方向(四元数)也处于全局坐标系中。然而,自由关节的旋转速度处于局部物体坐标系中。这与其说是一个设计决策,不如说是对四元数拓扑结构的正确使用。角速度存在于四元数切空间中,该空间是针对特定方向局部定义的,因此局部坐标系的角速度是自然的参数化。加速度定义在与相应速度相同的空间中。
自由关节始终在物体坐标系中定义,但计算上更有利的做法是将此坐标系与物体的惯性对齐。在 freejoint/align 属性的文档中阅读更多关于此选项的信息。