第三讲:Gazebo仿真世界搭建

各位同学,欢迎来到第三讲。上一讲我们把Nav2的框架搭起来了,但说实话,没有场景的机器人就像瞎子摸象。今天我们就来亲手搭建一个仿真世界——从零开始,用SDF格式创建静态世界,添加障碍物和墙壁,设置光照和地面纹理,最后再用Building Editor快速生成室内场景。

我个人习惯把仿真世界比作「机器人的幼儿园」。在这里,它要学习走路、避障、认路。世界搭得好不好,直接决定了后续导航算法的表现。我见过太多同学在仿真里跑得飞起,一上实车就翻车——原因往往就是仿真世界太「理想」了。

核心要点:仿真世界不是越复杂越好,而是越「真实」越好。真实意味着有光照变化、有纹理干扰、有合理的障碍物布局。

3.1 SDF格式:为什么选它?

SDF(Simulation Description Format)是Gazebo的原生格式。你可能会问:URDF不行吗?URDF当然可以,但它更偏向机器人本身的描述。SDF对场景、光照、物理属性支持更好。

说白了,URDF描述「机器人长什么样」,SDF描述「世界长什么样」。两者各司其职。

来看一个最简单的SDF世界文件:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="my_world">
    <include>
      <uri>model://sun</uri>
    </include>
    
    <include>
      <uri>model://ground_plane</uri>
    </include>
  </world>
</sdf>

嗯,这里要注意:model://是Gazebo内置模型的路径。如果你有自己的模型,可以用file://指定本地路径。

我的经验:刚开始做仿真时,我总喜欢把所有东西写在一个文件里。后来发现维护起来太痛苦了。建议把地面、墙壁、障碍物拆成独立的SDF文件,用<include>引用。这样改一个障碍物位置,不用动整个文件。

3.2 添加障碍物与墙壁

静态世界最核心的就是障碍物。没有障碍物,导航算法就没法验证。我建议你从最简单的立方体开始,逐步增加复杂度。

下面是一个带墙壁和障碍物的世界示例:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="warehouse">
    <include>
      <uri>model://sun</uri>
    </include>
    
    <!-- 地面 -->
    <include>
      <uri>model://ground_plane</uri>
    </include>
    
    <!-- 墙壁1:左侧墙 -->
    <model name="wall_left">
      <pose>-5 0 1 0 0 0</pose>
      <link name="body">
        <collision name="collision">
          <geometry>
            <box>
              <size>0.2 10 2</size>
            </box>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <box>
              <size>0.2 10 2</size>
            </box>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
          </material>
        </visual>
      </link>
    </model>
    
    <!-- 障碍物:一个箱子 -->
    <model name="box_obstacle">
      <pose>2 1 0.5 0 0 0</pose>
      <link name="body">
        <collision name="collision">
          <geometry>
            <box>
              <size>1 1 1</size>
            </box>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <box>
              <size>1 1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.2 0.5 0.8 1</ambient>
            <diffuse>0.2 0.5 0.8 1</diffuse>
          </material>
        </visual>
      </link>
    </model>
  </world>
</sdf>

你想想看,这里最关键的是什么?是<pose>标签。它定义了模型的位置和朝向。格式是:x y z roll pitch yaw。我刚开始经常搞混顺序,后来养成了习惯:先写位置,再写角度。

避坑指南:我曾经在项目里把障碍物的z坐标设成了0,结果它半截埋在地里。导航算法死活绕不过去,我还以为是路径规划有问题。排查了半天才发现是障碍物位置不对。记住:障碍物的z坐标应该是它高度的一半。

3.3 设置光照与地面纹理

光照和纹理看起来是「美化」工作,但实际上对导航影响很大。为什么?因为激光雷达和摄像头对光照敏感。仿真里的光照太均匀,实车上的传感器就会「不适应」。

来看一个带自定义光照和纹理的例子:

<!-- 自定义光照 -->
<light name="sun_light" type="directional">
  <pose>0 0 10 0 0 0</pose>
  <diffuse>1 0.95 0.9 1</diffuse>
  <specular>0.1 0.1 0.1 1</specular>
  <direction>-0.5 0.1 -1</direction>
  <attenuation>
    <range>100</range>
    <constant>0.9</constant>
    <linear>0.01</linear>
    <quadratic>0.001</quadratic>
  </attenuation>
  <cast_shadows>true</cast_shadows>
</light>

<!-- 地面纹理 -->
<model name="ground">
  <static>true</static>
  <link name="link">
    <collision name="collision">
      <geometry>
        <plane>
          <normal>0 0 1</normal>
          <size>20 20</size>
        </plane>
      </geometry>
    </collision>
    <visual name="visual">
      <geometry>
        <plane>
          <normal>0 0 1</normal>
          <size>20 20</size>
        </plane>
      </geometry>
      <material>
        <script>
          <uri>model://ground_plane/materials/scripts/ground_plane.material</uri>
          <name>GroundPlane/Grass</name>
        </script>
      </material>
    </visual>
  </link>
</model>

这里有几个关键参数:

参数 作用 我的建议
diffuse 漫反射颜色 别用纯白,稍微偏暖色更真实
direction 光照方向 模拟下午的阳光,角度别太垂直
cast_shadows 是否投射阴影 一定要开,否则传感器数据太假
地面纹理 视觉参考 用纹理能让视觉SLAM有更多特征点

小技巧:如果你想让地面纹理更丰富,可以自己制作Ogre材质脚本。我在一个仓储项目里,用棋盘格纹理做地面,这样机器人经过时能明显看到里程计的漂移——这对调试非常有帮助。

3.4 使用Building Editor创建室内场景

手动写SDF文件搭建复杂室内场景太累了。Gazebo自带的Building Editor就是干这个的。它是个图形化工具,你画墙、放门、加窗户,它自动生成SDF。

启动方法很简单:

gz building_editor

界面打开后,你会看到三个视图:俯视图、3D视图、属性面板。操作逻辑跟画图软件差不多:

  1. 画墙:点击「Wall」工具,在俯视图上点两个点,墙就出来了。按住Shift可以画直线。
  2. 加门:选中一段墙,右键选择「Add Door」。门的位置可以拖动调整。
  3. 加窗户:类似门,选中墙后添加。窗户高度可以调。
  4. 放家具:左侧模型库里有桌子、椅子、柜子,拖进去就行。
  5. 导出:画完后,File → Export as SDF。会生成一个.world文件和对应的模型文件夹。

导出后的文件结构是这样的:

my_building/
├── my_building.world
└── models/
    └── my_building/
        ├── model.sdf
        └── model.config

你只需要把my_building.world里的内容复制到你的世界文件中,或者直接用gz sim my_building.world启动。

注意:Building Editor导出的模型默认是静态的。如果你想让门可以动(比如作为可开关的门),需要手动修改SDF,添加<joint>标签。这个我们后面讲动态世界时会详细说。

知识体系总览

下面这张图总结了本章的核心逻辑:

Gazebo仿真世界搭建知识体系 仿真世界搭建 SDF格式基础 障碍物与墙壁 光照与纹理 Building Editor 原生格式 与URDF区别 include引用 pose定位 碰撞与视觉 材质设置 方向光/点光源 阴影投射 地面纹理 图形化画墙 添加门/窗 导出SDF 目标:搭建一个真实、可复用的仿真测试环境

这张图把本章的知识点串起来了。你会发现,四个分支最终都指向同一个目标:搭建一个真实、可复用的仿真测试环境。SDF是基础,障碍物和墙壁是内容,光照和纹理是真实感,Building Editor是效率工具。

我个人建议的学习顺序是:先手动写一个简单的SDF世界,理解每个标签的含义。然后用Building Editor快速生成复杂场景。最后再回头手动调整细节——比如光照角度、纹理贴图。这样既有深度,又有广度。

好了,这一讲的内容就到这里。记住:仿真世界是你的试验田,花时间把它搭好,后续的导航调试会事半功倍。

可可精品资料库