3、Gazebo仿真环境搭建:创建仿真世界与物理引擎配置

Gazebo仿真环境,说白了就是给我们的激光雷达和机器人搭一个「虚拟试验场」。你想想看,真机调试一次成本多高?撞坏了还得修。所以仿真这一步,我个人觉得是整个联调流程里最值得花时间打磨的环节。

我在项目里遇到过好几次,仿真环境搭得太糙,结果算法在仿真里跑得飞起,一上真车就翻车。嗯,后来我学乖了——仿真世界必须尽可能贴近真实场景。今天我们就来一步步搭好这个虚拟世界。

核心要点:一个合格的仿真世界 = 合理的物理参数 + 有意义的障碍物布局 + 稳定的地面与墙壁结构。

3.1 世界文件的结构与创建

Gazebo的世界文件是 .world 格式,本质上就是一个SDF(Simulation Description Format)文件。我习惯把它放在 my_robot_gazebo/worlds/ 目录下。

先看一个最简世界文件长什么样:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="lidar_test_world">
    
    <!-- 全局物理属性 -->
    <physics type="ode">
      <real_time_update_rate>1000</real_time_update_rate>
      <max_step_size>0.001</max_step_size>
    </physics>
    
    <!-- 光照 -->
    <include>
      <uri>model://sun</uri>
    </include>
    
    <!-- 地面 -->
    <include>
      <uri>model://ground_plane</uri>
    </include>
    
  </world>
</sdf>

这个文件定义了三个基本要素:物理引擎、光照和地面。你直接保存成 empty.world,用 gazebo empty.world 就能打开一个带地板的空白世界。

我的习惯:每次新建世界文件,我都会先跑一遍这个最小模板,确认Gazebo能正常加载,再逐步添加内容。这样可以快速定位是模型问题还是世界配置问题。

3.2 添加障碍物与墙壁

空荡荡的世界没法测试激光雷达。我们需要加一些障碍物。Gazebo支持两种方式:用SDF内联定义,或者引用外部模型。

3.2.1 内联定义障碍物

我个人更推荐内联定义——所有东西写在一个文件里,方便版本管理。下面是一个带墙壁和柱子的世界:

<?xml version="1.0" ?>
<sdf version="1.6">
  <world name="lidar_test_world">
    
    <physics type="ode">
      <real_time_update_rate>1000</real_time_update_rate>
      <max_step_size>0.001</max_step_size>
    </physics>
    
    <include>
      <uri>model://sun</uri>
    </include>
    <include>
      <uri>model://ground_plane</uri>
    </include>
    
    <!-- 北侧墙壁 -->
    <model name="wall_north">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry>
            <box>
              <size>10.0 0.2 2.0</size>
            </box>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <box>
              <size>10.0 0.2 2.0</size>
            </box>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1.0</ambient>
          </material>
        </visual>
      </link>
      <pose>0.0 5.0 1.0 0 0 0</pose>
    </model>
    
    <!-- 圆柱障碍物 -->
    <model name="cylinder_obstacle">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry>
            <cylinder>
              <radius>0.3</radius>
              <length>1.0</length>
            </cylinder>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <cylinder>
              <radius>0.3</radius>
              <length>1.0</length>
            </cylinder>
          </geometry>
          <material>
            <ambient>0.2 0.5 0.8 1.0</ambient>
          </material>
        </visual>
      </link>
      <pose>2.0 0.0 0.5 0 0 0</pose>
    </model>
    
  </world>
</sdf>

这里有几个关键点:

  • <static>true</static>:墙壁和障碍物设为静态,不会被撞飞。我曾经忘了加这个,机器人一碰墙壁,墙壁直接飞出去了……
  • pose:位置和姿态,格式是 x y z roll pitch yaw。
  • collision 和 visual:必须成对出现。collision 管物理碰撞,visual 管显示。两者几何体可以不同,但我建议保持一致,否则会出现「看着没撞上,实际撞上了」的诡异情况。

3.2.2 障碍物布局建议

为了测试激光雷达的SLAM和导航效果,我一般会摆这几类障碍物:

障碍物类型 尺寸示例 测试目的
长直墙壁 10m × 0.2m × 2m 测试激光雷达的直线检测精度
圆柱体 半径0.3m,高1m 测试圆形物体的点云拟合效果
L型墙角 两段墙壁垂直拼接 测试拐角处的反射与遮挡处理
窄通道 宽度0.8m,两侧墙壁 测试导航的通过性判断

避坑提醒:障碍物不要摆得太密集。激光雷达在狭窄空间里会产生大量反射和多次回波,仿真里可能没问题,但真实场景下数据质量会急剧下降。我一般保持障碍物间距至少1米以上。

3.3 配置物理引擎参数

物理引擎决定了仿真世界的「手感」。Gazebo默认使用ODE(Open Dynamics Engine),也支持Bullet、Simbody等。我个人一直用ODE,稳定且文档齐全。

3.3.1 关键参数详解

下面这张表是我反复调试后总结的核心参数:

参数名 推荐值 作用
real_time_update_rate 1000 每秒物理更新次数,越高越精确,但CPU负载也高
max_step_size 0.001 单步最大时间步长(秒),与更新率配合
gravity 0 0 -9.8 重力加速度,默认就是地球标准值
max_contacts 20 每个物体最大接触点数量,太多会拖慢仿真
cfm 0.01 约束力混合参数,控制物体「软硬」程度
erp 0.2 误差修正参数,值越大物体回弹越明显

完整的物理引擎配置块长这样:

<physics type="ode">
  <real_time_update_rate>1000</real_time_update_rate>
  <max_step_size>0.001</max_step_size>
  <gravity>0 0 -9.8</gravity>
  <max_contacts>20</max_contacts>
  <ode>
    <solver>
      <type>quick</type>
      <iters>50</iters>
      <sor>1.3</sor>
    </solver>
    <constraints>
      <cfm>0.01</cfm>
      <erp>0.2</erp>
      <contact_max_correcting_vel>100.0</contact_max_correcting_vel>
      <contact_surface_layer>0.001</contact_surface_layer>
    </constraints>
  </ode>
</physics>

经验之谈:如果你发现机器人在地面上「漂移」或者抖动,多半是 cfm 和 erp 没调好。cfm 太大物体会变「软」,erp 太大物体会「弹」。我一般从 cfm=0.01, erp=0.2 开始,再根据实际表现微调。

3.4 本章知识体系总览

下面这张图帮你理清整个仿真环境搭建的逻辑脉络:

Gazebo仿真环境搭建知识体系 Gazebo仿真环境搭建核心流程 世界文件 (.world) 物理引擎配置 环境模型 障碍物与墙壁 ODE / Bullet 引擎选择 更新率 / 步长 / 重力 cfm / erp / 接触参数 光照 (sun) 配置 地面 (ground_plane) 材质与视觉属性 内联SDF定义 外部模型引用 静态/动态属性设置 三者缺一不可,共同决定仿真环境的真实度与可用性

3.5 验证你的世界文件

写完了世界文件,怎么确认它没问题?我一般做三步检查:

  1. 语法检查:用 gazebo --verbose your_world.world 启动,看终端有没有报错。Gazebo的报错信息还算友好,会告诉你哪一行出了问题。
  2. 物理表现检查:把一个物体放到高处,看它会不会正常下落。如果物体穿模或者飘在空中,说明碰撞体配置有问题。
  3. 激光雷达模拟测试:在机器人模型上挂一个激光雷达传感器(后面章节会讲),看扫描到的点云是否与障碍物位置吻合。

曾经踩过的坑:有一次我忘了给墙壁加 <static>true</static>,结果Gazebo启动后墙壁直接「躺」在了地上。更坑的是,激光雷达扫描到的点云位置还是对的,但导航算法死活规划不出路径——因为墙壁一直在「滑动」。排查了整整一下午才发现是静态属性没设。

好了,到这里你已经掌握了Gazebo仿真环境搭建的核心技能。世界文件的结构、障碍物的添加方式、物理引擎的参数调优——这三块搞定了,后面往里面放机器人、挂激光雷达就水到渠成了。