Choosing Bullet Physics in Gazebo for Faster Rigid‑Body Simulation
Learn how to switch Gazebo’s physics engine to Bullet, tune its solver, and verify that your simulation stays accurate while gaining speed.
16 Jun 2026, 01:17 UTC

Problem: Simulation speed slowing down your robot tests
You have a Gazebo world with dozens of convex collision shapes (e.g., a warehouse of boxes or a fleet of quadrotors). Each simulation step takes noticeable CPU time, making real‑time testing or long‑duration experiments frustratingly slow. The default ODE solver works, but you wonder if another built‑in engine can give you a speed boost without breaking your existing models or plugins.
Thesis: Bullet is a drop‑in alternative that can halve step time for many rigid‑body workloads
Gazebo ships with several physics engines; the Bullet engine is selectable via a single SDF tag and retains the same ROS 2 topic interface for joint states and sensors. By enabling Bullet and tuning its solver iterations, you can often achieve roughly twice the step speed of ODE while keeping energy conservation within acceptable bounds for typical robotics scenarios.
How to enable Bullet in your world file
Add or modify the <physics> element in your SDF world (.sdf) as shown below. The snippet assumes Gazebo >= 11 (Ignition Gazebo) where the command line tool is gz sim.
<sdf version='1.6'>
<world name='demo'>
<physics>
<type>bullet</type>
<max_step_size>0.005</max_step_size>
<solver>
<type>projected_gauss_seidel</type>
<iterations>50</iterations>
</solver>
</physics>
</world>
</sdf>
Key points:
<type>bullet</type>tells Gazebo to load the Bullet plugin.max_step_sizelimits the integration step; smaller values improve accuracy but raise CPU load.- The
<solver>block lets you choose the constraint solver type (Projected Gauss‑Seidel is the default and works well for most cases) and set the iteration count.
Worked example: verifying a falling cube
To confirm that Bullet behaves similarly to ODE for a simple gravity test, create a minimal world (falling_cube.sdf) containing a unit cube positioned 1 m above the ground.
<sdf version='1.6'>
<world name='falling_cube'>
<physics>
<type>bullet</type>
<max_step_size>0.005</max_step_size>
<solver>
<type>projected_gauss_seidel</type>
<iterations>50</iterations>
</solver>
</physics>
<model name='cube'>
<pose>0 0 1 0 0 0</pose>
<link name='link'>
<visual name='visual'>
<geometry>
<box><size>1 1 1</size></box>
</geometry>
</visual>
<collision name='collision'>
<geometry>
<box><size>1 1 1</size></box>
</geometry>
</collision>
</link>
</model>
<plane name='ground'>
<pose>0 0 0 0 0 0</pose>
</plane>
</world>
</sdf>
Run the simulation headless and log the cube’s z position over time:
# Run in a terminal; no special permissions needed.
gz sim -s -r falling_cube.sdf --headless
While the simulation runs, open another terminal and echo the model state via the gz topic command (replace /model/cube/pose with the actual topic if your installation differs):
gz topic -e /model/cube/pose | grep -oP '(?<=position:\{).*?(?=\})'
Collect a few seconds of data, then compute the observed acceleration (Δz / Δt²). It should be close to 9.81 m/s². Repeat the test with <type>ode</type> to compare results; you should see similar acceleration values, confirming that Bullet does not introduce a systematic bias for this basic case.
Checking that Bullet is actually active
Gazebo prints the selected engine at startup. Look for the line:
[Info] [System.cc:...] Physics engine: bullet
You can also open the GUI (gz sim -g falling_cube.sdf), enable the “Physics Profiler” panel, and verify that the solver type shown matches your <solver> settings and that the iteration count matches what you set.
Trade‑off: solver jitter with high mass ratios
Bullet’s default Projected Gauss‑Seidel solver can exhibit noticeable jitter when bodies differ greatly in mass (e.g., a light sensor attached to a heavy base). Increasing <iterations> reduces jitter but raises CPU usage proportionally. If you observe jitter:
- Open the world SDF.
- Increase
<iterations>(try 100, then 150) and re‑run the simulation. - Monitor the physics profiler; step time will grow, but jitter should diminish.
- If CPU becomes a concern, consider switching the solver type to
sequential_impulse(another Bullet option) which may handle mass disparity better at a different cost.
Remember that any change to <iterations> or solver type only affects Bullet; ODE‑specific contact parameters like <kp> and <kd> are ignored when Bullet is active, so you may need to retune those if you switch back.
Actionable closing: try it, measure, adjust
If your Gazebo workload consists mainly of rigid‑body objects and you are seeing CPU‑bound simulation steps, give Bullet a try:
- Add the
<physics>block with<type>bullet</type>to your world file. - Start with the default
max_step_size(0.005 s) and 50 PGS iterations. - Run a representative scenario and check the physics profiler for step time.
- If step time is acceptable and you see no jitter, you’re done.
- If jitter appears, increase iterations or switch solver type as described above, then re‑measure.
Finally, keep a copy of the original ODE configuration (or a separate world file) so you can revert instantly if a particular model behaves unexpectedly. With these steps you can decide, based on actual measurements, whether Bullet gives you the speed‑up you need without sacrificing the fidelity required for your robotics experiments.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.