第2章 link标签详解:视觉、碰撞、惯性、几何

link 是 URDF 里最基础的零件。说白了,它就是机器人的一个刚体部件——底盘、轮子、激光雷达支架,全都靠 link 来描述。我刚开始写 URDF 时,以为 link 就是画个形状而已,后来才发现里面藏着三个关键属性:visual(视觉)、collision(碰撞)、inertial(惯性)。再加上 geometry(几何形状)贯穿其中,这四个东西构成了 link 的完整定义。

这一章,我会把每个属性拆开讲清楚。你想想看,一个轮子如果只有好看的模型,没有碰撞体积,仿真时就会直接陷进地面;如果忘了惯性参数,机器人跑起来就像在太空中飘。嗯,咱们一个一个来。

link 标签核心结构 link visual 视觉 collision 碰撞 inertial 惯性 geometry + origin + material geometry + origin mass + inertia + origin 每个 link 至少包含 visual + collision + inertial,geometry 是它们的共同基础

1. visual —— 看得见的外壳

visual 定义的是机器人“长什么样”。在 Rviz 里你看到的那个彩色模型,就是 visual 渲染出来的。它包含三个子元素:geometry(形状)、origin(位置偏移)、material(颜色/材质)。

我个人习惯把 visual 做得精细一些,毕竟调试时看着舒服。但注意,visual 不影响物理仿真——它就是个皮囊。

核心原则: visual 只负责显示,不参与碰撞计算。所以你可以用复杂的 mesh 文件,只要电脑跑得动。
<link name="base_link">
    <visual>
        <geometry>
            <cylinder length="0.2" radius="0.15"/>
        </geometry>
        <origin xyz="0 0 0.1" rpy="0 0 0"/>
        <material name="blue">
            <color rgba="0.2 0.5 0.9 1.0"/>
        </material>
    </visual>
</link>

2. collision —— 真实的“肉体”

collision 才是仿真引擎用来检测碰撞的体积。我记得有一次做差速底盘,visual 用的复杂轮毂 mesh,但 collision 偷懒直接复制了 visual。结果 Gazebo 里轮子一碰到小石头就疯狂弹跳……后来我把 collision 简化成圆柱体,问题立刻解决。

为什么 collision 要简化? 因为碰撞检测计算量很大。你想想看,如果每个螺丝都用三角网格去算碰撞,仿真帧率直接掉到个位数。所以工业上通常用 box、cylinder、sphere 来近似。

我曾经见过一个新手把 collision 的 geometry 设得比 visual 还大,结果机器人还没碰到障碍物就被“空气墙”挡住了。collision 的尺寸一定要和实际物理轮廓匹配,可以略小一点(留出安全余量),但绝不能比 visual 大太多。
<collision>
    <geometry>
        <cylinder length="0.2" radius="0.14"/>  <!-- 比 visual 半径小0.01,留余量 -->
    </geometry>
    <origin xyz="0 0 0.1" rpy="0 0 0"/>
</collision>

3. inertial —— 让机器人有“重量”

没有 inertial 的 link,在 Gazebo 里会被当作质量无限大——推不动,也掉不下来。说白了就是“幽灵零件”。inertial 包含三个东西:mass(质量)、inertia(惯性张量)、origin(质心偏移)。

惯性张量是很多人的噩梦。其实对于简单形状,ROS 官方有计算脚本。我一般用 urdf_tutorial 里的 inertia_macro.xacro 自动生成。但如果你手写,记住:单位是 kg·m²,矩阵要对称。

形状ixxiyyizz
实心圆柱 (半径r, 高h, 质量m)m(3r²+h²)/12m(3r²+h²)/12mr²/2
实心球 (半径r, 质量m)2mr²/52mr²/52mr²/5
长方体 (长a, 宽b, 高c, 质量m)m(b²+c²)/12m(a²+c²)/12m(a²+b²)/12
如果你不确定惯性参数,可以用 urdf_to_graphiz 检查,或者直接在 Gazebo 里让机器人自由落体——如果它旋转得不对劲,八成是惯性张量填错了。
<inertial>
    <mass value="2.0"/>
    <origin xyz="0 0 0.05"/>  <!-- 质心略高于几何中心 -->
    <inertia ixx="0.012" ixy="0" ixz="0"
             iyy="0.012" iyz="0"
             izz="0.008"/>
</inertial>

4. geometry —— 形状是万物的基础

visual、collision 甚至某些 sensor 插件都依赖 geometry。URDF 支持四种基本形状:box、cylinder、sphere、mesh。mesh 可以导入 STL/DAE 文件,实现任意复杂外形。

我个人建议:能用基本形状就别用 mesh。因为 mesh 文件一旦丢失,整个模型加载失败。我在项目里吃过这个亏——把 mesh 路径写成了相对路径,换电脑编译就找不到文件。后来统一用 package:// 格式,再也没出过问题。

  • box:尺寸用 xyz 表示,适合底盘、箱体。
  • cylinder:半径 + 长度,轮子、连杆首选。
  • sphere:半径,万向轮、球铰。
  • mesh:filename 指定路径,scale 缩放。
<geometry>
    <!-- 基本形状示例 -->
    <box size="0.4 0.3 0.1"/>
    <!-- 或者 -->
    <mesh filename="package://my_robot/meshes/arm.stl" scale="1.0 1.0 1.0"/>
</geometry>

5. 组合起来:一个完整的 link

下面是一个真实项目中轮子的 link 定义。你可以看到 visual 用了精细的 mesh,collision 简化为圆柱,inertial 根据实际质量计算。

<link name="left_wheel">
    <visual>
        <geometry>
            <mesh filename="package://bot_description/meshes/wheel.dae"/>
        </geometry>
        <origin xyz="0 0 0" rpy="1.5708 0 0"/>
        <material name="dark_gray"/>
    </visual>
    <collision>
        <geometry>
            <cylinder radius="0.08" length="0.04"/>
        </geometry>
        <origin xyz="0 0 0" rpy="1.5708 0 0"/>
    </collision>
    <inertial>
        <mass value="0.5"/>
        <origin xyz="0 0 0"/>
        <inertia ixx="0.0003" ixy="0" ixz="0"
                 iyy="0.0003" iyz="0"
                 izz="0.0006"/>
    </inertial>
</link>
避坑总结: ① visual 和 collision 的 origin 尽量保持一致,否则模型显示和碰撞体积错位;② inertial 的 origin 是质心位置,不是几何中心;③ 所有数值单位采用 SI(米、千克、秒)。

嗯,link 标签的内容差不多就这些。你可能会觉得惯性张量有点烦,但相信我,多写几次就熟练了。下一章我们会讲 joint——把 link 连起来,机器人才能真正动起来。