概述(Overview)#

简介(Introduction)#

MuJoCo 代表 Mu lti-Jo int dynamics with Co ntact(多关节接触动力学)。它是一个通用物理引擎,旨在促进机器人学、生物力学、图形与动画、机器学习以及其他需要快速准确模拟关节结构与环境交互的研究和开发。MuJoCo 最初由 Roboti LLC 开发,于 2021 年 10 月被 DeepMind 收购并 免费提供 ,并于 2022 年 5 月开源。MuJoCo 代码库可在 GitHub 上的 google-deepmind/mujoco 仓库获取。

MuJoCo 是一个 C/C++ 库,提供 C API,面向研究人员和开发者。运行时仿真模块经过调优以最大化性能,并操作由内置 XML 解析器和编译器预先分配的低级数据结构。用户使用原生的 MJCF 场景描述语言(一种旨在尽可能便于人类阅读和编辑的 XML 文件格式)来定义模型。也可以加载 URDF 模型文件。该库包含通过原生 GUI 进行的交互式可视化,使用 OpenGL 渲染。MuJoCo 还提供了大量用于计算物理相关量的实用函数。

MuJoCo 可用于实现基于模型的计算,例如控制综合、状态估计、系统辨识、机构设计、通过逆动力学进行数据分析以及机器学习应用的并行采样。它也可以用作更传统的模拟器,包括用于游戏和交互式虚拟环境。

关键特性(Key features)#

MuJoCo 拥有众多特性。此处我们概述最显著的部分。

广义坐标结合现代接触动力学(Generalized coordinates combined with modern contact dynamics)

物理引擎传统上分为两类。机器人和生物力学引擎在广义坐标或关节坐标中使用高效准确的递归算法。然而,它们要么忽略接触动力学,要么依赖早期的弹簧-阻尼器方法,这需要非常小的时间步长。游戏引擎使用更现代的方法,通过求解优化问题来找到接触力。然而,它们通常采用过度指定的笛卡尔表示,其中关节约束通过数值方式施加,当涉及复杂的运动学结构时会导致不准确和不稳定。MuJoCo 是第一个结合两者优势的通用引擎:在广义坐标中进行仿真和基于优化的接触动力学。其他模拟器最近也进行了调整以使用 MuJoCo 的方法,但这通常与其所有功能不兼容,因为它们并非从一开始就为此设计。习惯游戏引擎的用户起初可能会觉得广义坐标有违直觉;请参阅下面的 澄清(Clarifications) 部分。

柔软、凸且可解析求逆的接触动力学(Soft, convex and analytically-invertible contact dynamics)

在现代接触动力学方法中,由摩擦接触引起的力或冲量通常定义为线性或非线性互补问题(LCP 或 NCP)的解,这两者都是 NP 难问题。MuJoCo 基于不同的接触物理公式,该公式可简化为凸优化问题,如 计算(Computation) 章节中详细解释。我们的模型允许软接触和其他约束,并具有唯一定义的逆,便于数据分析和控制应用。可以选择优化算法,包括可处理椭圆摩擦锥的投影高斯-赛德尔方法的推广。该求解器统一处理摩擦接触(包括扭转和滚动摩擦)、无摩擦接触、关节和肌腱限制、关节和肌腱中的干摩擦,以及各种等式约束。

肌腱几何(Tendon geometry)

MuJoCo 可以模拟肌腱的 3D 几何形状——这些是遵循包裹和经由点约束的最小路径长度绳索。其机制与 OpenSim 中的类似,但实现了一组更受限的、封闭形式的包裹选项以加速计算。它还提供机器人专用的结构,如滑轮和耦合自由度。肌腱可用于驱动以及施加关于肌腱长度的不等式或等式约束。

通用驱动模型(General actuation model)

在使用与模型无关的 API 的同时,设计一个足够丰富的驱动模型具有挑战性。MuJoCo 通过采用一个抽象的驱动模型来实现这一目标,该模型可以具有不同类型的传动、力生成和内部动力学(即,使整体动力学变为三阶的状态变量)。这些组件可以被实例化,以统一的方式模拟电机、气动和液压缸、PD 控制器、生物肌肉和许多其他执行器。

可重构的计算流水线(Reconfigurable computation pipeline)

MuJoCo 有一个顶级的步进函数 mj_step(),它运行整个前向动力学并推进仿真状态。然而,在许多仿真之外的应用中,能够运行计算流水线的选定部分是有益的。为此,MuJoCo 提供了大量的 标志(flags),可以以任何组合设置,允许用户根据需要重新配置流水线,超越了通过 选项(options) 选择算法和算法参数的范围。此外,许多较低级别的函数可以直接调用。用户定义的回调可以实现自定义力场、执行器、碰撞例程和反馈控制器。

模型编译(Model compilation)

如上所述,用户在称为 MJCF 的 XML 文件格式中定义 MuJoCo 模型。然后,该模型由内置编译器编译成低级数据结构 mjModel,该结构经过交叉索引并针对运行时计算进行了优化。编译后的模型也可以保存在二进制的 MJB 文件中。

模型与数据的分离(Separation of model and data)

MuJoCo 在运行时将仿真参数分离到两个数据结构(C 结构体)中:

  • mjModel 包含模型描述,并期望保持不变。其中嵌入的其他结构包含仿真和可视化选项,这些选项需要偶尔更改,但这由用户完成。

  • mjData 包含所有动态变量和中间结果。它用作便笺本,所有函数在其中读取输入并写入输出——这些输出随后成为仿真流水线后续阶段的输入。它还包含一个预先分配和内部管理的堆栈,因此运行时模块在模型初始化后不需要调用内存分配函数。

mjModel 由编译器构建。mjData 在运行时构建,给定 mjModel。这种分离使得模拟多个模型以及每个模型的多个状态和控制变得容易,进而便于用于采样的 多线程(multi-threading)有限差分(finite differences)。顶级 API 函数反映了这种基本分离,并具有以下格式:

void mj_step(const mjModel* m, mjData* d);
交互式仿真与可视化(Interactive simulation and visualization)

原生的 3D 可视化器(3D visualizer) 提供网格和几何图元的渲染、纹理、反射、阴影、雾、透明度、线框、天空盒、立体可视化(在支持四缓冲 OpenGL 的显卡上)。此功能用于生成 3D 渲染,帮助用户深入了解物理仿真,包括视觉辅助,如自动生成的模型骨架、等效惯性框、接触位置和法线、可分离为法向和切向分量的接触力、外部扰动力、局部坐标系、关节和执行器轴以及文本标签。可视化器期望一个带有 OpenGL 渲染上下文的通用窗口,从而允许用户采用他们选择的 GUI 库。MuJoCo 分发的代码示例 simulate.cc 展示了如何使用 GLFW 库实现这一点。一个相关的可用性特性是能够“伸入”仿真,推动物体并观察物理响应。用户选择将施加外力和扭矩的物体,并实时看到扰动及其动态后果的渲染。这可用于直观地调试模型、测试反馈控制器的响应或将模型配置到所需的姿态。

强大而直观的建模语言(Powerful yet intuitive modeling language)

MuJoCo 拥有自己的建模语言,称为 MJCF。MJCF 的目标是提供对 MuJoCo 所有计算功能的访问,同时使用户能够快速开发新模型并进行实验。这个目标在很大程度上得益于广泛的 默认设置(default setting) 机制,该机制类似于内联在 HTML 中的层叠样式表(CSS)。虽然 MJCF 有许多元素和属性,但用户在任何一个给定模型中需要设置的 surprisingly few of them。这使得 MJCF 文件比许多其他格式更短、更易读。

复合柔性对象的自动生成(Automated generation of composite flexible objects)

MuJoCo 的软约束可用于模拟绳索、布料和可变形 3D 对象。这需要大量规则物体、关节、肌腱和约束协同工作。建模语言具有高级宏,这些宏由模型编译器自动扩展为必要的标准模型元素集合。重要的是,这些生成的柔性对象能够与仿真的其余部分完全交互。

模型实例(Model instances)#

在 MuJoCo 中有几个称为“模型”的实体。用户在 MJCF 或 URDF 编写的 XML 文件中定义模型。然后,软件可以在不同介质(文件或内存)和不同描述级别(高级或低级)上创建同一模型的多个实例。所有组合都是可能的,如下表所示:

高级(High level)

低级(Low level)

文件 (File)

MJCF/URDF (XML)

MJB (binary)

内存 (Memory)

mjSpec (C struct)

mjModel (C struct)

所有运行时计算都是使用 mjModel 执行的,它过于复杂,无法手动创建。这就是为什么我们有两个建模级别。高级别存在是为了用户方便:其唯一目的是被编译成可以进行计算的低级别模型。生成的 mjModel 可以加载和保存到二进制文件(MJB)中,然而这些文件是特定版本的且无法反编译,因此模型应始终维护为 XML 文件。

mjSpec C 结构体与 MJCF 文件格式一一对应。XML 加载器解释 MJCF 或 URDF 文件,创建相应的 mjSpec 并将其编译为 mjModel。用户可以以编程方式创建 mjSpec,然后将其保存为 MJCF 或进行编译。程序化模型创建和编辑在 模型编辑(Model Editing) 章节中描述。

下图显示了获取 mjModel 的不同路径:

  • (文本编辑器) → MJCF/URDF 文件 → (MuJoCo 解析器 → mjSpec → 编译器) → mjModel

  • (用户代码) → mjSpec → (MuJoCo 编译器) → mjModel

  • MJB 文件 → (模型加载器) → mjModel

示例(Examples)#

这是一个 MuJoCo MJCF 格式的简单模型。它定义了一个固定在世界上的平面、一个更好地照亮物体和投射阴影的光源,以及一个具有 6 个自由度(这就是“free”关节的作用)的浮动盒子。

hello.xml:

<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 可视化器将此模型渲染为:

_images/hello.png

如果模拟此模型,盒子将落到地面上。下面给出了无渲染的被动动力学的基本模拟代码。

 1#include "mujoco.h"
 2#include "stdio.h"
 3
 4char error[1000];
 5mjModel* m;
 6mjData* d;
 7
 8int main(void) {
 9  // 从文件加载模型并检查错误
10  m = mj_loadXML("hello.xml", NULL, error, 1000);
11  if (!m) {
12    printf("%s\n", error);
13    return 1;
14  }
15
16  // 创建与模型对应的数据
17  d = mj_makeData(m);
18
19  // 运行模拟 10 秒钟
20  while (d->time < 10)
21    mj_step(m, d);
22
23  // 释放模型和数据
24  mj_deleteData(d);
25  mj_deleteModel(m);
26
27  return 0;
28}

这从技术上讲是一个 C 文件,但也是一个合法的 C++ 文件。确实,MuJoCo API 与 C 和 C++ 都兼容。通常用户代码会用 C++ 编写,因为它增加了便利性,并且不会牺牲效率,因为计算瓶颈在已经高度优化的模拟器中。

函数 mj_step() 是顶级函数,它将仿真状态推进一个时间步长。这个例子当然只是一个被动的动态系统。当用户指定控制或施加力并开始与系统交互时,事情会变得更有趣。

接下来我们提供一个更复杂的示例,说明 MJCF 的几个特性。考虑以下 example.xml

 1<mujoco model="example">
 2  <default>
 3    <geom rgba=".8 .6 .4 1"/>
 4  </default>
 5
 6  <asset>
 7    <texture type="skybox" builtin="gradient" rgb1="1 1 1" rgb2=".6 .8 1" width="256" height="256"/>
 8  </asset>
 9
10  <worldbody>
11    <light pos="0 1 1" dir="0 -1 -1" diffuse="1 1 1"/>
12    <body pos="0 0 1">
13      <joint type="ball"/>
14      <geom type="capsule" size="0.06" fromto="0 0 0  0 0 -.4"/>
15      <body pos="0 0 -0.4">
16        <joint axis="0 1 0"/>
17        <joint axis="1 0 0"/>
18        <geom type="capsule" size="0.04" fromto="0 0 0  .3 0 0"/>
19        <body pos=".3 0 0">
20          <joint axis="0 1 0"/>
21          <joint axis="0 0 1"/>
22          <geom pos=".1 0 0" size="0.1 0.08 0.02" type="ellipsoid"/>
23          <site name="end1" pos="0.2 0 0" size="0.01"/>
24        </body>
25      </body>
26    </body>
27
28    <body pos="0.3 0 0.1">
29      <joint type="free"/>
30      <geom size="0.07 0.1" type="cylinder"/>
31      <site name="end2" pos="0 0 0.1" size="0.01"/>
32    </body>
33  </worldbody>
34
35  <tendon>
36    <spatial limited="true" range="0 0.6" width="0.005">
37      <site site="end1"/>
38      <site site="end2"/>
39    </spatial>
40  </tendon>
41</mujoco>
_static/example.mp4

这个模型是一个 7 自由度的手臂“握住”一根绳子,另一端连接着一个圆柱体。这根绳子是作为具有长度限制的肌腱实现的。肩部有一个球关节,肘部和腕部各有一对铰链关节。圆柱体内部的盒子表示一个“free”关节。XML 中的外部 body 元素是必需的 worldbody。注意,在两个物体之间使用多个关节不需要创建虚拟物体。#

MJCF 文件包含指定模型所需的最少信息。胶囊由空间中的线段定义——在这种情况下只需要胶囊的半径。物体框架的位置和方向是从属于它们的 geom 推断出来的。惯性属性是在均匀密度假设下从 geom 形状推断出来的。两个 site 被命名是因为肌腱定义需要引用它们,但没有其他东西被命名。仅针对铰链关节定义了关节轴,而没有为球关节定义。碰撞规则是自动定义的。摩擦属性、重力、仿真时间步长等均设置为其默认值。顶部指定的默认 geom 颜色适用于所有 geoms。

除了将编译后的模型以二进制 MJB 格式保存外,我们还可以将其保存为 MJCF 或人类可读的文本;请分别参阅 example_saved.xmlexample_saved.txt。XML 版本与原始版本相似,而文本版本包含来自 mjModel 的所有信息。将文本版本与 XML 版本进行比较,可以揭示模型编译器为我们做了多少工作。

模型元素(Model elements)#

本节简要描述可以包含在 MuJoCo 模型中的所有元素。稍后我们将更详细地解释底层计算、在 MJCF 中指定元素的方式以及它们在 mjModel 中的表示。

选项(Options)#

每个模型都有三组选项,如下所列。它们总是被包含。如果它们的值未在 XML 文件中指定,则使用默认值。这些选项的设计使得用户可以在每个仿真时间步长之前更改它们的值。然而,在一个时间步长内,不应更改任何选项。

mjOption#

此结构包含影响物理仿真的所有选项。它用于选择算法并设置其参数、启用和禁用仿真流水线的不同部分,以及调整系统级物理属性(如重力)。

mjVisual#

此结构包含所有可视化选项。还有额外的 OpenGL 渲染选项,但这些选项依赖于会话,不是模型的一部分。

mjStatistic#

此结构包含关于模型的统计信息,由编译器计算:平均物体质量、模型的空间范围等。包含它是为了提供信息,也是因为可视化器使用它进行自动缩放。

资源(Assets)#

资源本身不是模型元素。模型元素可以引用它们,在这种情况下,资源会以某种方式改变引用元素的属性。一个资源可以被多个模型元素引用。由于包含资源的唯一目的是引用它,并且引用只能通过名称完成,因此每个资源都有一个名称(在适用时可以从文件名推断)。相反,常规元素的名称可以保留未定义。

网格(Mesh)#

MuJoCo 可以从 OBJ 文件和二进制 STL 加载三角网格。可以使用诸如 MeshLab 之类的软件从其他格式进行转换。虽然可以加载任何三角形集合并将其可视化为网格,但碰撞检测器使用凸包进行处理。有编译时选项用于缩放网格,以及将原始几何形状拟合到它。网格还可以用于自动推断惯性属性——通过将其视为三角棱锥的并集并组合它们的质量和惯性。请注意,网格没有颜色,而是使用引用 geom 的材质属性为网格着色。相反,所有空间属性由网格数据确定。MuJoCo 支持 OBJ 和用于法线和纹理坐标的自定义二进制文件格式。网格也可以直接嵌入 XML 中。

蒙皮(Skin)#

蒙皮网格(或 skins)是其形状可以在运行时变形的网格。它们的顶点附着在刚体(在此上下文中称为骨骼)上,并且每个顶点可以属于多个骨骼,从而导致蒙皮的平滑变形。蒙皮是纯粹的可视化对象,不影响物理,但它们可以显著增强视觉真实感。蒙皮可以从自定义二进制文件加载,或直接嵌入 XML 中,类似于网格。在自动生成复合柔性对象时,模型编译器也会为这些对象生成蒙皮。

高度场(Height field)#

高度场可以从 PNG 文件(内部转换为灰度)或自定义二进制格式的文件中加载,该格式稍后描述。高度场是 elevation 数据的矩形网格。编译器将数据标准化到范围 [0-1]。然后,高度场的实际空间范围由引用 geom 的大小参数确定。高度场只能从附加到世界物体的 geoms 引用。为了渲染和碰撞检测的目的,网格矩形被自动三角化,因此高度场被视为三角棱柱的并集。与此类复合物体的碰撞检测原则上可以为单个 geom 对生成大量接触点。如果发生这种情况,仅保留前 64 个接触点。其原理是高度场应用于模拟地形图,其空间特征相对于仿真中的其他物体较大,因此对于设计良好的模型,接触数量将会很少。

纹理(Texture)#

纹理可以从 PNG 文件加载,或由编译器基于用户定义的程序参数合成。还有一个选项是在模型创建时将纹理留空,并在以后的运行时更改它——以便在 MuJoCo 仿真中渲染视频,或创建其他动态效果。可视化器支持两种类型的纹理映射:2D 和立方体。2D 映射对于平面和高度场很有用。立方体映射对于将纹理“收缩包裹”在 3D 物体周围而无需指定纹理坐标非常有用。它也用于创建天空盒。立方体贴图的六个面可以从单独的图像文件加载,或从一个复合图像文件加载,或通过重复相同的图像生成。与所有其他直接被模型元素引用的资源不同,纹理只能从另一个资源(即材质)引用,然后该资源被模型元素引用。

材质(Material)#

材质用于控制 geoms、sites 和 tendons 的外观。这是通过从相应的模型元素引用材质来完成的。外观包括纹理映射以及与以下 OpenGL 灯光交互的其他属性:RGBA、镜面反射度、光泽度、自发光。材质也可用于使物体具有反射性。目前,反射仅渲染在平面上和盒子的 Z+ 面上。请注意,模型元素也可以有其本地 RGBA 参数来设置颜色。如果同时指定了材质和本地 RGBA,则以本地定义为准。

运动学树(Kinematic tree)#

MuJoCo 模拟一组刚体的动力学,这些刚体的运动通常受到约束。系统状态用关节坐标表示,物体被明确地组织成运动学树。除了顶层的“世界”物体外,每个物体都有一个唯一的父物体。不允许运动学循环;如果需要循环关节,应使用等式约束对其进行建模。因此,MuJoCo 模型的骨干是由嵌套的 body 定义形成的一个或几个运动学树;一个孤立的浮动物体也算作一棵树。下面列出的其他几个元素在一个 body 内定义并属于该 body。这与后面列出的独立元素形成对比,后者不能与单个 body 关联。

物体(Body)#

物体具有质量和惯性属性,但不具有任何几何属性。相反,几何形状(或 geoms)附加到物体上。每个物体有两个坐标系:用于定义它以及相对于它定位其他元素的框架,以及一个以物体质心为中心并与其惯性主轴对齐的惯性框架。因此,物体惯性矩阵在此框架中是对角的。在每个时间步,MuJoCo 递归地计算前向运动学,得到全局笛卡尔坐标中所有物体的位置和方向。这为所有后续计算提供了基础。

关节(Joint)#

关节在物体内定义。它们在物体与其父物体之间创建运动自由度(DOFs)。没有关节时,物体将焊接在其父物体上。这与游戏引擎相反,游戏引擎使用过度指定的笛卡尔坐标,其中关节是移除自由度而不是添加自由度。有四种类型的关节:球关节、滑动关节、铰链关节和创建浮动物体的“自由关节”。一个物体可以有多个关节。通过这种方式自动创建复合关节,而无需定义虚拟物体。球关节和自由关节的方向分量用单位四元数表示,并且 MuJoCo 中的所有计算都尊重四元数的属性。

关节参考(Joint reference)#

参考姿态是存储在 mjModel.qpos0 中的关节位置向量。它对应于模型处于其初始配置时关节的数值。在我们之前的示例中,肘部是在 90° 角的弯曲配置中创建的。但是 MuJoCo 不知道肘部是什么,因此默认情况下它将此关节配置视为具有数值 0。我们可以覆盖默认行为,并指定初始配置对应于 90°,使用 joint 的 ref 属性。所有关节的参考值被组装成向量 mjModel.qpos0。每当仿真重置时,关节配置 mjData.qpos 被设置为 mjModel.qpos0。在运行时,关节位置向量相对于参考姿态进行解释。特别地,关节施加的空间变换量是 mjData.qpos - mjModel.qpos0。这种变换是除了存储在 mjModel 的 body 元素中的父子平移和旋转偏移之外的变换。ref 属性仅适用于标量关节(滑动和铰链)。对于球关节,保存在 mjModel.qpos0 中的四元数始终是 (1,0,0,0),对应于空旋转。对于自由关节,浮动物体的全局 3D 位置和四元数保存在 mjModel.qpos0 中。

弹簧参考(Spring reference)#

这是所有关节和肌腱弹簧达到其静止长度的姿态。当关节配置偏离弹簧参考姿态时会产生弹簧力,并且与偏离量成线性关系。弹簧参考姿态保存在 mjModel.qpos_spring 中。对于滑动和铰链关节,弹簧参考使用属性 springref 指定。对于球关节和自由关节,弹簧参考对应于初始模型配置。

自由度(DOF)#

自由度与关节密切相关,但不是一一对应的,因为球关节和自由关节有多个自由度。可以将关节视为指定位置信息,而将自由度视为指定速度和力信息。更正式地说,关节位置是系统配置流形上的坐标,而关节速度是该流形在当前位置处的切空间上的坐标。自由度具有与速度相关的属性,如摩擦损失、阻尼、电枢惯性。作用在系统上的所有广义力都在自由度空间中表示。相反,关节具有与位置相关的属性,如限制和弹簧刚度。自由度不是由用户直接指定的。相反,它们是由编译器根据关节创建的。

几何体(Geom)#

Geoms 是刚性附着在物体上的 3D 形状。多个 geoms 可以附加到同一个物体上。考虑到 MuJoCo 仅支持凸 geom-geom 碰撞,并且创建非凸物体的唯一方法是将其表示为凸 geoms 的并集,这一点特别有用。除了碰撞检测和随后接触力的计算之外,geoms 还用于渲染,以及在省略物体质量和惯性时自动推断它们。MuJoCo 支持几种原始几何形状:平面、球体、胶囊体、椭球体、圆柱体、盒子。一个 geom 也可以是网格或高度场;这是通过引用相应的资源来完成的。Geoms 具有许多影响仿真和可视化的材质属性。

站点(Site)#

站点本质上是轻量级的 geoms。它们表示物体框架内感兴趣的位置。站点不参与碰撞检测或惯性属性的自动计算,但是它们可用于指定其他对象(如传感器、肌腱布线、滑块曲柄端点)的空间属性。

相机(Camera)#

可以在模型中定义多个相机。总有一个默认相机,用户可以在交互式可视化器中用鼠标自由移动。然而,定义附加的相机通常很方便,这些相机要么固定在世界中,要么附加在其中一个物体上并随之移动。除了相机位置和方向外,用户还可以调整垂直视场和用于立体渲染的瞳距,以及创建立体虚拟环境所需的斜投影。当模拟具有不完美光学的真实相机时,可以指定水平和垂直方向的单独焦距以及非中心的主点。

光源(Light)#

光源可以固定在世界物体上或附加在移动的物体上。可视化器提供对 OpenGL(固定功能)中完整光照模型的访问,包括环境光、漫反射和镜面反射分量、衰减和截止、位置光和方向光、雾。光源,或者更确切地说是被它们照亮的物体,也可以投射阴影。然而,与材质反射类似,每个投射阴影的光源会增加一次渲染通道,因此应谨慎使用此功能。详细记录光照模型超出了本章的范围;请参阅 OpenGL 文档。请注意,除了用户在运动学树中定义的光源外,还有一个随相机移动的默认头灯。其属性通过 mjVisual 选项进行调整。

独立元素(Stand-alone)#

这里我们描述不属于单个物体的模型元素,因此在运动学树之外描述。

肌腱(Tendon)#

肌腱是标量长度元素,可用于驱动、施加限制和等式约束,或创建弹簧阻尼器和摩擦损失。有两种类型的肌腱:固定肌腱和空间肌腱。固定肌腱是(标量)关节位置的线性组合。它们对于建模机械耦合很有用。空间肌腱被定义为通过一系列指定站点(或经由点)或围绕指定 geoms 缠绕的最短路径。仅支持球体和圆柱体作为包裹 geoms,并且圆柱体在包裹目的上被视为具有无限长度。为了避免肌腱从包裹 geom 的一侧突然跳到另一侧,用户还可以指定首选侧。如果肌腱路径中有多个包裹 geoms,它们必须由站点分隔,以避免需要迭代求解器。空间肌腱也可以使用滑轮分成多个分支。

执行器(Actuator)#

MuJoCo 提供了一个灵活的执行器模型,具有三个可以独立指定的组件。它们共同决定了执行器的工作方式。通过以协调的方式指定这些组件,可以获得常见的执行器类型。这三个组件是传动、激活动力学和力生成。传动指定了执行器如何连接到系统的其余部分;可用类型是关节、肌腱和滑块曲柄。激活动力学可用于模拟气动或液压缸以及生物肌肉的内部激活状态;使用此类执行器会使整体系统动力学变为三阶。力生成机制决定了提供给执行器作为输入的标量控制信号如何映射成标量力,然后该标量力通过从传动推断出的力矩臂映射成广义力。

传感器(Sensor)#

MuJoCo 可以生成模拟传感器数据,这些数据保存在全局数组 mjData.sensordata 中。结果不用于任何内部计算;而是提供它,因为用户可能需要它进行自定义计算或数据分析。可用的传感器类型包括触摸传感器、惯性测量单元(IMU)、力-扭矩传感器、关节和肌腱位置和速度传感器、执行器位置、速度和力传感器、运动捕捉标记位置和四元数以及磁力计。其中一些需要额外的计算,而其他一些则从 mjData 的相应字段复制。还有一个用户传感器,允许用户代码在传感器数据数组中插入任何其他感兴趣的量。MuJoCo 还具有离屏渲染功能,使得模拟颜色和深度相机传感器变得简单。这不包含在标准传感器模型中,而是必须以编程方式完成,如代码示例 simulate.cc 中所示。

等式约束(Equality)#

等式约束可以施加超出运动学树结构以及其中定义的关节/自由度已施加的约束之外的额外约束。它们可用于创建循环关节,或者通常用于建模机械耦合。强制执行这些约束的内部力与所有其他约束力一起计算。可用的等式约束类型有:在一点连接两个物体(在运动学树外创建球关节);将两个物体焊接在一起;固定关节或肌腱的位置;通过三次多项式耦合两个关节或两个肌腱的位置;将 flex(即可变形网格)的边缘约束到其初始长度。

柔性体(Flex)#

柔性体在 MuJoCo 3.0 中添加。它们代表可变形网格,可以是 1、2 或 3 维的(因此它们的元素是胶囊、三角形或四面体)。与刚性附着在单个物体上的静态形状的 geoms 不同,flex 的元素是可变形的:它们通过连接多个物体来构造,因此物体的位置和方向决定了运行时 flex 元素的形状。这些可变形元素支持碰撞和接触力,并产生被动和约束力,以柔和地保持可变形实体的形状。提供了自动化功能来从文件加载网格,构建对应于网格顶点的物体,构建对应于网格面(或线或四面体,取决于维度)的 flex 元素,并获得相应的可变形网格。

接触对(Contact pair)#

MuJoCo 中的接触生成是一个精细的过程。被检查接触的 geom 对可以来自两个来源:自动 proximity 测试和其他统称为“动态”的过滤器,以及模型中提供的显式 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)#

关键帧是仿真状态变量的快照。它包含关节位置、关节速度、存在时的执行器激活以及仿真时间的向量。模型可以包含一个关键帧库。它们对于将系统状态重置到感兴趣的点很有用。请注意,关键帧不旨在在模型中存储轨迹数据;应为此目的使用外部文件。

澄清(Clarifications)#

读者可能具有其他物理模拟器和相关约定以及不与 MuJoCo 对齐的通用编程实践的经验。这有可能导致混淆。本节的目标是抢先澄清最可能令人困惑的方面;它介于 FAQ 和关于选定主题的教程之间。我们将需要参考文档后面涵盖的材料,但下面的文本尽可能自包含和介绍性。

发散(Divergence)#

仿真的发散发生在状态元素快速趋于无穷大时。在 MuJoCo 中,这通常表现为 mjWARN_BADQACC 警告。发散是所有物理模拟的通病,并不一定表示模型不良或模拟器中的错误,而是暗示时间步长对于给定的积分器选择来说太大。在物理模拟中,速度(大时间步长)和稳定性(小时间步长)之间总是存在紧张关系。一个为速度调整良好的模型具有不会发散的最大可能时间步长,这通常意味着它在极端条件下 可能 会发散。在这个意义上,罕见 的发散情况实际上可能表明模型调整良好。在所有情况下,应该可以通过减少时间步长和/或切换到更稳定的 积分器(integrator) 来防止发散。如果失败,罪魁祸首就不同了。例如,在物体初始化时处于穿透状态的模型中,大的排斥力可能会将它们推开并导致发散。

单位未指定(Units are unspecified)#

MuJoCo 不指定基本的物理单位。用户可以根据自己的选择来解释单位系统,只要它是一致的。要理解这一点,请考虑一个例子:一个 1 米长、重 1 千克、具有 1 牛顿推进器的宇宙飞船的动力学,与一个 1 厘米长、重 1 克、具有 1 达因推进器的宇宙飞船的动力学是相同的。这是因为 MKSCGS 都是一致的单位系统。这个特性允许用户根据需要缩放他们的模型,这在模拟非常小或非常大的物体以改善模拟的数值特性时非常有用。

也就是说,鼓励用户使用 MKS,因为有两个地方 MuJoCo 使用了类似 MKS 的默认值:

  • gravity 的默认值是 (0, 0, -9.81),这对应于地球表面的重力(以 MKS 为单位)。 请注意,这并没有真正指定 MKS 单位系统,因为我们可能在 土卫二(Enceladus) 上使用 CGS。

  • geom density 的默认值(用于推断 body 的质量和惯性)是 1000,这对应于 MKS 中水的密度。

一旦选择了一个一致的基本单位系统(长度、质量、时间),所有衍生单位都对应于这个系统,就像在 量纲分析(Dimensional Analysis) 中一样。例如,如果我们的模型被解释为使用 MKS,那么力和扭矩分别以牛顿(Newton)和牛顿米(Newton-Meter)为单位。

角度(Angles): 虽然角度可以在 MJCF 中使用度(degrees)来指定(并且度确实是 默认值),但所有角度量在 mjModelmjData 中都以 弧度(radians) 表示。因此,例如,如果我们使用 MKS,由 陀螺仪(gyroscopes) 报告的角速度将以 rad/s 为单位,而铰链关节的刚度将以 Nm/rad 为单位。

令人惊讶的碰撞(Surprising Collisions)#

MuJoCo 默认排除属于具有直接父子关系的 body 对的 geoms 之间的碰撞。例如,考虑上面 示例(Examples) 部分中的手臂模型:即使在胶囊几何体(capsule geoms)发生穿透,“肘部”也没有碰撞,因为前臂是上臂的直接子级。

然而,如果父级是静态 body,即世界 body(world body),或相对于世界 body 没有任何自由度的 body,则**不应用**此排除规则。这种行为在 碰撞检测(Collision detection) 部分有文档说明,可以防止物体掉落地板或穿过墙壁。然而,这种行为常常导致以下情况:

用户注释掉了一个浮动基模型的根关节,也许是为了防止它掉落;现在基 body 被视为静态的,出现了之前没有的新碰撞,用户感到困惑。有两种简单的方法可以避免这个问题:

  1. 不要移除根关节。也许 禁用重力(disable gravity) 并可能添加一些 流体粘度(fluid viscosity) 就足以防止你的模型移动太多。

  2. 使用 碰撞过滤(collision filtering) 显式禁用不需要的碰撞,可以通过设置相关的 contypeconaffinity 属性,或者使用 contact exclude 指令来实现。

非面向对象(Not object-oriented)#

面向对象编程是一个非常有用的抽象,它建立在更基础(且更接近硬件)的数据结构与操作它们的函数的概念之上。对象是数据结构和函数的集合,它们对应于一个语义实体,因此它们之间的依赖关系比与应用程序其余部分的依赖关系更强。我们不在这里使用它的原因是,依赖结构使得自然的实体是整个物理模拟器。我们没有使用对象,而是使用了少量数据结构和大量操作它们的函数。

我们仍然使用一种分组方式,但它不同于面向对象的方法。我们将模型(mjModel)与数据(mjData)分开。这两者都是数据结构。模型包含描述被建模物理系统的恒定属性所需的一切,而数据包含随时间变化的状态和内部计算的可重用中间结果。所有顶级函数都期望指向 mjModelmjData 的指针作为参数。通过这种方式,我们避免了污染工作空间并干扰多线程的全局变量,但我们实现这一目标的方式与面向对象编程实现相同效果的方式不同。

柔软性和滑动(Softness and slip)#

正如我们将在 计算(Computation) 章节详细解释的那样,MuJoCo 基于一个关于接触和其他约束的物理学的数学模型。这个模型本质上是柔软的(soft),因为更用力地推一个约束总是会导致更大的加速度,因此逆动力学可以被唯一定义。这是可取的,因为它产生了一个凸优化问题,并支持依赖于逆动力学的分析,而且,我们在实践中需要建模的大多数接触都有一定的柔软性。然而,一旦我们允许软约束,我们实际上就在创建一种新型的动力学——即变形动力学——现在我们必须指定这些动力学的行为方式。这需要对接触和其他约束进行精细的参数化,涉及可以按约束设置的属性 solrefsolimp,这将在后面描述。

这种软模型的一个经常令人困惑的方面是,逐渐的接触滑动(slip)无法避免。类似地,摩擦关节会在重力作用下逐渐屈服。这不是因为求解器无法防止滑动(即达到摩擦锥或摩擦损失极限),而是因为它一开始就没有试图防止滑动。回想一下,对抗给定约束的更大的力必须导致更大的加速度。如果要完全抑制滑动,这个关键属性就必须被违反。所以,如果你在模拟中看到逐渐的滑动,直观的解释可能是摩擦力不足,但在 MuJoCo 中这很少是原因。相反,需要调整 solrefsolimp 参数向量以减少这种效应。增加约束阻抗(solimp 的前两个元素)以及全局设置 mjModel.opt.impratio 可能特别有效。这种调整通常需要更小的时间步长以保持模拟稳定,因为它们使非线性动力学更难以数值积分。牛顿求解器(Newton solver)通常更准确,也能减少滑动。

对于希望完全抑制滑动的情况,有第二个 noslip 求解器,它在主求解器之后运行。它通过忽略约束柔软性来更新摩擦维度中的接触力。然而,当使用此选项时,MuJoCo 不再解决它设计用来解决的凸优化问题,并且模拟可能变得不那么稳健。因此,使用具有椭圆摩擦锥和大 impratio 值的牛顿求解器是减少滑动的推荐方法。更详细的建议,请参见建模章节中的 防止滑动(preventing slip)

类型、名称、ID(Types, names, ids)#

MuJoCo 支持大量模型元素,如前面所述。每种元素类型在 mjModel 中都有一个对应的部分列出其各种属性。例如,关节限制数据在数组中:

mjtNum* jnt_range;             // 关节限制 (njnt x 2)

每个数组的大小(本例中为 njnt)也在 mjModel 中给出。第一个关节的限制首先包含,然后是第二个关节的限制,依此类推。这种顺序反映了 MuJoCo 中所有矩阵都具有行优先(row-major)格式的事实。

可用的元素类型在 mjmodel.h 中定义,在枚举类型 mjtObj 中。这些枚举主要在内部使用。一个例外是 MuJoCo API 中的函数 mj_name2idmj_id2name,它们将元素名称映射到整数 id,反之亦然。这些函数接受一个元素类型作为输入。

在 XML 中命名模型元素是可选的。同一类型的两个元素(例如,两个关节)不能具有相同的名称。仅当某个元素需要在模型的其他地方被引用时才需要命名;在 XML 中的引用只能通过名称进行。模型编译后,名称仍然存储在 mjModel 中以方便用户,尽管它们对模拟没有进一步的影响。名称对于查找相应的整数 id 以及渲染很有用:例如,如果你启用关节标签,每个关节旁边将显示一个字符串(未定义名称的元素被标记为“joint N”,其中 N 是 id)。

元素的整数 id 对于索引 MuJoCo 数据数组至关重要。id 是从 0 开始的,遵循 C 语言的惯例。假设我们已经有了 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。

Body、Geom、Site(Bodies, geoms, sites)#

Body、geom 和 site 是 MuJoCo 元素,它们大致对应于物理世界中的刚体。那么为什么它们是分开的呢?原因既有语义上的,也有计算上的,在此解释。

首先是相似之处。Body、geom 和 site 都有附加的空间框架(尽管 body 还有第二个框架,该框架以 body 的质心为中心并与主惯性轴对齐)。这些框架的位置和方向在每个时间步从 mjData.qpos 通过正向运动学计算得到。正向运动学的结果在 mjData 中可用,分别是 body 的 xpos、xquat 和 xmat,geom 的 geom_xpos 和 geom_xmat,site 的 site_xpos 和 site_xmat。

现在是区别。Body 用于构建运动学树,并且是其他元素的容器,包括 geoms 和 sites。Body 有一个空间框架、惯性属性,但没有与外观或碰撞几何相关的属性。这是因为这些属性不影响物理(当然接触除外,但它们是单独处理的)。如果你看过机器人教科书中的运动学树图,body 通常被画成无定形的形状——目的是说明它们的实际形状与物理无关。

Geom(几何图元 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 传感器的附着、空间肌腱(spatial tendons)的路径、滑块曲柄(slider-crank)执行器的端点。这些都是空间量,但它们不对应于应该具有质量或与其他实体碰撞的实体——这就是创建 site 元素的原因。Sites 也可用于指定用户感兴趣的点(或者更确切地说是框架)。

以下示例说明了可以将多个 sites 和 geoms 附加到同一个 body 的观点:本例中两个 sites 和两个 geoms 附加到一个 body。

<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>
_images/bodygeomsite.png

OpenGL 可视化器将此模型渲染为:

注意红色的盒子。这是 body 惯性属性的等效惯性盒(equivalent-inertia box)渲染,由 MuJoCo 内部生成。盒子覆盖在 geoms 上,但不覆盖在 sites 上。这是因为只有 geoms 被用来(自动)推断 body 的惯性属性。如果我们碰巧知道后者,我们当然可以直接指定它们。但通常更方便的是让模型编译器使用均匀密度的假设(geom 密度可以在 XML 中指定;默认是水的密度)从附加到它的 geoms 推断这些 body 属性。

关节坐标(Joint coordinates)#

MuJoCo 和游戏引擎之间的一个关键区别是 MuJoCo 在广义坐标或关节坐标中运行,而大多数游戏引擎在笛卡尔坐标中运行。这两种方法之间的差异可以总结如下:

关节坐标:

  • 最适合精细的运动学结构,例如机器人;

  • 关节在默认焊接在一起的 body 之间**增加**自由度;

  • 关节约束在表示中是隐含的,不能被违反;

  • 模拟 body 的位置和方向是通过正向运动学从广义坐标获得的,不能直接操作(根 body 除外)。

笛卡尔坐标:

  • 最适合许多相互碰撞的 body,如分子动力学和箱子堆叠;

  • 关节在默认自由浮动的 body 之间**移除**自由度;

  • 关节约束是通过数值强制执行的,并且可能被违反;

  • 模拟 body 的位置和方向被显式表示,可以直接操作,尽管这可能会引入进一步的关节约束违反。

当处理作为也包含运动学树的模型一部分的自由浮动 body 时,关节坐标可能特别令人困惑。下面将对此进行澄清。

浮动对象(Floating objects)#

在关节坐标下工作时,你不能简单地将任意 body 的位置和方向设置为你想要的任何值。要达到这种效果,你必须实现某种形式的逆运动学,它计算一组(不一定唯一)关节坐标,使得正向运动学将 body 放置在你想要的位置。

对于浮动 body(即通过自由关节(free joint)连接到世界的 body)来说,情况有所不同。这些 body 的位置和方向以及线速度和角速度都明确地表示在 mjData.qposmjData.qvel 中,因此可以直接操作。

自由关节的语义如下。位置数据是 7 个数字(3D 位置后跟单位四元数),而速度数据是 6 个数字(3D 线速度后跟 3D 角速度)。自由关节的线性位置在全局框架中,线速度也是如此。自由关节的方向(四元数)也在全局框架中。然而,自由关节的旋转速度在局部 body 框架中。这与其说是一个设计决定,不如说是对四元数拓扑的正确使用。角速度存在于四元数切空间中,该空间是为某个方向局部定义的,因此框架局部的角速度是自然的参数化方式。加速度在与相应速度相同的空间中定义。

自由关节总是在 body 框架中定义,然而在计算上有利于将此框架与 body 的惯性对齐。在 freejoint/align 属性的文档中阅读更多关于此选项的信息。