4、Gazebo仿真环境配置:world文件创建、添加光源与地面、导入静态物体、设置物理引擎参数

好,咱们接着往下走。上一章我们把机械臂模型和控制器都搞定了,模型能在Gazebo里动起来了。但说实话,光秃秃一个机械臂悬在半空中,看着挺别扭的,也没法做正经的抓取任务。这一章,我们就来搭一个像样的仿真环境。

我个人习惯,每次开始一个新项目,第一件事就是先把“舞台”搭好。你想想看,一个真实的机器人工作台,得有地面、有光照、有桌子、有被抓的物体,对吧?Gazebo里也一样,这些全得靠 .world 文件来定义。

4.1 什么是world文件?

说白了,.world 文件就是Gazebo的“场景剧本”。它告诉Gazebo:

  • 天空是什么颜色?
  • 地面在哪里?
  • 有没有桌子、箱子?
  • 重力加速度设多少?
  • 物理引擎用ODE还是Bullet?

嗯,你可以把它理解成一个超级详细的“装修图纸”。Gazebo启动时,会先读这个文件,然后按照图纸把场景搭起来。

核心要点: 一个完整的仿真环境 = 模型 + 控制器 + world文件。缺一不可。

4.2 创建你的第一个world文件

我们先从最简单的开始。创建一个空文件,命名为 pick_place.world,放在你的功能包 worlds 目录下。

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="pick_place_world">
    
    <!-- 这里放光源 -->
    <!-- 这里放地面 -->
    <!-- 这里放静态物体 -->
    <!-- 这里设置物理参数 -->

  </world>
</sdf>

这个骨架文件,就是我们的起点。接下来,我们往里面填东西。

4.3 添加光源与地面

没有光,你啥也看不见。Gazebo默认会带一个环境光,但效果很差。我建议你至少加一个主光源,让场景有立体感。

4.3.1 添加光源

我个人习惯用 <light> 标签来定义光源。下面是一个典型的太阳光配置:

<light type="directional" name="sun">
  <cast_shadows>true</cast_shadows>
  <pose>0 0 10 0 0 0</pose>
  <diffuse>0.8 0.8 0.8 1</diffuse>
  <specular>0.2 0.2 0.2 1</specular>
  <attenuation>
    <range>1000</range>
    <constant>0.9</constant>
    <linear>0.01</linear>
    <quadratic>0.001</quadratic>
  </attenuation>
  <direction>-0.5 0.1 -0.9</direction>
</light>

这里有几个参数我解释一下:

  • type:directional 是平行光,模拟太阳。还有 point(点光源)和 spot(聚光灯)。
  • cast_shadows:一定要设为 true,否则物体没有影子,看着很假。
  • diffuse:漫反射颜色。RGB值,范围0-1。0.8 0.8 0.8 是暖白色。
  • direction:光线方向。这个向量指向光源照射的方向。我一般调成斜45度,效果最自然。

我的小技巧: 如果你发现场景太暗或太亮,优先调 diffuse 的亮度值,别去动 attenuation(衰减)。衰减参数主要是给点光源用的,平行光基本用不上。

4.3.2 添加地面

地面其实就是一个巨大的平面模型。Gazebo提供了一个内置的 ground_plane 模型,直接用就行。

<include>
  <uri>model://ground_plane</uri>
</include>

就这么简单?嗯,就这么简单。但如果你想要一个带纹理的地面,比如木地板或者水泥地,那就得自己定义 <visual> 了。不过对于抓取任务,默认的灰色地面完全够用。

注意: 地面模型一定要放在 <world> 标签的最前面。因为Gazebo加载模型是有顺序的,地面作为“基底”,应该最先被加载。我曾经因为把地面放在最后,导致其他模型加载时悬空报错,排查了半天。

4.4 导入静态物体

光有地面和机械臂还不够。我们要抓东西,总得有个桌子、几个箱子吧?这些物体在Gazebo里被称为“静态物体”(static objects)。

静态物体和动态物体的区别:

  • 静态物体:不受重力影响,不会移动。比如桌子、墙壁、传送带。
  • 动态物体:受物理引擎影响,可以被推动、抓取。比如被抓的零件。

4.4.1 添加一个桌子

我习惯用SDF格式直接定义桌子的几何体。下面是一个简单的桌子模型:

<model name="table">
  <static>true</static>
  <link name="table_link">
    <collision name="collision">
      <geometry>
        <box>
          <size>1.0 0.8 0.05</size>
        </box>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <box>
          <size>1.0 0.8 0.05</size>
        </box>
      </geometry>
      <material>
        <ambient>0.5 0.3 0.1 1</ambient>
        <diffuse>0.8 0.5 0.2 1</diffuse>
      </material>
    </visual>
    <pose>0 0 0.75 0 0 0</pose>
  </link>
</model>

这里的关键点:

  • <static>true</static>:告诉Gazebo,这个物体是固定的,别用物理引擎去算它。
  • pose:桌面的高度是0.75米。为什么?因为机械臂的基座高度通常是0,桌面设在0.75米,机械臂刚好能轻松够到。
  • collision 和 visual:碰撞体和视觉体。我一般让它们保持一致,避免出现“看得见摸不着”或者“摸得着看不见”的诡异情况。

4.4.2 添加被抓物体

抓取任务,总得有个东西抓吧?我放一个红色的小方块在桌面上:

<model name="target_box">
  <static>false</static>
  <link name="box_link">
    <inertial>
      <mass>0.1</mass>
      <inertia>
        <ixx>0.0001</ixx>
        <ixy>0</ixy>
        <ixz>0</ixz>
        <iyy>0.0001</iyy>
        <iyz>0</iyz>
        <izz>0.0001</izz>
      </inertia>
    </inertial>
    <collision name="collision">
      <geometry>
        <box>
          <size>0.05 0.05 0.05</size>
        </box>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <box>
          <size>0.05 0.05 0.05</size>
        </box>
      </geometry>
      <material>
        <ambient>0.8 0.1 0.1 1</ambient>
        <diffuse>0.9 0.2 0.2 1</diffuse>
      </material>
    </visual>
    <pose>0.3 0 0.78 0 0 0</pose>
  </link>
</model>

注意看,这个物体的 <static> 是 false,而且有 <inertial>(惯性参数)。这意味着它会被物理引擎影响,可以被机械臂抓起来、推走。

避坑指南: 惯性参数 <inertia> 一定要算对。对于一个小方块,质量0.1kg,边长0.05m,惯性矩大概就是 0.1 * (0.05^2) / 6 ≈ 0.0000417。我上面写的0.0001是个近似值。如果你设得太小,物体会像羽毛一样飘;设得太大,又像铅块一样推不动。我曾经因为惯性矩设错,导致夹爪一碰物体,物体就飞出去了,场面一度非常尴尬。

4.5 设置物理引擎参数

物理引擎是Gazebo的“心脏”。它负责计算碰撞、摩擦力、重力等等。Gazebo默认使用ODE引擎,但你也换成Bullet或DART。

我个人推荐用ODE,因为它最稳定,社区支持也最好。下面是一组我常用的物理参数:

<physics type="ode">
  <real_time_update_rate>1000</real_time_update_rate>
  <max_step_size>0.001</max_step_size>
  <real_time_factor>1</real_time_factor>
  <ode>
    <solver>
      <type>quick</type>
      <iters>50</iters>
      <sor>1.3</sor>
      <use_dynamic_moi_rescaling>false</use_dynamic_moi_rescaling>
    </solver>
    <constraints>
      <cfm>0.0</cfm>
      <erp>0.2</erp>
      <contact_max_correcting_vel>100</contact_max_correcting_vel>
      <contact_surface_layer>0.001</contact_surface_layer>
    </constraints>
  </ode>
</physics>

这些参数什么意思?我挑几个重点讲:

参数 作用 我的建议值
real_time_update_rate 物理引擎每秒更新次数 1000(越高越精确,但CPU压力大)
max_step_size 每一步的时间步长(秒) 0.001(与1000Hz对应)
real_time_factor 仿真速度倍率(1=实时) 1(除非你要加速仿真)
iters 求解器迭代次数 50(越大越稳定,但越慢)
sor 超松弛因子 1.3(1.0-1.5之间)
cfm 约束力混合参数 0.0(设大了物体会“软”)
erp 误差修正参数 0.2(设大了物体会“抖”)

我的经验: 如果你发现机械臂抓取时物体抖动得很厉害,先别急着调PID。试试把 erp 降到0.1,或者把 iters 提高到100。很多时候,物理参数比控制参数更能解决问题。

4.6 完整的world文件示例

好了,我们把上面所有的东西拼在一起,就是一个完整的 pick_place.world 文件了。结构如下:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="pick_place_world">
    
    <!-- 光源 -->
    <light type="directional" name="sun">
      ...
    </light>

    <!-- 地面 -->
    <include>
      <uri>model://ground_plane</uri>
    </include>

    <!-- 桌子(静态) -->
    <model name="table">
      <static>true</static>
      ...
    </model>

    <!-- 被抓物体(动态) -->
    <model name="target_box">
      <static>false</static>
      ...
    </model>

    <!-- 物理引擎参数 -->
    <physics type="ode">
      ...
    </physics>

  </world>
</sdf>

把这个文件保存好,然后在启动文件里引用它:

roslaunch gazebo_ros empty_world.launch world_name:=/path/to/your/pick_place.world

启动后,你应该能看到一个带光照、有地面、有桌子、桌子上有个红色小方块的仿真环境。机械臂就位后,就可以开始抓取了。

4.7 本章知识体系

为了让你更直观地理解这一章的内容,我画了一张图:

Gazebo仿真环境配置核心结构 world文件 光源配置 地面模型 静态物体 物理引擎参数 type/diffuse cast_shadows direction ground_plane 碰撞/视觉体 static=true pose定位 材质/颜色 solver/iter cfm/erp max_step_size 每个分支都是world文件的核心组成部分,缺一不可

嗯,这一章的内容就到这里。环境搭好了,下一章我们就可以真正开始写抓取逻辑了。不过在那之前,我建议你先把world文件跑起来,亲手调一调光源角度、物体位置,感受一下参数变化对仿真效果的影响。纸上得来终觉浅,绝知此事要躬行嘛。


可可精品资料库