ROS2系列教程:Gazebo仿真基础与世界搭建
本文摘要
内容是关于ROS2第18篇,Gazebo仿真基础与世界搭建。摘要需简洁概括核心内容。注意字数限制。</think>本文是ROS2系列第18篇,讲解Gazebo仿真基础与世界搭建…
本文是 ROS2 系列教程的第 18 篇
本文是 ROS2 系列教程的第 18 篇:Gazebo 仿真基础与世界搭建。前两篇我们用 URDF/Xacro 描述了机器人的“骨骼”,但骨骼自己不会动、不会掉下来、也不会感知。Gazebo 就是那个给机器人注入物理规律的“虚拟世界”:重力、摩擦、碰撞、传感器噪声全部真实可感。本篇从零搭建 Gazebo 环境:理解 world 文件结构、掌握
gazebo_ros桥接包、把小车 spawn 进世界、用 ROS2 命令控制它动起来。学完你就有了一套可复用的“仿真闭环”,后续 SLAM 建图、Nav2 导航、MoveIt2 全部在此之上进行。
一、Gazebo 是什么
1.1 定位
Gazebo 是一个高保真 3D 机器人仿真器,提供:
- 物理引擎(ODE/Bullet 等):重力、摩擦、碰撞、关节约束。
- 传感器仿真:激光雷达(gpu_laser)、相机、IMU、接触传感器。
- 世界构建:静态物体、地形、光源、GUI 交互。
- ROS2 桥接:
gazebo_ros包把仿真世界和 ROS2 话题无缝对接。
在 ROS2 Humble 中,经典 Gazebo(Gazebo Classic 11)仍是教学主流,ros_gz(新 Gazebo 与 ROS2 的桥接)正在兴起。本篇以 Gazebo Classic + gazebo_ros 为例,概念对新版同样适用。
1.2 安装
# Ubuntu 22.04 + ROS2 Humblesudo apt install ros-humble-gazebo-ros-pkgs
# 验证ros2 pkg list | grep gazebo# 输出:gazebo_dev、gazebo_msgs、gazebo_plugins、gazebo_ros、gazebo_ros_pkgs启动测试:
gazebo --verbose如果打开一个带地板和天空的默认世界,说明安装成功。
1.3 与 RViz 的分工
| 工具 | 作用 | 谁在“算” |
|---|---|---|
| RViz | 可视化显示 | 只是“看”,不产生物理 |
| Gazebo | 物理仿真 | 真实计算重力/碰撞/传感器 |
| 两者关系 | Gazebo 是“真实世界”,RViz 是“仪表盘” | 常同时运行 |
理解:Gazebo 里掉下来的东西真的会摔,RViz 里不会。仿真流程中通常 Gazebo 跑物理,ROS2 节点发控制指令,RViz 显示 TF/地图/点云。
二、world 文件:虚拟世界的“地图”
2.1 world 文件是什么
world 文件是 Gazebo 的 SDF(Simulation Description Format)文件,描述世界的物理属性、静态物体、光照等。它是 Gazebo 世界的“上帝视角配置”。
最小 world 文件:
<?xml version="1.0"?><sdf version="1.6"> <world name="default"> <!-- 物理参数 --> <physics type="ode"> <max_step_size>0.001</max_step_size> <real_time_factor>1</real_time_factor> </physics> <!-- 光照 --> <include> <uri>model://sun</uri> </include> <!-- 地面 --> <include> <uri>model://ground_plane</uri> </include> </world></sdf>2.2 常用元素
| 元素 | 作用 | 关键属性 |
|---|---|---|
<physics> |
物理引擎参数 | max_step_size(仿真步长,越小越稳)、real_time_factor(实时因子) |
<include><uri>model://sun</uri> |
太阳光照 | 默认就有 |
<include><uri>model://ground_plane</uri> |
地面(带摩擦) | 默认就有 |
<model> |
静态物体(墙/障碍) | 自己定义 |
<scene> |
环境背景色/雾 | 可选 |
<gravity> |
重力向量 | 默认 0 0 -9.8 |
2.3 添加障碍物(墙)
<model name="wall"> <static>true</static> <link name="wall_link"> <collision name="collision"> <geometry> <box> <size>2.0 0.2 0.5</size> </box> </geometry> </collision> <visual name="visual"> <geometry> <box> <size>2.0 0.2 0.5</size> </box> </geometry> <material> <ambient>0.8 0.2 0.2 1</ambient> </material> </visual> </link> <pose>0 0 0.25 0 0 0</pose></model><static>true</static> 表示不可移动。把墙摆成迷宫,就有了 SLAM 建图的世界。
三、把机器人放进世界:spawn_entity
3.1 gazebo_ros 的核心节点
gazebo_ros 提供两个关键可执行文件:
gazebo:启动仿真服务器与 GUI。spawn_entity.py:把 URDF/SDF 模型生成到世界中。
spawn 的本质:把 robot_description 参数里的 URDF 转成 SDF,插入 Gazebo 世界,并在 world 坐标系的指定位姿处放置。
3.2 命令行手动 spawn
# 1. 启动世界(先手动放好机器人)ros2 run gazebo_ros gazebo --verbose -s libgazebo_ros_factory.so
# 2. 另一个终端:spawn 机器人到 (0,0,0.1)ros2 run gazebo_ros spawn_entity.py \ -topic robot_description \ -entity demo_car \ -x 0 -y 0 -z 0.1-topic robot_description:从话题读 URDF(需要有人发布)。-entity:模型在 Gazebo 中的名字。-x -y -z:spawn 位置;-R -P -Y:姿态(欧拉角)。
3.3 一步到位:launch 文件
工程实践中从 launch 启动,模板如下:
from launch import LaunchDescriptionfrom launch.actions import IncludeLaunchDescription, SetEnvironmentVariablefrom launch.launch_description_sources import PythonLaunchDescriptionSourcefrom launch_ros.actions import Nodefrom launch.substitutions import Commandimport osfrom ament_index_python.packages import get_package_share_directory
def generate_launch_description(): # 1. 找到世界文件 world_path = os.path.join( get_package_share_directory('my_robot_bringup'), 'worlds', 'my_world.world')
# 2. 启动 Gazebo(加载世界) gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')), launch_arguments={'world': world_path}.items(), )
# 3. 发布 robot_description(xacro 展开) robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': Command( ['xacro ', os.path.join( get_package_share_directory('my_robot_description'), 'urdf', 'demo_car.xacro')])}], output='screen', )
# 4. spawn 机器人 spawn_entity = Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-topic', 'robot_description', '-entity', 'demo_car', '-x', '0', '-y', '0', '-z', '0.1'], output='screen', )
return LaunchDescription([ SetEnvironmentVariable('GZ_SIM_RESOURCE_PATH', os.path.join( get_package_share_directory('my_robot_description'), 'meshes')), gazebo, robot_state_publisher, spawn_entity, ])运行:
ros2 launch my_robot_bringup gazebo.launch.py如果一切正常:Gazebo 窗口中出现小车,RViz(另开)能看到 TF 树。此时小车会立刻被重力按在地面上——因为惯性/碰撞体都配好了。
3.4 常见失败:机器人掉穿地面
小车掉穿地面通常原因:
- 模型碰撞体缺失:检查每个 link 都有
<collision>。 - spawn 高度太低:
-z应大于底盘一半高度(0.1 一般够)。 - 碰撞体与 visual 不一致:visual 比 collision 大,看起来“悬空”。
排查命令:
# 看模型是否在 /tf 中(base_link 相对 world)ros2 run tf2_ros tf2_echo world base_link四、让小车动起来:差速驱动插件
4.1 差速驱动原理回顾
差速小车通过左右轮速度差控制运动:
- 两轮同速同向 → 直行。
- 两轮同速反向 → 原地旋转。
- 速度差 → 转弯。
控制输入是 cmd_vel(geometry_msgs/msg/Twist:线速度+角速度),gazebo 差速插件把它转成左右轮角速度。
4.2 在 URDF 中加差速插件(gazebo 标签)
在 URDF/Xacro 的 <robot> 内、link/joint 之外添加:
<!-- gazebo 标签专门给仿真用,URDF 解析器会忽略 --><gazebo> <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so"> <ros> <namespace>/</namespace> <remapping>cmd_vel:=cmd_vel</remapping> </ros> <update_rate>30</update_rate> <left_joint>base_to_wheel_1</left_joint> <right_joint>base_to_wheel_-1</right_joint> <wheel_separation>0.38</wheel_separation> <wheel_diameter>0.16</wheel_diameter> <max_wheel_torque>20</max_wheel_torque> <max_wheel_acceleration>1.0</max_wheel_acceleration> <command_topic>cmd_vel</command_topic> <publish_odom>true</publish_odom> <publish_odom_tf>true</publish_odom_tf> <odometry_frame>odom</odometry_frame> <robot_base_frame>base_link</robot_base_frame> </plugin></gazebo>关键参数:
| 参数 | 说明 |
|---|---|
left_joint / right_joint |
左右轮关节名(必须与 URDF 一致) |
wheel_separation |
轮距(两轮中心距离,米) |
wheel_diameter |
轮径(米) |
command_topic |
订阅的速度指令话题,默认 cmd_vel |
publish_odom |
发布里程计(odom 话题) |
publish_odom_tf |
发布 odom→base_link 的 TF |
odometry_frame |
里程计坐标系名(odom) |
robot_base_frame |
机器人底盘坐标系名(base_link) |
4.3 发送速度指令
# 直行 0.5 m/sros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \ "{linear: {x: 0.5}, angular: {z: 0.0}}"
# 原地右转ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \ "{linear: {x: 0.0}, angular: {z: 0.5}}"
# 停止ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \ "{linear: {x: 0.0}, angular: {z: 0.0}}"或者用 teleop_twist_keyboard 手动控制:
sudo apt install ros-humble-teleop-twist-keyboardros2 run teleop_twist_keyboard teleop_twist_keyboard按 i/j/k/l 前后左右移动,小车会在 Gazebo 中真实运动,同时 odom 话题输出里程计、/tf 广播 odom→base_link。
4.4 里程计验证
ros2 topic echo /odom --once输出包含 pose.pose.position(位置)和 twist.twist.linear.x(速度)。这就是导航系统的输入:SLAM 建图和 Nav2 都用 odom 做预测。
五、Gazebo 与 RViz 联调
5.1 同时启动
推荐结构(bringup 包统一管理):
# 终端 1:Gazebo + 小车ros2 launch my_robot_bringup gazebo.launch.py
# 终端 2:RViz 显示rviz2RViz 添加:
- RobotModel:显示模型。
- TF:查看 world→odom→base_link 树。
- LaserScan(下篇配好雷达后):显示激光数据。
5.2 检查话题清单
ros2 topic list# 应看到:# /clock# /odom# /robot_description# /tf# /tf_static/clock 是仿真时间(use_sim_time=true 时节点使用它),仿真环境必查。
5.3 use_sim_time 必须开
所有“感知时间”的节点(SLAM、导航、传感器处理)在仿真中都要:
ros2 run your_node your_node --ros-args -p use_sim_time:=true或 launch 里统一设置:
Node(..., parameters=[{'use_sim_time': True}])不开 sim time 的典型症状:TF 时间戳错乱、传感器回调延迟、导航“觉得”时间不动。
六、常见问题与排障
| 现象 | 原因 | 解决 |
|---|---|---|
spawn_entity 报 No entity found |
robot_description 话题无数据 | 先确认 ros2 topic echo /robot_description --once 有内容 |
| 小车掉穿地面 | 碰撞体缺失或 spawn 太低 | 补 collision、抬高 -z |
| 发 cmd_vel 车不动 | 插件未加载或关节名不匹配 | ros2 topic list 查插件话题;核对 joint 名 |
| 模型在 Gazebo 可见但 RViz 无 TF | robot_state_publisher 未跑 | 单独启动 rsp 节点 |
| 里程计不动 | 车轮未收到力矩 | 检查 max_wheel_torque 是否过小 |
| Gazebo 卡顿/掉帧 | 物理步长太大或模型网格复杂 | max_step_size 调小到 0.001,collision 用简单形状 |
| 时间跳跃 | real_time_factor 与机器性能不匹配 |
关 GUI(headless)或用 -r 0.5 降速 |
/clock 不存在 |
没走 gazebo_ros 启动方式 | 用 ros2 run gazebo_ros gazebo 而非裸 gazebo |
6.1 headless 模式(无 GUI 仿真)
服务器/CI 场景常用:
# 无 GUI 启动(省资源,适合批量测试)ros2 run gazebo_ros gazebo --verbose -s libgazebo_ros_factory.so --headless6.2 仿真时间加速/减速
# 启动时指定实时因子(0.5 = 半速仿真)gazebo -r 0.5七、小结与下一篇预告
本篇核心:
- Gazebo 是物理仿真器:重力/摩擦/碰撞/传感器,与 RViz 分工明确(仿真 vs 显示)。
- world 文件:SDF 格式,
physics/ground_plane/sun/静态模型,可搭墙做迷宫。 - spawn 流程:
gazebo_ros启动世界 +spawn_entity.py插入模型 +robot_state_publisher发布描述。 - 差速插件:
libgazebo_ros_diff_drive.so订阅cmd_vel→ 驱动左右轮 → 发布 odom/TF。 - 闭环验证:launch 一键启动 →
teleop_twist_keyboard控制 → 话题/TF 检查。 use_sim_time与/clock是仿真与真机最大的区别,必须理解。
下一篇进入 Gazebo 插件:给小车装上“眼睛”——激光雷达(gpu_laser)、相机、IMU 插件,让传感器数据真正进入 ROS2 话题。有了传感器,机器人才能“看见”世界,这是 SLAM 建图(第 21 篇)的前提。物理世界已就绪,感知即将开启。
ROS2系列教程:Gazebo仿真基础与世界搭建
作者:Wang Shijie
本文链接:https://wsj-blog.pages.dev/posts/ros2-xi-lie-jiao-cheng-gazebo-fang-zhen-ji-chu-yu-shi-jie/
本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
留言评论
评论资源加载失败,请重试。