概述#

简介#

MuJoCoMulti-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-难的。MuJoCo 基于一种不同的接触物理学公式,它将问题简化为一个凸优化问题,具体在计算章节中有详细解释。我们的模型允许软接触和其他约束,并且具有唯一定义的逆过程,便于数据分析和控制应用。有多种优化算法可供选择,包括对投影高斯-赛德尔方法的推广,该方法可以处理椭圆摩擦锥。求解器统一处理了摩擦接触(包括扭转和滚动摩擦)、无摩擦接触、关节和肌腱限位、关节和肌腱中的干摩擦,以及各种等式约束。

肌腱几何

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 文件中定义模型。然后,软件可以在不同的媒介(文件或内存)和不同的描述级别(高级或低级)上创建同一模型的多个实例。所有组合都是可能的,如下表所示

高级

低级

文件

MJCF/URDF (XML)

MJB (二进制)

内存

mjSpec (C 结构体)

mjModel (C 结构体)

所有运行时计算都是用 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 个自由度(DOF)的浮动盒子(这就是“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

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

#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 文件包含了指定模型所需的最少信息。胶囊体由空间中的线段定义——在这种情况下,只需要胶囊的半径。物体坐标系的位置和方向是根据属于它们的几何体(geom)推断出来的。惯性属性是根据几何体形状在均匀密度假设下推断的。两个站点(site)被命名是因为肌腱定义需要引用它们,但其他任何东西都没有命名。关节轴仅为铰链关节定义,而球形关节则没有。碰撞规则是自动定义的。摩擦属性、重力、模拟时间步长等都设置为默认值。在顶部指定的默认几何体颜色适用于所有几何体。

除了将编译后的模型保存为二进制 MJB 格式外,我们还可以将其保存为 MJCF 或人类可读的文本;分别参见 example_saved.xmlexample_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)的尺寸参数决定。高度场只能从附着于世界物体的几何体中引用。为了渲染和碰撞检测,网格矩形会自动进行三角剖分,因此高度场被视为三角棱柱的并集。与这种复合对象的碰撞检测原则上可以为单个几何体对生成大量的接触点。如果发生这种情况,只保留前 64 个接触点。其基本原理是,高度场应用于模拟地形图,其空间特征相对于模拟中的其他物体要大得多,因此对于设计良好的模型,接触点的数量会很小。

纹理#

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

材质#

材质用于控制几何体(geom)、站点(site)和肌腱(tendon)的外观。这是通过从相应的模型元素引用材质来完成的。外观包括纹理映射以及与下面的 OpenGL 光源相互作用的其他属性:RGBA、镜面反射度、光泽度、自发光。材质也可以用来使物体具有反射性。目前,反射仅在平面和盒子的 Z+ 面上渲染。请注意,模型元素也可以有其局部的 RGBA 参数来设置颜色。如果同时指定了材质和局部 RGBA,则局部定义优先。

运动树#

MuJoCo 模拟一组刚体的动力学,这些刚体的运动通常是受约束的。系统状态用关节坐标表示,物体被明确地组织成运动树。树结构由 mjModel.body_parentid 给出,这是一个长度为 nbody >= 1 的整数数组。顶层的“世界”物体总是存在(id 为 0)并且是它自己的父物体,因此 body_parentid[0] == 0 并且对于所有其他 ibody_parentid[i] < i。请注意,世界物体和其他静态(无关节的)子物体形成一个唯一的“静态树”,没有相关的自由度。在这个顶层静态树下面,可以附加多个运动树,见下面的

不允许运动学闭环;如果需要闭环关节,应使用等式约束来建模。因此,一个 MuJoCo 模型的主干是由嵌套的物体定义形成的一个或多个运动树;一个孤立的浮动体算作一棵树。下面列出的其他几个元素是在一个物体内定义的,并属于该物体。这与后面列出的独立元素形成对比,后者不能与单个物体关联。

物体#

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

关节#

关节在物体内部定义。它们在物体与其父物体之间创建运动自由度(DOF)。在没有关节的情况下,物体被焊接到其父物体上。这与游戏引擎相反,游戏引擎使用超完备的笛卡尔坐标,其中关节是移除自由度而不是增加自由度。有四种类型的关节:球形、滑动、铰链和创建浮动体的“自由关节”。单个物体可以有多个关节。通过这种方式,复合关节被自动创建,而无需定义虚拟物体。球形关节和自由关节的方向分量表示为单位四元数,MuJoCo 中的所有计算都遵循四元数的属性。关节的数量由 mjModel.njnt 给出。

关节参考#

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

弹簧参考#

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

自由度#

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

#

上文所述,运动的物体被组织成运动学树。一个运动学树或“树”是*一个可移动的物体及其所有后代*。因此,世界物体和其他静态物体在全局树结构中,但与任何*树*无关。因为全局树结构使用深度优先组织,属于单个树的所有物体、关节和自由度总是连续的。请注意,与物体(如果静态则不与任何树关联)不同,关节和自由度总是与一个树关联。 岛发现岛休眠 都在树的层面上操作。

树的数量由 mjModel.ntree 给出。例如,一个包含三个自由物体和标准人形机器人的模型有 ntree = 4。请注意,虽然这些树确实是全局树(其根是世界)的子树,但这不应与特定术语 subtree 混淆,该术语保留用于每个物体的部分树,因此 mjModel.body_subtreemass 给出每个物体下部分树的总质量,对所有物体均如此。

几何体#

几何体(Geom)是刚性附着在物体上的三维形状。多个几何体可以附着在同一个物体上。考虑到 MuJoCo 只支持凸几何体之间的碰撞,这一点特别有用,而创建非凸物体的唯一方法是将它们表示为凸几何体的并集。除了碰撞检测和随后的接触力计算,几何体还用于渲染,以及在省略物体质量和惯性时自动推断它们。MuJoCo 支持几种基本几何形状:平面、球体、胶囊体、椭球体、圆柱体、长方体。一个几何体也可以是网格或高度场;这是通过引用相应的资源来完成的。几何体有许多影响模拟和可视化的材质属性。几何体的数量由 mjModel.ngeom 给出。

站点#

站点(Site)本质上是轻量级的几何体。它们代表了物体坐标系内感兴趣的位置。站点不参与碰撞检测或惯性属性的自动计算,但它们可以用来指定其他对象的空间属性,如传感器、肌腱路径和滑块-曲柄的端点。站点的数量由 mjModel.nsite 给出。

相机#

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

光源#

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

独立元素#

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

肌腱#

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

驱动器#

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

传感器#

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

等式约束#

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

柔性体#

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

接触对#

MuJoCo 中的接触生成是一个复杂的过程。被检查接触的几何体对可以来自两个来源:统称为“动态”的自动邻近测试和其他过滤器,以及模型中提供的显式几何体对列表。后者是一种独立的模型元素。因为接触涉及两个几何体的组合,所以显式规范允许用户以动态机制无法做到的方式定义接触参数。这对于微调接触模型也很有用,特别是添加被激进过滤方案移除的接触对。接触机制现在已扩展到柔性体元素,它可以在两个以上的物体之间创建接触交互。然而,这种碰撞是自动化的,不能使用接触对进行微调。

接触排除#

这与接触对相反:它指定了应从候选接触对生成中排除的物体对(而不是几何体对)。这对于禁用那些几何形状导致不希望的永久接触的物体之间的接触很有用。请注意,MuJoCo 有其他机制来处理这种情况(特别是,如果几何体属于同一个物体或属于父子物体,则它们不能碰撞),但有时这些自动化机制不足,需要显式排除。

自定义数值#

在 MuJoCo 模拟中输入自定义数值有三种方法。首先,可以在 XML 中定义全局数值字段。它们有一个名称和一个实数值数组。其次,某些模型元素的定义可以用特定于元素的自定义数组来扩展。这是通过在 XML 元素 size 中设置属性 nuser_XXX 来完成的。第三,有一个数组 mjData.userdata,它不被任何 MuJoCo 计算使用。用户可以在那里存储自定义计算的结果;请记住,所有随时间变化的东西都应该存储在 mjData 中,而不是 mjModel 中。

自定义文本#

自定义文本字段可以保存在模型中。它们可用于自定义计算——既可以指定关键字命令,也可以提供其他文本信息。但不要用它们来写注释;将注释保存在编译后的模型中没有任何好处。XML 有自己的注释机制(被 MuJoCo 的解析器和编译器忽略),这更合适。

自定义元组#

自定义元组是 MuJoCo 模型元素的列表,可能包括其他元组。它们不被模拟器使用,但可用于指定用户代码所需的元素组。例如,人们可以使用元组来定义用于自定义接触处理的物体对。

关键帧#

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

澄清#

读者很可能对其他物理模拟器和相关惯例,以及与 MuJoCo 不一致的通用编程实践有经验。这有可能引起混淆。本节的目标是预先澄清最可能令人困惑的方面;它介于常见问题解答和特定主题教程之间。我们将需要引用文档后面涵盖的材料,但下面的文本尽可能地自成体系和入门。

发散#

模拟的发散发生于状态的某些元素迅速趋向无穷大。在 MuJoCo 中,这通常表现为 mjWARN_BADQACC 警告。发散是所有物理模拟中普遍存在的现象,不一定表明模型不好或模拟器有 bug,而是暗示对于给定的积分器选择,时间步长太大了。在物理模拟中,速度(大时间步长)和稳定性(小时间步长)之间总是存在一种张力。一个为速度而精心调整的模型,其时间步长是不会发散的最大可能值,这通常意味着它在极端条件下*可以*被弄到发散。从这个意义上说,*罕见的*发散情况实际上可能表明模型调整得很好。在所有情况下,应该可以通过减小时间步长和/或切换到更稳定的积分器来防止发散。如果这失败了,那么罪魁祸首就不同了。例如,在物体被初始化为穿透状态的模型中,巨大的排斥力可能会将它们推开并导致发散。

单位未指定#

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

尽管如此,还是鼓励用户使用 MKS,因为有两个地方 MuJoCo 使用了类似 MKS 的默认值

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

  • geom density(用于推断物体质量和惯性)的默认值是 1000,这对应于 MKS 单位制下水的密度。

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

角度: 虽然在 MJCF 中可以用度来指定角度(实际上度是默认值),但所有在 mjModelmjData 中的角度量都是用弧度表示的。所以,例如,如果我们使用 MKS,陀螺仪报告的角速度将是 rad/s,而铰链关节的刚度将是 Nm/rad。

意外的碰撞#

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

然而,如果父物体是静态物体,则此排除规则不适用,即世界物体,或相对于世界物体没有任何自由度的物体。这种行为在碰撞检测部分有记载,可以防止物体穿过地板或墙壁。然而,这种行为经常导致以下情况

用户注释掉了浮动基座模型的根关节,可能是为了防止它下落;现在基座物体被算作静态物体,出现了以前没有的新碰撞,用户感到困惑。有两种简单的方法可以避免这个问题

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

  2. 使用碰撞过滤来明确禁用不想要的碰撞,可以通过设置相关的 contypeconaffinity 属性,或者使用接触排除指令。

非面向对象#

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

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

软性和滑动#

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

这个软模型一个经常令人困惑的方面是,渐进的接触滑动无法避免。同样,摩擦关节在重力作用下会逐渐屈服。这并不是因为求解器无法防止滑动,即达到摩擦锥或摩擦损失极限,而是因为它从一开始就没有试图防止滑动。回想一下,对给定约束施加更大的力必须导致更大的加速度。如果要完全抑制滑动,就必须违反这个关键属性。所以,如果你在模拟中看到渐进的滑动,直观的解释可能是摩擦力不足,但在 MuJoCo 中这种情况很少。相反,需要调整 solrefsolimp 参数向量来减少这种效应。增加约束阻抗(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_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)是 MuJoCo 中大致对应于物理世界中刚体的元素。那么为什么它们是分开的呢?这里解释了其语义和计算上的原因。

首先是相似之处。物体、几何体和站点都有附着于其上的空间坐标系(尽管物体还有一个以其质心为中心并与惯性主轴对齐的第二个坐标系)。这些坐标系的位置和方向在每个时间步都通过正向运动学从 mjData.qpos 计算得出。正向运动学的结果在 mjData 中可用,对于物体是 xpos、xquat 和 xmat,对于几何体是 geom_xpos 和 geom_xmat,对于站点是 site_xpos 和 site_xmat。

现在是不同之处。物体用于构建运动树,是其他元素(包括几何体和站点)的容器。物体有一个空间坐标系、惯性属性,但没有与外观或碰撞几何相关的属性。这是因为这些属性不影响物理(当然,除了接触,但接触是分开处理的)。如果你在机器人学教科书中看过运动树的图表,物体通常被画成无定形的形状——以说明它们的实际形状与物理无关。

几何体(Geom,geometric primitive 的缩写)用于指定外观和碰撞几何。每个几何体都属于一个物体,并刚性地附着在该物体上。多个几何体可以附着在同一个物体上。考虑到 MuJoCo 的碰撞检测器假设所有几何体都是凸的(如果网格不是凸的,它会在内部用它们的凸包替换),这一点尤其有用。因此,如果你想模拟一个非凸形状,你必须将其分解为多个凸几何体的并集,并将它们全部附着在同一个物体上。

几何体也可以在 XML 中指定密度或质量值,模型编译器用这些值来计算父物体的质量和惯性。质量要么是指定的,要么是根据几何体的体积和密度计算的。惯性是根据质量、形状和均匀密度假设计算的。如果设置了 shellinertia 标志,则假设质量均匀分布在**表面**上,density 被解释为单位面积质量,并相应地计算对父物体的惯性贡献。在实际被模拟的 mjModel 中,几何体没有惯性属性。

站点是轻量级的几何体。它们具有相同的外观属性,但不能参与碰撞,也不能用于推断物体质量。另一方面,站点可以做几何体不能做的事情:它们可以指定触摸传感器的体积、IMU 传感器的附着点、空间肌腱的路径、滑块-曲柄驱动器的端点。这些都是空间量,但它们不对应于应该具有质量或与其他实体碰撞的实体——这就是创建站点元素的原因。站点也可以用来指定用户感兴趣的点(或坐标系)。

下面的例子说明了多个站点和几何体可以附着在同一个物体上:本例中有两个站点和两个几何体附着在一个物体上。

<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 可视化器渲染为

注意那个红色的盒子。这是物体惯性属性的等效惯量盒渲染,由 MuJoCo 内部生成。盒子覆盖了几何体,但没有覆盖站点。这是因为只有几何体被用来(自动)推断物体的惯性属性。如果我们恰好知道后者,我们当然可以直接指定它们。但通常让模型编译器根据附着在物体上的几何体,使用均匀密度的假设来推断这些物体属性更为方便(几何体密度可以在 XML 中指定;默认是水的密度)。

关节坐标#

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

关节坐标

  • 最适合于复杂的运动结构,如机器人;

  • 关节在默认情况下焊接在一起的物体之间**增加**了自由度;

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

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

笛卡尔坐标

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

  • 关节在默认情况下自由浮动的物体之间**移除**了自由度;

  • 关节约束是数值施加的,可以被违反;

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

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

浮动对象#

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

对于浮动体,情况则不同,即那些通过自由关节连接到世界的物体。这些物体的位置和方向以及线速度和角速度在 mjData.qposmjData.qvel 中是显式表示的,因此可以直接操纵。

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

自由关节总是在物体坐标系中定义,但在计算上,将此坐标系与物体的惯性对齐是有利的。请在 freejoint/align 属性的文档中阅读有关此选项的更多信息。