Robot Simulation with Gazebo & Ignition | Robotics Development Hub
Gazebo (now Ignition/Gazebo) is the standard for high-fidelity robot simulation. This guide covers SDFormat modeling, sensor plugins, physics configuration, ROS 2 integration, headless CI/CD, and domain randomization for sim-to-real transfer.
1. Gazebo Versions & Architecture
Version
Series
ROS 2 Distro
Status
Gazebo 11
Classic
Humble
EOL
Ignition Fortress
Ignition
Humble/Iron
EOL
Ignition Garden
Ignition
Iron/Jazzy
Supported
Gazebo Harmonic
Gazebo (new)
Jazzy/Kilted
Current
Gazebo Ionic
Gazebo (new)
Kilted/Rolling
Latest
2. SDFormat Models
2.1 Model Structure
false
0 0 0.1 0 0 0
5.0
0.01
0
0
0.01
0
0.02
0 0 0.05 0 0 0
0.4 0.3 0.2
0.4 0.3 0.2
0.7 0.7 0.7 1
0.7 0.7 0.7 1
base_link
wheel_left
-0.2 0.15 0 0 0 0
0 1 0
-1e16
1e16
10
20
0.1
0.01
0.5
0.001
0.001
0.0005
0.05
0.04
0.05
0.04
2.2 Nested Models & Includes
model://my_robot_description
robot_1
0 0 0 0 0 0
# gz sdf -p robot.urdf > robot.sdf
3. Sensor Plugins
3.1 Camera Sensor
0.2 0 0.1 0 0 0
1.047
640
480
R8G8B8
0.1
100
gaussian
0.0
0.005
true
30
/camera/image_raw
3.2 IMU Sensor
0 0 0.1 0 0 0
true
200
/imu/data
gaussian
0
0.001
0
0.0001
gaussian
0
0.01
0
0.001
/imu/data
3.3 Lidar Sensor
0 0 0.2 0 0 0
360
1
-3.14159
3.14159
1
1
0
0
0.1
30
0.01
gaussian
0
0.01
/scan
4. Physics Configuration
4.1 Physics Engine Settings
0.001
1.0
1000
quick
50
0
1.3
true
1e-5
0.2
100
0.001
dantzig
4.2 Contact Parameters
0.05
0.04
1.0
1.0
0.0
0.0
0.0
1e4
1e-5
0.2
1e8
1.0
0.01
0.001
5. ROS 2 - Gazebo Bridge (ros_gz)
5.1 Bridge Configuration
bridges:
# Clock
- topic: /clock
gz_topic: /clock
type: rosgraph_msgs/msg/Clock
direction: gz_to_ros
# Joint states
- topic: /joint_states
gz_topic: /model/my_robot/joint_state
type: sensor_msgs/msg/JointState
direction: gz_to_ros
# TF
- topic: /tf
gz_topic: /model/my_robot/tf
type: tf2_msgs/msg/TFMessage
direction: gz_to_ros
# Odometry
- topic: /odom
gz_topic: /model/my_robot/odometry
type: nav_msgs/msg/Odometry
direction: gz_to_ros
# Camera
- topic: /camera/image_raw
gz_topic: /camera
type: sensor_msgs/msg/Image
direction: gz_to_ros
- topic: /camera/camera_info
gz_topic: /camera_info
type: sensor_msgs/msg/CameraInfo
direction: gz_to_ros
# IMU
- topic: /imu/data
gz_topic: /imu
type: sensor_msgs/msg/Imu
direction: gz_to_ros
# Lidar
- topic: /scan
gz_topic: /lidar
type: sensor_msgs/msg/LaserScan
direction: gz_to_ros
# Command velocity (ROS to Gazebo)
- topic: /cmd_vel
gz_topic: /model/my_robot/cmd_vel
type: geometry_msgs/msg/Twist
direction: ros_to_gz
5.2 Launch Bridge
5.3 Spawn Robot in Gazebo
6. Headless CI/CD Integration
6.1 Docker for Headless Simulation
# Dockerfile
FROM gazebo:harmonic
# Install ROS 2 Jazzy
RUN apt-get update && apt-get install -y \
ros-jazzy-ros-gz \
ros-jazzy-ros-gz-bridge \
ros-jazzy-ros-gz-sim \
&& rm -rf /var/lib/apt/lists/*
# Set up headless rendering
ENV QT_QPA_PLATFORM=offscreen
ENV GZ_RENDER_ENGINE=ogre2
ENV OGRE_RTT_MODE=Copy
WORKDIR /workspace
COPY . .
# Run headless simulation
CMD ["gz", "sim", "-v", "4", "-r", "world.sdf"]
6.2 GitHub Actions Workflow
# .github/workflows/simulation-test.yml
name: Simulation Tests
on: [push, pull_request]
jobs:
simulation:
runs-on: ubuntu-latest
container:
image: myorg/ros2-gazebo:harmonic-jazzy
options: --shm-size=2g
steps:
- uses: actions/checkout@v4
- name: Build workspace
run: |
cd /workspace
colcon build --symlink-install
source install/setup.bash
- name: Run headless simulation test
run: |
source install/setup.bash
gz sim -v 4 -r worlds/empty.sdf &
sleep 5
ros2 launch my_robot_bringup simulation.launch.py &
sleep 10
ros2 run my_robot_test test_navigation.py
kill %1 %2
- name: Upload logs
if: always()
uses: actions/upload-artifact@v4
with:
name: simulation-logs
path: ~/.gz/log/
7. Domain Randomization for Sim-to-Real
7.1 Visual Randomization
true
0.5 2.0
0.8 1.2
true
/textures/random/
true
0.01
3
0.5 2.0
true
0.8 1.2
true
0.5 1.5
true
-0.5 -0.5 -0.1 0.5 0.5 0.1
7.2 Randomization via ROS 2
# ROS 2 service to trigger randomization
from std_srvs.srv import Trigger
def randomize_simulation():
cli = node.create_client(Trigger, '/gazebo/randomize')
req = Trigger.Request()
future = cli.call_async(req)
rclpy.spin_until_future_complete(node, future)
return future.result()
8. Advanced: Custom Plugins
8.1 C++ Model Plugin
// CustomPlugin.cc
#include
#include
#include
#include
namespace my_plugins {
class CustomController : public gz::sim::System,
public gz::sim::ISystemConfigure,
public gz::sim::ISystemPreUpdate {
public:
void Configure(const gz::sim::Entity& entity,
const std::shared_ptr
& sdf,
gz::sim::EntityComponentManager& ecm,
gz::sim::EventManager& event_mgr) override {
model_ = gz::sim::Model(entity);
joint_ = model_.JointByName(ecm, "wheel_joint");
}
void PreUpdate(const gz::sim::UpdateInfo& info,
gz::sim::EntityComponentManager& ecm) override {
if (info.paused) return;
// Simple velocity control
double target_vel = 10.0; // rad/s
double current_vel = joint_.Velocity(ecm);
double error = target_vel - current_vel;
double force = 5.0 * error; // P controller
auto force_cmd = ecm.Component
(joint_.Entity());
if (!force_cmd) {
ecm.CreateComponent(joint_.Entity(), gz::sim::components::JointForceCmd({force}));
} else {
force_cmd->Data()[0] = force;
}
}
private:
gz::sim::Model model_;
gz::sim::Joint joint_;
};
GZ_ADD_PLUGIN(CustomController,
gz::sim::System,
CustomController::ISystemConfigure,
CustomController::ISystemPreUpdate)
}
8.2 Build Plugin
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(custom_plugins)
find_package(gz-sim8 REQUIRED COMPONENTS gz-sim8)
add_library(custom_controller SHARED CustomPlugin.cc)
target_link_libraries(custom_controller gz-sim8::gz-sim8)
target_include_directories(custom_controller PRIVATE ${GZ_SIM8_INCLUDE_DIRS})
install(TARGETS custom_controller LIBRARY DESTINATION lib)
9. Isaac Sim Integration
9.1 NVIDIA Isaac Sim (Alternative)
# Isaac Sim provides:
# - PhysX GPU physics (faster for large scenes)
# - RTX rendering (photorealistic)
# - ROS 2 bridge built-in
# - ML-based domain randomization (Replicator)
# Docker
docker run --gpus all -it \
-e "ACCEPT_EULA=Y" \
-e "PRIVACY_CONSENT=Y" \
nvcr.io/nvidia/isaac-sim:4.2.0
# Python API
from isaacsim import SimulationApp
simulation_app = SimulationApp({"headless": True})
from omni.isaac.core import World
world = World()
world.scene.add_default_ground_plane()
# Add robot
from omni.isaac.core.articulations import Articulation
robot = Articulation(prim_path="/World/robot", usd_path="robot.usd")
world.reset()
Summary
SDFormat:
Declarative model definition with nested includes
Sensors:
Camera, IMU, Lidar, GPU-accelerated plugins
Physics:
DART/ODE/Bullet, tune solver, contacts, friction
ROS 2 Bridge:
ros_gz parameter_bridge for bidirectional topics
Headless CI:
Docker + offscreen rendering + GitHub Actions
Domain Randomization:
Visual + physics randomization for sim-to-real
Custom Plugins:
C++ plugins for specialized behavior
Start with ros_gz bridge for ROS 2 integration. Use headless Docker for CI. Add domain randomization before sim-to-real transfer. Consider Isaac Sim for GPU physics and photorealistic rendering.
Related Articles
ROS 2 Architecture Patterns
Sim-to-Real Transfer
Motion Planning Algorithms
Robotics Software Testing
Comments
Post a Comment