4、Gazebo基础操作:世界文件(.world)创建、添加静态物体与灯光、传感器插件(IMU/Laser)配置
各位同学,欢迎来到第四章。这一章我们开始真正动手搭建仿真环境。说实话,Gazebo的世界文件(.world)就像是我们给机器人搭建的一个“舞台”。舞台搭得好不好,直接决定了后面导航算法跑起来顺不顺。我刚开始学的时候,总觉得.world文件很神秘,后来发现它就是个XML配置文件,没那么玄乎。
4.1 世界文件(.world)的结构与创建
一个.world文件,说白了就是告诉Gazebo三件事:物理环境长什么样、有哪些物体、光线怎么打。它的根标签是<sdf>,里面包含一个<world>标签。我习惯把.world文件放在功能包的worlds/目录下,这样好管理。
来看一个最简单的例子:
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="my_first_world">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
</world>
</sdf>
这段代码创建了一个带地面和阳光的世界。嗯,这里要注意:model://是Gazebo内置模型的路径写法,就像你用file://访问本地文件一样。我刚开始总写成file://,结果Gazebo一直报错找不到模型。
<ground_plane>里加<material>标签。我个人习惯用灰色大理石纹理,视觉上不刺眼,调试时也容易看清机器人。
4.2 添加静态物体
静态物体就是那些不会动的障碍物——墙壁、柱子、桌子什么的。在Gazebo里,静态物体用<model>标签定义,并且要设置<static>true</static>。为什么?因为导航算法需要知道哪些地方是“不可通行”的。
我曾经在一个项目中,忘了给墙壁加<static>true</static>,结果机器人一撞上去,墙壁就飞了……嗯,那场面挺尴尬的。
下面是一个添加墙壁的例子:
<model name="wall_01">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<box>
<size>2.0 0.2 1.0</size>
</box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box>
<size>2.0 0.2 1.0</size>
</box>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1.0</ambient>
</material>
</visual>
</link>
<pose>1.0 0.0 0.5 0 0 0</pose>
</model>
这里我创建了一堵长2米、高1米、厚0.2米的灰色墙壁,放在(1.0, 0.0, 0.5)的位置。注意<pose>标签里的六个数字:前三个是xyz坐标,后三个是翻滚角、俯仰角、偏航角(单位是弧度)。
<collision>和<visual>最好保持一致。如果碰撞模型比视觉模型小,机器人可能会“穿模”;如果碰撞模型比视觉模型大,机器人还没碰到墙就停下了。这两种情况我在调试时都遇到过。
4.3 灯光配置
灯光在仿真中容易被忽略,但它直接影响传感器数据的质量。Gazebo支持三种光源:方向光(directional)、点光源(point)和聚光灯(spot)。
我个人最常用的是方向光,模拟太阳光。点光源适合室内场景,比如走廊里的灯泡。来看一个点光源的配置:
<light name="ceiling_light" type="point">
<pose>0 0 2.5 0 0 0</pose>
<diffuse>1.0 1.0 1.0 1.0</diffuse>
<specular>0.1 0.1 0.1 1.0</specular>
<attenuation>
<range>5.0</range>
<constant>0.5</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
</light>
这里<diffuse>是漫反射颜色,<specular>是高光颜色。<attenuation>控制光的衰减——距离越远,光越弱。我一般把<range>设成5到10米,太远了计算量大,太近了场景太暗。
<diffuse>的alpha通道设成了0.5,结果灯光半透明,场景忽明忽暗。后来才发现,Gazebo的灯光颜色不支持alpha通道,直接写RGB三个值就行。
4.4 传感器插件配置
传感器插件是连接Gazebo和ROS2的桥梁。没有插件,Gazebo里的传感器数据就传不到ROS2里,Nav2也就没法用。我们重点讲两个最常用的:IMU和激光雷达(Laser)。
4.4.1 IMU插件
IMU提供角速度和线加速度数据。Nav2的里程计融合、EKF定位都离不开它。配置如下:
<plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
<ros>
<namespace>/robot</namespace>
<remapping>~/imu:=imu/data</remapping>
</ros>
<update_rate>100.0</update_rate>
<body_name>base_link</body_name>
<frame_name>imu_link</frame_name>
<gaussian_noise>0.01</gaussian_noise>
</plugin>
注意<update_rate>我设成了100Hz。为什么?因为IMU数据更新快,Nav2的EKF滤波器才能准确估计姿态。设太低的话,机器人转弯时会感觉“卡顿”。
<gaussian_noise>这个参数很关键。真实IMU都有噪声,如果你设成0,仿真太理想,算法在真机上会崩。我一般设0.01到0.05之间,模拟消费级IMU的水平。
4.4.2 激光雷达插件
激光雷达是导航的核心传感器。Nav2的建图、定位、避障都靠它。配置如下:
<plugin name="laser_plugin" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/robot</namespace>
<remapping>~/scan:=scan</remapping>
</ros>
<update_rate>10.0</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1.0</resolution>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.1</min>
<max>30.0</max>
<resolution>0.01</resolution>
</range>
</ray>
</plugin>
这里<samples>360</samples>表示360度扫描,每度一个点。如果你想要更高分辨率,可以设720,但计算量会翻倍。<min_angle>和<max_angle>是扫描范围,我设了-π到π,也就是全向扫描。
<range>最大距离不要设太大。30米对于室内导航足够了。设成100米的话,Gazebo要计算远处物体的反射,帧率会掉得很厉害。我在一个仓库项目里吃过这个亏,后来改成20米,流畅多了。
4.5 完整世界文件示例
把上面这些整合起来,就是一个完整的.world文件。我把它放在worlds/my_nav_world.world里:
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="nav_world">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
<!-- 添加墙壁 -->
<model name="wall_01">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>2.0 0.2 1.0</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>2.0 0.2 1.0</size></box></geometry>
<material><ambient>0.8 0.8 0.8 1.0</ambient></material>
</visual>
</link>
<pose>1.0 0.0 0.5 0 0 0</pose>
</model>
<!-- 灯光 -->
<light name="ceiling_light" type="point">
<pose>0 0 2.5 0 0 0</pose>
<diffuse>1.0 1.0 1.0 1.0</diffuse>
<specular>0.1 0.1 0.1 1.0</specular>
<attenuation>
<range>5.0</range>
<constant>0.5</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
</light>
<!-- 机器人模型(含传感器插件) -->
<include>
<uri>model://my_robot</uri>
<pose>0 0 0.1 0 0 0</pose>
</include>
</world>
</sdf>
启动这个世界的命令很简单:
gazebo worlds/my_nav_world.world
或者通过ROS2 launch文件:
gazebo_launch.py:
...
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gazebo_ros, 'launch', 'gazebo.launch.py')
),
launch_arguments={'world': world_path}.items()
)
4.6 本章知识体系
下面这张图帮你理清本章的核心逻辑:
这张图展示了.world文件的三大核心模块:静态物体定义环境布局,灯光影响传感器数据质量,传感器插件负责数据通信。三者缺一不可。
gazebo --verbose your_world.world启动,看终端有没有报错。我习惯在修改完灯光或传感器参数后,打开rviz2订阅/scan和/imu/data话题,确认数据是否正常。这一步能帮你省下大量排查时间。
好了,这一章的内容就到这里。世界文件是Gazebo仿真的地基,地基打牢了,后面建图、定位、导航才能跑得稳。下一章我们会把机器人模型(URDF)和这个世界文件结合起来,让机器人真正动起来。