MuJoCo SDF 插件实战:隐式几何体的定义、五个内置实现与自定义开发指南
2026/9/14 12:30:48 网站建设 项目流程

MuJoCo SDF 插件实战:隐式几何体的定义、五个内置实现与自定义开发指南

【免费下载链接】mujocoMulti-Joint dynamics with Contact. A general purpose physics simulator.项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco

本篇基于 MuJoCo 仓库中 plugin/sdf/README.md 展开,讲解如何用 Signed Distance Function(SDF,有符号距离函数)插件为geommesh(位于 asset 段)赋予隐式几何形状,覆盖 bolt、nut、bowl、gear、torus 五个一方实现的参数与用法,并结合 plugin/sdf 目录下的源码拆解插件注册、距离/梯度计算与碰撞迭代的可视化机制,最后给出开发自定义 SDF 插件的完整接口模板。读完本文,你可以直接复制仓库中的 XML 示例运行 SDF 碰撞模型,也能按照 README 的接口规范 写出并注册属于自己的隐式几何插件。

一、SDF 插件是什么,能应用在哪里

根据 plugin/sdf/README.md,这些一方插件使用 SDF 实现隐式几何(implicit geometries)。与 mesh 显式给出三角形网格不同,SDF 几何体由一个解析的距离场函数d(p)描述:对空间中任意查询点p,返回其到物体表面的有符号距离(外部为正、内部为负)。MuJoCo 的碰撞求解器通过在这个距离场上做梯度下降迭代来搜索接触点,因此 SDF 特别适合参数化、可解析表达的复杂曲面(如带螺纹的螺栓、齿轮),且无需离散的网格数据。

README 明确指出这些插件可以应用到geomsmeshes(在asset段中),示例模型统一放在 model/plugin/sdf/ 目录下,包含 nutbolt.xml、bowl.xml、gear.xml、torus.xml 等可直接用 simulate/studio 打开的场景。

XML 中的标准使用模式:extension 声明 → asset 挂 mesh → geom 引用

以 nutbolt.xml 为例,一个 SDF 几何体的接入分三步:

  1. <extension>段声明插件实例并配置参数plugin属性是插件的全名,<config key value>逐一对应插件的属性名:
<extension> <plugin plugin="mujoco.sdf.nut"> <instance name="nut"> <config key="radius" value="0.26"/> </instance> </plugin> <plugin plugin="mujoco.sdf.bolt"> <instance name="bolt"> <config key="radius" value="0.255"/> </instance> </plugin> </extension>
  1. <asset>段把插件实例挂到具名 mesh 上,一个插件实例可以对应多个 mesh:
<asset> <mesh name="nut"> <plugin instance="nut"/> </mesh> <mesh name="bolt"> <plugin instance="bolt"/> </mesh> </asset>
  1. <worldbody>中创建type="sdf"的 geom 并绑定 mesh 与插件实例
<geom type="sdf" name="bolt" mesh="bolt" rgba="0.7 0.7 0.7 1"> <plugin instance="bolt"/> </geom>

<option>段的sdf_iterationssdf_initpoints控制碰撞搜索过程:每次迭代执行的梯度下降次数和初始采样点数。四个示例中的取值分别为:nutbolt.xml 用sdf_iterations="10" sdf_initpoints="20",bowl.xml 用sdf_iterations="5" sdf_initpoints="20",gear.xml 用sdf_iterations="5" sdf_initpoints="20",torus.xml 用sdf_iterations="10" sdf_initpoints="40"

从源码结构看,Gradient回调中记录的每一步查询点会被送入SdfVisualizer(见下文第五节),开启可视化标志mjVIS_SDFITER后可以在渲染器中看到这些梯度下降轨迹,这正是调参sdf_iterations时的直观依据。

二、五个内置 SDF 插件的参数详解

README 为每个插件给出了参数表、实现文件与示例模型。下表汇总,并注明各参数的默认值与实现位置。

插件实现文件示例模型参数(默认值)
Bolt(六角头螺栓)bolt.ccnutbolt.xmlradius[m](0.26)
Bowl(切割空球碗)bowl.ccbowl.xmlheight[m](0.4)、radius[m](1)、thickness[m](0.02)
Gear(齿轮)gear.ccgear.xmlalpha(0)、diameter[m](2.8)、teeth(25)
Nut(六角螺母)nut.ccnutbolt.xmlradius[m](0.26)
Torus(环面)torus.cctorus.xml主半径(0.35)、次半径(0.15)

2.1 Bolt 与 Nut:螺纹 + 六角头组合

Bolt 实现了一个带六角头的螺栓,Nut 实现了与螺栓头部完全相同的六角螺母。nutbolt.xml 让一个带 free joint 的螺母与一个固定螺栓相互作用,并特意把 nut 半径设为 0.26、bolt 半径设为 0.255(略小),以便螺纹配合。

从 bolt.cc 的distance函数可以看出 SDF 组合的典型手法:先算出到螺纹柱面的距离thread(用Fract生成的三角波绕 Oy 轴旋转),再用Subtraction裁剪上下端、减去一个cone做斜切;六角头部分通过对查询点做旋转矩阵投影后求平面距离,再用两次Intersection分别用圆锥削顶和用水平平面封顶,最后用Union把柱体与头部合并。这些 Union/Intersection/Subtraction 原语都定义在 sdf.h:

inline mjtNum Union(mjtNum a, mjtNum b) { return mju_min(a, b); } inline mjtNum Intersection(mjtNum a, mjtNum b) { return mju_max(a, b); } inline mjtNum Subtraction(mjtNum a, mjtNum b) { return mju_max(a, -b); }

2.2 Bowl:切割出的空心球

Bowl 的参数含义见 README 的参数表;示例 bowl.xml 显式配置了height=0.4radius=1.0thickness=0.02,让碗口位于 z=0.4 处,并放置三个自由关节小球演示落入碗中的碰撞。值得注意的是该示例的 geom 使用了condim="1"(纯摩擦接触),以匹配曲面碰撞的求解稳定性。

2.3 Gear:两个相互咬合的齿轮

gear.xml 是两个mujoco.sdf.gear实例的演示:gear1用默认参数且alpha=0gear2设置alpha=15以错开齿形实现啮合;两者分别位于 z 轴铰链关节上,间距 2.85(略大于直径 2.8),并由一个gear="1500"的 motor actuator 驱动。这体现了 SDF 参数化几何的价值:同一个插件实例,仅改变alpha就能得到齿形相位不同的两个齿轮。

从 gear.h 的GearAttribute结构看,Gear 实际暴露了 5 个属性:alpha(默认 0)、diameter(默认 2.8)、teeth(默认 25)、thickness(默认 0.2)和innerdiameter(默认 -1),其中后两个未在 README 的参数表中列出,但同样可以通过<config key="thickness" value="..."/>方式配置。

2.4 Torus:最简单的参数化环面

torus.xml 用同一个 torus 插件实例创建了三个带 freejoint 的环面 geom 与两根 cylinder 轴,构成穿过环面的穿插碰撞演示。参数名以 torus.h 为准:radius1(主半径,默认 0.35)与radius2(次半径,默认 0.15)。这里提示一处 README 的笔误:其参数表将次半径也写成了radius1,实际应为radius2

三、插件的注册机制与生命周期(源码级剖析)

以 bolt.cc 中的RegisterPlugin为主线,可以看到一个 SDF 插件需要向 MuJoCo 提供的全部信息:

mjpPlugin plugin; mjp_defaultPlugin(&plugin); plugin.name = "mujoco.sdf.bolt"; plugin.capabilityflags |= mjPLUGIN_SDF; plugin.nattribute = BoltAttribute::nattribute; plugin.attributes = BoltAttribute::names; plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
  • 插件名即 XML<extension>plugin属性的取值,如mujoco.sdf.bolt
  • capabilityflags:置mjPLUGIN_SDF位(定义为1<<3,见 mjplugin.h)声明本插件提供有符号距离场能力;nstate = 0表示插件没有运行时状态。

随后填充一组回调:

回调作用
init调用工厂Bolt::Create构造对象,把指针存入d->plugin_data[instance];失败返回 -1
destroydelete释放实例并清零plugin_data
reset重置内部可视化计数器
compute每个求解周期调用,通知SdfVisualizer开始记录新一轮迭代
visualize把记录的梯度下降点画进mjvScene
sdf_distance返回查询点的有符号距离
sdf_gradient返回距离场梯度,并把查询点送入SdfVisualizer::AddPoint
sdf_staticdistance仅依赖属性数组的静态距离函数,不绑定实例
sdf_aabb返回几何体的轴对齐包围盒(bolt 为[0, 0.6]² × [0, 1]
sdf_attribute把 XML 中的字符串属性解析为数值并填充attribute[]

mjPLUGIN_SDF回调槽位(sdf_gradientsdf_staticdistance等)都定义在 include/mujoco/mjplugin.h 的mjpPlugin结构中。

梯度的实现是有限差分:bolt.cc 的Gradienteps = 1e-8在 x、y、z 三方向各取一个偏移点,用一阶前向差分(distX - dist0)/eps近似梯度。由于解析 SDF 距离场通常非光滑(Union/Intersection 会产生折线),有限差分是最通用的求梯度方式,代价是每次梯度查询需要 4 次距离求值。

参数解析由SdfDefault<T>模板统一完成(sdf.h):构造时把T::namesT::defaults装入 map;GetDefault(name, value)在 XML 未提供值时回落到默认值,提供了值则strtod解析,非法值会调用mju_error终止。每个插件的属性结构(如BoltAttribute)必须保证names[]顺序与类中attribute[]数组顺序一致,这是 README 接口约定中强调的关键点。工厂函数Create还会先用 CheckAttr 校验数值属性字符串合法性,不合法时以mju_warning("Invalid parameter specification in Bolt plugin")告警并返回空。

注册入口在 register.cc:

mjPLUGIN_LIB_INIT(sdf) { Bolt::RegisterPlugin(); Bowl::RegisterPlugin(); Gear::RegisterPlugin(); Nut::RegisterPlugin(); Torus::RegisterPlugin(); }

mjPLUGIN_LIB_INIT(n)宏(同样定义于 mjplugin.h)在动态库加载时触发,一次注册本库内的全部插件。构建层面,plugin/sdf/CMakeLists.txt 将上述全部源文件编译为共享库sdf_pluginadd_library(sdf_plugin SHARED))并链接mujoco主库,因此该插件目录本身就是"新增插件时该放什么文件、如何接入构建"的完整样板。

碰撞迭代轨迹可视化:SdfVisualizer

sdf.h 与 sdf.cc 中的SdfVisualizer记录了每次梯度下降的完整轨迹:Next()开新轨迹、AddPoint()逐点追加、Reset()清空。Visualize仅在可视化选项opt->flags[mjVIS_SDFITER]打开时生效,并把局部坐标轨迹变换到世界系(mju_mulMatMatT组合geom_xmat与静态geom_mat再平移geom_pos)。轨迹起点用红色球、终点用绿色球标记(rgba中通道 0/2 随j==0/j>0切换),中间连线颜色沿轨迹从红渐变到绿,方便观察迭代是否收敛到正确的接触面。缓冲区按mjMAXCONPAIR预分配,超出scn->maxgeom时给出 "Increase maxgeom" 警告,这是大规模调试迭代时的实际限制。

四、如何开发自己的 SDF 插件

README 给出的开发流程是:在 plugin/sdf 目录(本 README 所在目录)中创建MySDF.hMySDF.cc,按以下接口实现,然后在 register.cc 中追加一个注册调用。接口模板如下(摘自 plugin/sdf/README.md):

struct MySDFAttribute { static constexpr int nattribute = /* insert the number of attributes */; static constexpr char const* names[nattribute] = /* an array of attributes with the same order as the attribute array in your SDF class */; static constexpr mjtNum defaults[nattribute] = /* an array of default values for your attributes */; }; class MySDF { public: // creates a new MySDF instance or returns null on failure. static std::optional<MySDF> Create(const mjModel* m, mjData* d, int instance); MySDF(MySDF&&) = default; ~MySDF() = default; // functions that return the SDF and its gradient at a query point mjtNum Distance(const mjtNum point[3]) const; void Gradient(mjtNum grad[3], const mjtNum point[3]) const; // a call to this needs to be added to register.cc static void RegisterPlugin(); // an array of attributes with the same order as in the struct above mjtNum attribute[MySDFAttribute::nattribute]; private: MySDF(const mjModel* m, mjData* d, int instance); };

对照五个内置实现,可以把模板落成以下可执行清单:

  1. 属性结构:仿照 BoltAttribute,nattributenamesdefaults三者数量一致,且names的顺序必须与类中attribute[]的填充顺序一致(SdfDefault::GetDefaults按位置取用)。
  2. 工厂函数Create:先用CheckAttr校验 XML 数值属性,通过则走私有构造函数(内部用SdfDefault<MySDFAttribute>+mj_getPluginConfig(m, instance, name)读取每个属性),否则mju_warning并返回std::nullopt
  3. Distance/Gradient:实现解析距离场;梯度可直接复用 bolt 的有限差分写法(eps=1e-8的三方向前向差分),也可对解析光滑部分给出解析梯度。
  4. RegisterPlugin:设置plugin.name(建议mujoco.sdf.*命名空间)、plugin.capabilityflags |= mjPLUGIN_SDFnattribute/attributes,以及initnew并写入d->plugin_data[instance])、destroyresetvisualizecomputesdf_distancesdf_gradientsdf_staticdistancesdf_aabbsdf_attribute回调,最后mjp_registerPlugin(&plugin)
  5. 接入注册入口:在 register.cc 的mjPLUGIN_LIB_INIT(sdf)块中增加MySDF::RegisterPlugin();,并按 CMakeLists.txt 把新源文件加入MUJOCO_SDF_SRCS列表,重新构建sdf_plugin共享库即可。
  6. 写一个示例 XML 验证:套用 nutbolt.xml 的三段式结构(<extension>声明实例 →<asset>挂 mesh →type="sdf"的 geom),配置sdf_iterations/sdf_initpoints后打开观察碰撞行为与mjVIS_SDFITER迭代轨迹。

五、小结与注意事项

  • 五个插件全部位于mujoco::plugin::sdf命名空间,共享 sdf.h 提供的布尔运算原语(Union/Intersection/Subtraction/Fract)、属性解析模板SdfDefault与迭代可视化器SdfVisualizer,是"一个插件目录 = 一个可加载 SDF 插件库"的完整范式。
  • README 的参数表是权威入口,但个别细节以源码为准:Torus 的次半径属性名是radius2(README 中误写为radius1);Gear 除 README 列出的三个参数外还有thickness(默认 0.2)与innerdiameter(默认 -1)两个属性。
  • SDF 几何的质量取决于距离场在表面的光滑程度;碰撞搜索是数值梯度下降,若发现接触点漂移或穿透,可先调大<option>sdf_iterationssdf_initpoints(如 torus.xml 使用 40 个初始点),并借助mjVIS_SDFITER可视化确认迭代轨迹是否落在预期表面。
  • 所有路径均相对仓库根目录:插件源码在 plugin/sdf/,可运行示例在 model/plugin/sdf/,插件 API 头文件为 include/mujoco/mjplugin.h。

【免费下载链接】mujocoMulti-Joint dynamics with Contact. A general purpose physics simulator.项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询