Skip to main content

Chapter 2: Building Simulation Environments

Introduction

A robotic digital twin is only as good as the environment it operates in. Whether you're testing a delivery robot navigating an office building or a humanoid walking across rough terrain, you need realistic, customizable simulation worlds.

In this chapter, you'll learn how to build Gazebo worlds from scratch, populate them with models, configure lighting and environmental properties, and create testing scenarios that match your real-world deployment conditions.

Why this matters: The right testing environment exposes edge cases and failure modes before they occur in the real world. A warehouse robot should be tested in a simulated warehouse. An outdoor rover should face simulated hills, rocks, and varying lighting conditions.

Gazebo World File Structure

Every Gazebo simulation starts with a world file - an XML document (SDF format) that defines everything in the scene: physics settings, models, lighting, and more.

Basic World Template

<?xml version="1.0"?>
<sdf version="1.6">
<world name="my_world">

<!-- Physics configuration -->
<physics type="ode">
<gravity>0 0 -9.81</gravity>
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>

<!-- Lighting -->
<scene>
<ambient>0.4 0.4 0.4 1</ambient>
<background>0.7 0.7 0.7 1</background>
<shadows>true</shadows>
</scene>

<!-- Sun (directional light) -->
<include>
<uri>model://sun</uri>
</include>

<!-- Ground plane -->
<include>
<uri>model://ground_plane</uri>
</include>

</world>
</sdf>

Save this as basic_world.world and launch it:

gazebo basic_world.world

You'll see an empty world with a ground plane and sunlight.

World File Sections Explained

SectionPurposeExample Use Case
<physics>Configure physics engine (see Chapter 1)Set gravity, simulation speed
<scene>Visual environment settingsAmbient lighting, background color, shadows
<include>Import pre-built modelsAdd ground plane, sun, buildings
<model>Define custom objectsCreate walls, obstacles, test objects
<light>Add custom light sourcesSpotlights, room lighting, outdoor sun
<gui>Configure GUI camera and interfaceSet initial camera position

Including Pre-Built Models

Gazebo ships with a library of pre-built models (ground planes, simple shapes, furniture, etc.). You include them using the <include> tag:

<!-- Include ground plane -->
<include>
<uri>model://ground_plane</uri>
</include>

<!-- Include sun (directional light) -->
<include>
<uri>model://sun</uri>
</include>

<!-- Include a table -->
<include>
<uri>model://table</uri>
<pose>2 0 0 0 0 0</pose> <!-- Position: x=2, y=0, z=0 -->
</include>

<!-- Include a construction cone -->
<include>
<uri>model://construction_cone</uri>
<pose>-1 1 0 0 0 0</pose>
<name>cone_1</name> <!-- Give it a unique name -->
</include>

Finding Available Models

Gazebo models are stored in model databases. View available models:

# List models in Gazebo model database
ls /usr/share/gazebo-11/models

# Or browse online Gazebo model database
# http://models.gazebosim.org/

Common useful models:

  • ground_plane: Flat infinite ground
  • sun: Directional light source (simulates sunlight)
  • asphalt_plane: Textured ground for outdoor scenes
  • cafe_table, wood_cube_5cm, construction_barrel: Indoor/outdoor props
  • grey_wall, brick_wall_*: Walls for building environments

Creating Custom Models

For custom objects (walls, obstacles, test fixtures), you can define models directly in the world file:

Example: Simple Wall

<model name="wall_north">
<static>true</static> <!-- Wall doesn't move (performance optimization) -->
<pose>5 0 1 0 0 0</pose> <!-- Position: x=5, y=0, z=1 (center of wall) -->

<link name="wall_link">
<!-- Collision geometry -->
<collision name="collision">
<geometry>
<box>
<size>0.2 10 2</size> <!-- Width: 0.2m, Length: 10m, Height: 2m -->
</box>
</geometry>
</collision>

<!-- Visual geometry -->
<visual name="visual">
<geometry>
<box>
<size>0.2 10 2</size>
</box>
</geometry>
<material>
<ambient>0.7 0.7 0.7 1</ambient> <!-- Light gray color -->
<diffuse>0.7 0.7 0.7 1</diffuse>
</material>
</visual>
</link>
</model>

Example: Test Room (Four Walls)

<!-- World with four walls forming a 10m x 10m room -->
<world name="test_room">
<physics type="ode">
<gravity>0 0 -9.81</gravity>
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>

<include>
<uri>model://sun</uri>
</include>

<include>
<uri>model://ground_plane</uri>
</include>

<!-- North wall -->
<model name="wall_north">
<static>true</static>
<pose>5 0 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
</material>
</visual>
</link>
</model>

<!-- South wall -->
<model name="wall_south">
<static>true</static>
<pose>-5 0 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
</material>
</visual>
</link>
</model>

<!-- East wall -->
<model name="wall_east">
<static>true</static>
<pose>0 5 1 0 0 1.5707</pose> <!-- Rotated 90° around Z-axis -->
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
</material>
</visual>
</link>
</model>

<!-- West wall -->
<model name="wall_west">
<static>true</static>
<pose>0 -5 1 0 0 1.5707</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.2 10 2</size></box>
</geometry>
<material>
<ambient>0.8 0.8 0.8 1</ambient>
</material>
</visual>
</link>
</model>
</world>

Lighting Configuration

Proper lighting is essential for realistic rendering and camera/vision sensor testing.

Ambient Lighting

Ambient light provides uniform illumination from all directions (simulates scattered light):

<scene>
<!-- RGBA values: 0.0 = dark, 1.0 = bright -->
<ambient>0.4 0.4 0.4 1</ambient> <!-- Moderate ambient light -->

<!-- Background color (sky) -->
<background>0.7 0.8 0.9 1</background> <!-- Light blue sky -->

<!-- Enable shadows for realism -->
<shadows>true</shadows>
</scene>

Directional Light (Sun)

The sun model provides directional light simulating sunlight:

<!-- Predefined sun model -->
<include>
<uri>model://sun</uri>
</include>

<!-- Or create custom directional light -->
<light name="sun" type="directional">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
</attenuation>
<direction>-0.5 0.1 -0.9</direction> <!-- Light direction vector -->
</light>

Point Lights (Indoor Lighting)

Point lights emit in all directions from a single point (like light bulbs):

<!-- Ceiling light in test room -->
<light name="ceiling_light" type="point">
<pose>0 0 2.5 0 0 0</pose> <!-- Position: center of room, 2.5m high -->
<diffuse>1 1 1 1</diffuse> <!-- White light -->
<specular>0.1 0.1 0.1 1</specular>
<attenuation>
<range>20</range> <!-- Light reaches 20m -->
<linear>0.01</linear> <!-- Attenuation factors -->
<constant>0.9</constant>
<quadratic>0.001</quadratic>
</attenuation>
<cast_shadows>false</cast_shadows> <!-- Indoor lights often don't cast shadows for performance -->
</light>

Spotlights (Focused Lighting)

Spotlights emit a cone of light (useful for headlights, flashlights):

<light name="spotlight" type="spot">
<pose>0 0 3 0 0 0</pose>
<diffuse>1 1 1 1</diffuse>
<specular>0.1 0.1 0.1 1</specular>
<attenuation>
<range>50</range>
<linear>0.01</linear>
<constant>0.9</constant>
<quadratic>0.001</quadratic>
</attenuation>
<direction>0 0 -1</direction> <!-- Pointing straight down -->
<spot>
<inner_angle>0.6</inner_angle> <!-- Inner cone (radians) -->
<outer_angle>1.0</outer_angle> <!-- Outer cone (radians) -->
<falloff>1.0</falloff> <!-- Light intensity falloff -->
</spot>
<cast_shadows>true</cast_shadows>
</light>

Spawning Models at Runtime

You can add models to a running Gazebo world using the command line:

Using Gazebo GUI

  1. Launch Gazebo: gazebo
  2. Click "Insert" tab on the left panel
  3. Select a model from the list
  4. Click in the world to place it

Using Command Line

# Spawn a model from Gazebo model database
gz model --spawn-file=/path/to/model.sdf --model-name=my_model -x 0 -y 0 -z 1

# Or use ROS 2 (if ros_gz_sim is installed)
ros2 run ros_gz_sim create -world default -file /path/to/model.sdf -name my_model -x 0 -y 0 -z 1

Example: Dynamically Spawning a Box

Create a simple box model file box.sdf:

<?xml version="1.0"?>
<sdf version="1.6">
<model name="dynamic_box">
<pose>0 0 1 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box><size>0.5 0.5 0.5</size></box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box><size>0.5 0.5 0.5</size></box>
</geometry>
<material>
<ambient>1 0 0 1</ambient> <!-- Red box -->
</material>
</visual>
<inertial>
<mass>1.0</mass>
<inertia>
<ixx>0.042</ixx>
<iyy>0.042</iyy>
<izz>0.042</izz>
</inertia>
</inertial>
</link>
</model>
</sdf>

Spawn it in Gazebo:

gz model --spawn-file=box.sdf --model-name=test_box -x 2 -y 2 -z 0.5

Creating Terrain and Outdoor Environments

Height Maps for Terrain

Gazebo supports heightmap-based terrain from grayscale images:

<model name="terrain">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<heightmap>
<uri>model://my_terrain/materials/textures/heightmap.png</uri>
<size>100 100 10</size> <!-- X, Y size in meters, max Z height -->
<pos>0 0 -5</pos> <!-- Offset position -->
</heightmap>
</geometry>
</collision>

<visual name="visual">
<geometry>
<heightmap>
<uri>model://my_terrain/materials/textures/heightmap.png</uri>
<size>100 100 10</size>
<texture>
<diffuse>model://my_terrain/materials/textures/ground.jpg</diffuse>
<normal>model://my_terrain/materials/textures/ground_normal.jpg</normal>
<size>10</size> <!-- Texture tiling -->
</texture>
</heightmap>
</geometry>
</visual>
</link>
</model>

Creating heightmap images:

  • Use grayscale images (PNG)
  • White pixels = highest elevation
  • Black pixels = lowest elevation
  • Resolution: 512x512 or 1024x1024 recommended

Textured Ground Planes

For flat outdoor environments with texture:

<include>
<uri>model://asphalt_plane</uri>
</include>

<!-- Or custom textured plane -->
<model name="grass_plane">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
</collision>
<visual name="visual">
<geometry>
<plane>
<normal>0 0 1</normal>
<size>100 100</size>
</plane>
</geometry>
<material>
<script>
<uri>file://media/materials/scripts/gazebo.material</uri>
<name>Gazebo/Grass</name>
</script>
</material>
</visual>
</link>
</model>

Configuring the Camera View

Set the initial camera position when the world loads:

<gui fullscreen='0'>
<camera name='user_camera'>
<pose>-8 0 5 0 0.4 0</pose> <!-- X, Y, Z, Roll, Pitch, Yaw -->
<view_controller>orbit</view_controller> <!-- orbit, fps, or ortho -->
<projection_type>perspective</projection_type>
</camera>
</gui>

Camera view modes:

  • orbit: Rotate around a point (default, good for overview)
  • fps: First-person shooter style (WASD movement)
  • ortho: Orthographic projection (no perspective)

Practical Example: Warehouse Environment

Let's create a complete warehouse testing environment:

<?xml version="1.0"?>
<sdf version="1.6">
<world name="warehouse">

<!-- Physics -->
<physics type="ode">
<gravity>0 0 -9.81</gravity>
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
<real_time_update_rate>1000</real_time_update_rate>
</physics>

<!-- Scene settings -->
<scene>
<ambient>0.5 0.5 0.5 1</ambient> <!-- Indoor ambient light -->
<background>0.3 0.3 0.3 1</background> <!-- Dark background -->
<shadows>true</shadows>
</scene>

<!-- Indoor ceiling lights -->
<light name="ceiling_light_1" type="point">
<pose>5 5 4 0 0 0</pose>
<diffuse>0.9 0.9 0.9 1</diffuse>
<attenuation>
<range>15</range>
<constant>0.9</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
</light>

<light name="ceiling_light_2" type="point">
<pose>-5 5 4 0 0 0</pose>
<diffuse>0.9 0.9 0.9 1</diffuse>
<attenuation>
<range>15</range>
<constant>0.9</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
</light>

<!-- Ground (concrete texture) -->
<include>
<uri>model://asphalt_plane</uri>
</include>

<!-- Walls (20m x 20m warehouse) -->
<model name="wall_north">
<static>true</static>
<pose>10 0 2 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry><box><size>0.3 20 4</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.3 20 4</size></box></geometry>
<material><ambient>0.6 0.6 0.6 1</ambient></material>
</visual>
</link>
</model>

<model name="wall_south">
<static>true</static>
<pose>-10 0 2 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry><box><size>0.3 20 4</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.3 20 4</size></box></geometry>
<material><ambient>0.6 0.6 0.6 1</ambient></material>
</visual>
</link>
</model>

<model name="wall_east">
<static>true</static>
<pose>0 10 2 0 0 1.5707</pose>
<link name="link">
<collision name="collision">
<geometry><box><size>0.3 20 4</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.3 20 4</size></box></geometry>
<material><ambient>0.6 0.6 0.6 1</ambient></material>
</visual>
</link>
</model>

<model name="wall_west">
<static>true</static>
<pose>0 -10 2 0 0 1.5707</pose>
<link name="link">
<collision name="collision">
<geometry><box><size>0.3 20 4</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.3 20 4</size></box></geometry>
<material><ambient>0.6 0.6 0.6 1</ambient></material>
</visual>
</link>
</model>

<!-- Storage shelves (using simple boxes as placeholders) -->
<include>
<uri>model://wood_cube_10cm</uri>
<name>shelf_1</name>
<pose>5 5 0.5 0 0 0</pose>
</include>

<include>
<uri>model://wood_cube_10cm</uri>
<name>shelf_2</name>
<pose>-5 5 0.5 0 0 0</pose>
</include>

<!-- Obstacles (cones for navigation testing) -->
<include>
<uri>model://construction_cone</uri>
<name>obstacle_1</name>
<pose>2 0 0 0 0 0</pose>
</include>

<!-- Camera view -->
<gui fullscreen='0'>
<camera name='user_camera'>
<pose>-15 -15 10 0 0.6 0.785</pose>
<view_controller>orbit</view_controller>
</camera>
</gui>

</world>
</sdf>

Save as warehouse.world and launch:

gazebo warehouse.world

Summary

In this chapter, you learned:

  • Gazebo worlds are defined in SDF XML files with <world>, <model>, <light>, and <scene> elements
  • Pre-built models can be included using <include> tags with model URIs
  • Custom models are created with <collision>, <visual>, and <inertial> elements
  • Lighting includes ambient (uniform), directional (sun), point (bulbs), and spotlight types
  • Static models (<static>true</static>) improve performance for non-moving objects
  • Terrain can be created using heightmaps from grayscale images
  • Models can be spawned at runtime using Gazebo commands or ROS 2 integration

Next Steps: Now that you can build realistic environments, proceed to Chapter 3: High-Fidelity Rendering and Human-Robot Interaction in Unity to learn how to create photorealistic visualizations and interactive interfaces.


Further Reading: