
Gazebo仿真这事儿圈里有个共识模型搭得越细后面改起来越痛苦但你要是不搭后面连痛苦的机会都没有。很多新手拿到一个URDF就急着launch结果机器人要么陷进地面要么关节抖成筛子要么传感器压根没数据最后只能对着终端日志发呆。这篇东西不聊虚的就讲清楚Gazebo模型从无到有怎么搭、拿到现成模型怎么改、踩了坑怎么排查全程用我实际跑过的案例说话。不管你是在Ubuntu 22.04上折腾ROS2 Humble还是在24.04上配Jazzy或者还在用老牌的Noetic思路都是通用的区别只在于具体包名和版本号。仿真平台的核心价值就一句话把真实世界里的“作死”成本转移到代码里。机械臂的撞机、移动机器人的翻车、传感器噪声的干扰在仿真里试错代价最多是重启一个进程。但仿真也有它自己的脾气——它不是游戏引擎物理引擎的每一条约束、每一个参数都是你在现实里要面对的真实物理规律。模型搭建与修改本质上就是一场和物理引擎的谈判。1. 模型搭建前必须先想明白的几件事1.1 仿真模型不是3D模型物理属性才是灵魂很多刚接触Gazebo的人有个错觉模型搭建就是把SolidWorks或者Blender里的模型导进来能看就行。这话对了一半。能看确实是最低要求但仿真里真正干活的是碰撞体、惯性参数、摩擦系数这些看不见的东西。你可以把可视化网格做得像电影特效一样精细但物理引擎根本不管你长什么样它只关心碰撞几何和惯性张量。举个例子一个真实的小车底盘外观mesh做得再复杂Gazebo计算接触力时用的还是底下的box或cylinder碰撞体。碰撞体越简单仿真越稳越快。这跟在游戏里做碰撞盒是一个道理——物理引擎最怕遇到几百个三角面片组成的复杂碰撞面数值求解容易发散轻则抖动重则直接把模型弹飞。所以我的建议是外观模型可以精细碰撞体一律用基本几何体去近似最多拆成几个box和cylinder的组合。惯性参数是另一个容易被忽略的坑。URDF里如果没写inertial标签Gazebo会崩溃或者给你一个离谱的默认值。你以为自己在仿真其实在跟一个没有质量的幽灵较劲。惯性张量的计算不复杂简单几何体可以直接用公式算复杂形状就交给建模软件或者SolidWorks导出。记住一个原则惯性参数必须和模型的质量、尺寸对得上不然机械臂的动力学仿真结果就是一堆废数据。1.2 URDF和SDF到底选哪个这是模型搭建绕不开的选择题。URDF是ROS家的标准从ROS1时代就在用支持xacro宏定义复用性极好。但URDF天生不是给Gazebo设计的它缺少很多仿真专属的配置比如摩擦系数、颜色光照、传感器噪声模型这些都得通过gazebo扩展标签塞进去。真正跑起来时Gazebo还要把URDF转成内部使用的SDF。SDFSimulation Description Format则是Gazebo的原生格式本身就支持完整的仿真属性从物理引擎参数到传感器噪声全都能描述编辑器里也可以直接预览。缺点是跟ROS生态的tf树衔接没有URDF那么顺手而且写起来确实啰嗦。那怎么选我的实践结论是如果你要跟ROS2的robot_state_publisher、moveit配合走URDFxacro路线然后让Gazebo在加载时自动转换。如果你只在Gazebo里做单机仿真不依赖ROS的tf那就直接用SDF。如果你要建的是复杂环境地形、建筑、动态障碍物SDF是唯一选择URDF压根不适合干这个。我在机械臂仿真项目里用的就是URDFxacro因为要接MoveIt。在无人机仿真项目里则直接用SDF因为机架结构重复度高SDF的include机制更好使。1.3 拿到一个现成模型之后通常要改哪里现实情况是很少人会从零手写整个机器人模型大多数人是在别人的模型基础上改。常见需求无非这几类换传感器、改机械结构、调动力学参数、加新的link和joint。改模型最怕的是“不知道自己改了什么”所以动手之前一定要把模型文件的结构理清楚。URDF/xacro文件里link是刚体joint是约束。改外观就去改mesh路径或者visual标签改碰撞就去改collision下的geometry改运动学就去改joint的origin、axis、limit。SDF则更直白所有属性都挂在link和joint下面一目了然。关键是自己要清楚每改一个数值物理引擎的求解结果会怎么变。这个意识决定了你是会改模型还是只会改文件。2. 环境准备与工具链版本匹配是第一步2.1 ROS2和Gazebo怎么搭配才顺版本匹配是仿真环境搭建里最劝退新手的一关。Gazebo从11之后直接把版本号跳到了Harmonic命名也从Ignition变成了Gazebo导致网上教程混乱不堪。实际开发中我推荐这么搭配Ubuntu版本ROS版本Gazebo版本说明20.04ROS1 NoeticGazebo 11老项目维护首选资料最多22.04ROS2 HumbleGazebo 11 (classic) 或 FortressHumble官方默认是Gazebo 1122.04ROS2 HumbleGazebo Fortress/Ionic用ros_gz桥接新特性更多24.04ROS2 JazzyGazebo Harmonic新版组合必须用ros_gz24.04ROS2 JazzyGazebo Ionic尝鲜可以生态还没跟上我自己主用是 Ubuntu 22.04 ROS2 Humble Gazebo 11这套最稳。但如果你拿到的是新版本教程写着gz sim那就说明用的是Harmonic之后的版本需要顺势调整。还有一个普遍踩坑点ROS2 Humble默认安装的gazebo_ros_pkgs只支持Gazebo classic如果你装了Harmonic就得装ros_gz而不是gazebo_ros_pkgs这俩不通用。2.2 gazebo_ros_pkgs和ros_gz包装不对全白费这就是热搜词里那个“获取gazebo ros pkgs包”说的事情。很多人launch失败十有八九是桥接包装错或者没装。ROS1时代gazebo_ros_pkgs直接支持Gazebo 11装好就能用。ROS2时代分了两条路gazebo_ros_pkgs配合Gazebo classic11及以前提供gazebo_ros::DiffDrivePlugin这些插件。ros_gz配合Gazebo Harmonic等新版本走的是ros_gz_bridge把ROS话题和Gazebo传输transport桥接起来。两者的安装方式不同。比如在Ubuntu 24.04 Jazzy环境下安装命令是sudo apt install ros-jazzy-ros-gz这个包会自动拉ros_gz_bridge、ros_gz_sim等依赖。而在Humble下如果你用经典版sudo apt install ros-humble-gazebo-ros-pkgs这里有个容易混淆的地方gazebo_ros_pkgs在Noetic下叫ros-noetic-gazebo-ros-pkgs在Humble下叫ros-humble-gazebo-ros-pkgs包名规律一样但支持的Gazebo版本完全不同。建议动手之前先确认清楚你系统上的Gazebo版本再倒推该装哪个桥接包。2.3 快速验证环境能不能跑起来环境装完之后别急着写模型先跑一个最小用例。我用Gazebo 11时最快验证方式是source /opt/ros/humble/setup.bash gazebo --verbose如果Gazebo能正常打开世界并且终端没有报缺库错误说明核心安装没问题。接着用ROS2侧的测试ros2 launch gazebo_ros gazebo.launch.py如果能看到空世界并且ros2 topic list里有/clock这个话题那说明ROS2和Gazebo的通信链路通了。对于Harmonic版本验证命令稍有不同需要用gz sim打开世界再通过ros2 run ros_gz_bridge parameter_bridge /clockrosgraph_msgs/msg/Clock[gz.msgs.Clock来测试桥接。老实说环境验证这一步值得认认真真做一遍。我见过太多人跳过这个步骤结果后面模型加载不出来时不知道是模型的问题还是环境的问题排查起来极其痛苦。3. 模型搭建实操从零搭一台带传感器的差速小车3.1 定义link和joint先让车“长出来”模型搭建的实际操作我习惯用URDF xacro来做原因前面说了要和ROS生态配合。先放一个最小可用的差速小车底盘这是一个简化版的xacro文件片段?xml version1.0? robot xmlns:xacrohttp://www.ros.org/wiki/xacro namediff_drive_car xacro:property namebase_width value0.4/ xacro:property namebase_length value0.6/ xacro:property namebase_height value0.1/ xacro:property namewheel_radius value0.15/ xacro:property namewheel_width value0.05/ !-- 底盘 -- link namebase_link visual geometry box size${base_length} ${base_width} ${base_height}/ /geometry origin xyz0 0 ${base_height/2} rpy0 0 0/ material nameblue/ /visual collision geometry box size${base_length} ${base_width} ${base_height}/ /geometry origin xyz0 0 ${base_height/2} rpy0 0 0/ /collision inertial mass value10.0/ inertia ixx0.1 ixy0.0 ixz0.0 iyy0.1 iyz0.0 izz0.1/ /inertial /link ... /robot这段代码看着简单里面藏了三个必须讲清楚的细节。第一个是visual和collision拆开写。车子的外观和碰撞体都是box但尺寸和位置完全一致这是简化后的写法。换成复杂模型时visual可以用精细meshcollision必须用基本几何体这是物理引擎稳定性的关键。第二个是inertial标签里写了mass和inertia矩阵。10kg的车重对室内机器人来说算合理值惯性矩阵这里用了近似值因为底盘是长方体用公式算了再做对角线化。第三个是xacro里用了${}表达式这是xacro的数学计算能力参数化之后改尺寸只需要改property不用满文件找数字。3.2 关节配置轮子转起来之前先想清楚转轴差速小车的两个主动轮一个万向轮或者从动轮。主动轮用continuous关节从动轮用fixed就行。这里有个新手特别容易犯的错joint的origin坐标写错了导致轮子“长”在车身里。每个joint的origin表示子link坐标系在父link坐标系中的位置和姿态轮子中心应该在车身侧面高度等于轮子半径所以joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz${base_length/2} ${base_width/2 wheel_width/2} ${wheel_radius} rpy0 0 0/ axis xyz0 1 0/ /jointleft_wheel这个link的原点要设在轮子中心joint的origin把轮子中心定位在底盘的左前位置。axis是旋转轴对于差速小车来说轮子是绕y轴转的所以是xyz0 1 0。如果轮子横着转那就要把轴改成0 0 1。万向轮的joint通常用fixed或者一个带阻尼的continuous都行实际仿真里我用fixed居多因为在平面场景里万向轮不起主导作用还能减少一个自由度让数值求解更稳。3.3 传感器接入让仿真小车“看得见”障碍物小车装传感器的意义在于仿真环境里的算法验证。雷达、摄像头、IMU、GPSGazebo里都有对应的传感器插件。这里用二维激光雷达做示例在URDF里需要加一个link和一个gazebo插件link namelaser_link visual geometry cylinder radius0.02 length0.02/ /geometry origin xyz0 0 0 rpy0 0 0/ /visual /link joint namelaser_joint typefixed parent linkbase_link/ child linklaser_link/ origin xyz0.3 0 ${base_height 0.15} rpy0 0 0/ /joint gazebo referencelaser_link sensor typegpu_lidar namelaser_sensor pose0 0 0 0 0 0/pose topicscan/topic update_rate10/update_rate ray scan horizontal samples720/samples resolution1/resolution min_angle-1.570796/min_angle max_angle1.570796/max_angle /horizontal /scan range min0.10/min max10.0/max resolution0.01/resolution /range noise typegaussian/type mean0.0/mean stddev0.01/stddev /noise /ray /sensor /gazebo这里一个容易被忽略的细节是sensor的topic设置。在gazebo_ros_pkgs配合下雷达话题会被发布成/scan这是因为插件把它转成了ROS话题。如果你在RViz里没有看到扫描数据大概率是sensor的坐标系tf没有发布或者topic名字对不上。另一个细节是update_rate这直接决定了传感器数据的频率。10Hz和30Hz的雷达在SLAM算法里的表现天差地别。我实际调参时通常先用10Hz跑通流程性能优化时再拉高到30Hz。3.4 差速驱动插件让模型真正动起来模型有了、传感器有了还差最后一步——给它一个驱动。差速小车在Gazebo里最常用的方式是gazebo_ros的DiffDrive插件gazebo plugin namediff_drive_controller filenamelibgazebo_ros_diff_drive.so ros namespace/demo/namespace /ros update_rate50/update_rate left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation${base_width wheel_width}/wheel_separation wheel_diameter${2 * wheel_radius}/wheel_diameter max_wheel_torque20/max_wheel_torque max_wheel_acceleration1.0/max_wheel_acceleration command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic odometry_frameodom/odometry_frame robot_base_framebase_link/robot_base_frame /plugin /gazebo这个插件的几个参数值得好好解释。wheel_separation和wheel_diameter必须和模型尺寸严格对应差一点就差很多因为差速运动学的计算全靠这两个参数。max_wheel_torque决定电机的最大输出力矩设小了车爬不了坡设大了会显得没有物理感。odometry_frame和robot_base_frame则要和你的tf树结构一致不然rostopic里能看到odom消息但tf一查就是找不到坐标系。实测中最常遇到的问题是cmd_vel话题发布了但小车不动。此时优先检查插件是否加载成功用gazebo --verbose启动可以看到插件报错信息。另外DiffDrive插件对joint名称大小写敏感如果插件里写的是left_wheel_joint而URDF里是left_wheel_Joint那就会静默失败。4. 模型修改实战把别人的模型改成自己想要的4.1 外观和尺寸修改不要动碰撞体去改视觉在实际项目里我们经常拿到一个别人分享的模型但尺寸跟自己的需求不一样。比如从网上下了一个机械臂模型发现它的底座尺寸和你的工作台对不上。这个时候很多人的第一反应是直接改visual里的box尺寸改完之后发现机械臂的抓取点全乱了因为mesh文件和visual的尺寸不匹配。正确的做法是分两层改如果visual用的是基本几何体box、cylinder、sphere直接改geometry里的尺寸没问题。如果visual用的是mesh文件那么应该去改mesh文件本身或者在visual的geometry外面加一层scale标签来缩放。在URDF里mesh引用是支持scale的visual geometry mesh filenamepackage://my_robot/meshes/arm.stl scale0.001 0.001 0.001/ /geometry /visual这个scale特别常用因为很多CAD导出的模型单位是毫米而URDF默认单位是米导入时直接缩小1000倍。忘了写scale是模型加载后尺寸不对的头号原因。外观的material颜色修改就比较简单了在URDF的material标签里定义颜色或者在SDF的visual下直接写material ambient0.8 0.2 0.2 1.0/ambient diffuse0.8 0.2 0.2 1.0/diffuse /material但这里有个细节SDF里材质颜色影响光照而URDF的material只是visual的颜色不会影响物理属性。如果要模拟发光材质、半透明材质就得在SDF里写URDF做不到。4.2 关节限位和力矩调整仿真效果的直接影响因素机械臂模型中最常修改的是joint的limit参数。比如你从网上下载的六轴机械臂模型关节范围是-180度到180度但你的应用场景只需要-90度到90度。改limit很简单joint nameshoulder_pan_joint typerevolute parent linkbase_link/ child linkshoulder_link/ origin xyz0 0 0.1 rpy0 0 0/ axis xyz0 0 1/ limit lower-1.570796 upper1.570796 effort100.0 velocity3.0/ /joint这里的effort是关节最大力矩velocity是最大角速度。这两个参数直接影响机械臂的动力学仿真效果。effort设太小机械臂就抬不起来velocity设太小机械臂就显得“肉”。我一般会参考真实机械臂的datasheet把effort和velocity设成接近真实值这样后面做轨迹规划时MoveIt的约束检查才有意义。关节改成连续旋转也是常见需求revolute和continuous的区别就是continuous没有limit限制适合轮子和云台转轴。改装时直接把type改成continuous去掉limit标签但注意continuous关节在Gazebo里如果没有控制器去控制会自由旋转停不下来需要配合PID控制。4.3 增加新link和joint完整加减法的操作顺序在现成模型上增加一个新的传感器或者执行器是个高频操作。比如给无人机模型加一个云台相机或者给小车加一个机械臂。这里最容易出的问题是link和joint的坐标关系搞错导致新零件飞在天上或者嵌入机身。我的习惯操作顺序是先在3D思维里确定新link的安装位置画一个简单的坐标草图。在URDF/xacro里定义新link设置好visual、collision和inertial。定义joint把新link挂到正确的父link上。启动仿真用gz model --info或者RViz查看tf关系确认位置正确。微调joint的origin直到位置满意。以给差速小车加一个单线激光雷达为例前面3.3节已经写了代码这里补充inertial的设置。传感器link同样需要inertial质量可能只有0.1kg惯性矩阵设一个很小的值就行。很多人在加传感器时忘记写inertial结果Gazebo直接崩溃错误信息还特别难懂其实就是在加载模型时发现link缺少惯性属性。4.4 修改SDF世界文件环境搭建的真正主战场URDF改的是机器人SDF世界文件改的是环境。在Gazebo里新建一个障碍物、一片地形、一堵墙都是在world文件里操作的。SDF的模型插入有两种方式把模型直接写在world文件里或者用include引用外部模型库。前者适合简单障碍物后者适合复用复杂模型。比如在world文件里加一个简单的柱子sdf version1.6 world namedefault include urimodel://sun/uri /include include urimodel://ground_plane/uri /include model nameobstacle_1 statictrue/static pose2.0 0.5 0.5 0 0 0/pose link nameobstacle_link collision namecollision geometry cylinder radius0.2/radius length1.0/length /cylinder /geometry /collision visual namevisual geometry cylinder radius0.2/radius length1.0/length /cylinder /geometry /visual /link /model /world /sdfstatic为true表示这个模型是静态的不受重力影响也不会因为碰撞而移动适合墙壁、柱子、地面障碍物。如果要模拟箱子、球这些可以被推开的物体把static改成false然后加上惯性参数和摩擦系数即可。还有一种常见需求是加载带网格的地形。Gazebo可以用高度图heightmap生成地形SDF里用heightmap标签引用图片灰度图的像素值就是地形高度。这个在野外机器人仿真里特别实用我之前跑四足机器人就是在heightmap地形上调试步态算法比平地测试多暴露了一堆问题。5. 常见问题与排查技巧实录5.1 模型一加载就掉进地面或者从天上砸下来这是排名第一的模型加载问题。现象是机器人刚出现就直接往下掉穿透地面后无限下落或者在半空中开始抖。这个问题的原因无非三类机器人模型的初始高度不对base_link的z坐标设得太低导致底盘和地面重叠物理引擎为了解算碰撞把车往上弹或者往下压。缺collision几何体或者collision几何体跟visual不一致物理引擎压根不知道模型还有边界。缺inertial属性物理引擎无法计算质量分布数值解算直接发散。排查办法很简单给base_link的origin z加一个值让它明显高于地面。在URDF里base_link的origin通常为origin xyz0 0 0.2 rpy0 0 0/其中0.2是底盘中心到地面的高度。如果是加载world里的模型直接在pose里改z值。改完后如果模型稳定落地不弹跳说明高度对了如果还是穿透那就是碰撞体或惯性属性缺失。5.2 关节抖动、模型飞走、数值发散关节抖动是物理引擎数值不稳定最直观的表现尤其在机械臂和带弹簧阻尼的系统里。抖动的原因通常是这几种碰撞体过度复杂物理引擎每步的接触点计算量太大导致求解不收敛。关节的effort值太小而重力矩很大机械臂在重力作用下处于极限状态。惯性矩阵设置得过于夸张比如给一个1kg的link设置了100kg·m²的惯性张量会导致求解器认为这个物体“极难转动”数值上就容易震荡。仿真步长太大。Gazebo默认步长是0.001秒如果你改过max_step_size比如改成0.01那么多数刚体系统的物理精度就会下降抖动是常见的后果。解决方向就是反着来简化碰撞体、合理设置effort、核对惯性参数、恢复步长到0.001。如果是机器人负载较重出现的关节抖动可以适当调大关节的PID控制器的P和D参数提高关节的刚性。5.3 传感器话题没有数据模型加载正常、tf正常但雷达话题就是没有数据。排查思路是这样的用ros2 topic list看看sensor话题是否存在。如果连话题都没有说明传感器插件没加载大概率是gazebo referencelaser_link里面的reference和link名字对不上。话题存在但类型不对。gazebo_ros的Lidar插件默认发布sensor_msgs/LaserScan但有些版本的插件配置不同发布的是PointCloud2那你订阅的话题名称和类型都要匹配。更新频率太低也会让人以为“没数据”比如update_rate设成了0.01Hz一分钟才发一帧肉眼看起来就是没数据。tf树缺了laser_link相关的关系RViz里看不到点云但不代表话题没数据。这个时候要检查joint有没有把laser_link挂到base_link的tf树上。我之前帮人排查过一个案例雷达话题有数据但RViz里就是没有点云最后发现是frame_id设成了laser而tf树里发布的是laser_link名字对不上。把所有坐标系统一命名之后问题立刻消失。5.4 仿真卡顿帧率上不去模型一多、传感器一多仿真性能就拉胯。优化思路按优先级排序优先级最高的是把碰撞体简化为基本几何体这对性能的影响最大。一次接触计算的开销和碰撞体面片数直接相关。传感器是性能黑洞。雷达的sample数从720改成360渲染频率从60fps降到30fps肉眼几乎看不出区别但仿真速度能翻倍。关闭实时渲染的额外开销。在Gazebo里可以设置headlesstrue/headless跑无界面模式如果需要看画面可以通过远程渲染或者录制视频离线看。利用GPU加速。新版本Gazebo支持硬件加速渲染在world文件的scene标签里设置GPU相关参数或者在启动命令里加--render-engine ogre2选对渲染引擎能明显降低CPU负载。我在一个多机器人编队仿真里模型数量从10台增加到20台后实时因子从1.0掉到0.3。优化碰撞体、降低雷达采样率和关闭不必要的传感器刷新之后实时因子恢复到了0.85。优化空间是实打实的。6. 实操心得与项目经验6.1 我的习惯模型越早改越好攒着不如当场处理仿真模型的“债”跟代码的技术债一样越拖越贵。模型文件里一个尺寸不一致的问题当时不修后面做SLAM、做导航、做抓取时就会以各种诡异的方式冒出来。我在做机械臂抓取项目时曾经因为URDF里一个link的坐标系旋转差了90度导致MoveIt规划出来的轨迹在仿真里看着正常一放到真实场景就撞机。后来查了一下午才发现是最开始建模时偷懒把坐标系方向写错了。所以模型验证这事每加一个新部件都要跑一次仿真哪怕只是在空世界里看一眼位置和姿态。直觉上这很花时间但整体算下来比憋到最后一次性排查要省太多时间。6.2 关于获取模型和包的一些个人看法网上很多教程会建议从别人的仓库直接git clone一堆模型文件但我不建议这么做。原因很简单你根本不知道那个模型是用什么版本的Gazebo、什么版本的ROS、什么物理引擎参数跑通的。拿过来直接在你自己环境里跑大概率有版本冲突尤其是URDF里引用了自定义mesh路径的模型路径一个对不上就加载失败。更好的做法是把别人的模型当成参考自己在URDF/xacro里按需重建或修改。模型搭建本身不难难的是搞懂每个参数的含义。自己动手写一遍比看十遍教程都管用。我现在做新项目时如果原模型是URDF就先转成xacro做参数化如果是SDF就会单独研究它的写法毕竟SDF的属性组织方式和URDF差别很大转换工具虽然存在但转换结果通常需要手动修复。6.3 最后分享一个小技巧版本管理对模型文件同样重要模型文件是代码不是“资源文件”。URDF、xacro、SDF、world文件、mesh文件全部应该纳入版本管理。我自己的项目里机器人模型和仿真环境配置都放在同一个git仓库里每次修改都写commit message说明改了什么这让我在出问题的时候能快速回溯到上一次能正常运行的版本。另外如果你经常在不同机器之间同步项目那就要注意区分绝对路径和相对路径。URDF里的mesh路径最好用package://your_package/meshes/xxx.stl这样的写法这样你的模型在别人的电脑上克隆下来也能直接运行不用手动改路径。这一点看着小但在团队协作和跨机器调试时能替你省掉好几个小时的沟通成本。Gazebo模型搭建与修改这件事说到底就是一场“跟物理引擎对话”的过程。你给的参数越合理它给的反馈就越真实。参数乱填它就会用各种玄学问题回报你。我的建议是每次改完模型先在仿真里花几分钟验证一下让模型在空世界里静止几秒看看有没有异常的漂移、抖动和穿透再去跑业务逻辑。这个习惯养成之后你的仿真效率会有一个质的提升。