-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Note: I’m not sure if this is the right place to ask, since my work is for a different project and not directly related to wave simulation. However, I’m trying to achieve something similar (overlaying dynamic textures on a mesh in Gazebo Sim with Ogre2), and I’m hoping you might be able to point me in the right direction or share any relevant patterns.
I’m working on a pair of plugins to render wheel tracks on a terrain mesh in Gazebo Sim Harmonic (gz-sim 8, Ogre2).
The idea is:
- A server-side system plugin tracks wheel positions, converts them to UVs over a terrain mesh, and publishes them.
- A GUI-side plugin receives these UVs, draws into a CPU texture (a grayscale mask / heightmap), uploads it to the GPU, and binds it to the terrain’s PBR material so the rover’s wheel tracks appear as darkened “dents” on the terrain.
Conceptually, the data flow works, but I’m struggling with getting the texture to render reliably on the terrain in Ogre2 / PBR (it either reverts to the original material after zooming, or the terrain becomes black when I remove the <pbr> block).
I’d appreciate guidance on the correct way to overlay a custom texture on a terrain in gz-sim + Ogre2, and any advice on material cloning issues and best practices for binding textures from a GUI plugin.
Setup / Architecture
Plugins
-
MeshTracksSystem (server-side system plugin)
- Attaches to the terrain model (e.g.
sand_terrain). - Tracks specified wheel links in the world.
- Converts wheel world positions
(x, y)into UV coordinates based on configurable terrain bounds. - Publishes those UV points on
/gz/mesh_tracks/track_point.
- Attaches to the terrain model (e.g.
-
MeshTracksGui + MeshTracksRenderer (GUI-side plugin)
- Runs in the GUI process.
- Subscribes to
/gz/mesh_tracks/track_pointand/gz/mesh_tracks/clear_cmd. - Maintains a CPU-side grayscale texture (height / mask).
- Draws circular “dents” at the received UVs.
- Uploads the texture to GPU and binds it to the terrain mesh’s Ogre2 /
HlmsPbsmaterial so tracks show up on the terrain visual.
Goal: As the rover drives over the terrain, wheel tracks are drawn into the texture and rendered on top of the terrain’s existing PBR material (albedo + normals).
Environment
-
Gazebo Sim: gz-sim 8 (Harmonic)
-
Rendering engine: Ogre2 ogre2.log
-
ROS: ROS 2 Jazzy (with
ros_gz_bridge) -
World: custom
leo_sand_world -
Terrain model:
sand_terrainusingmeshes/flatmoon.dae -
Terrain visual SDF:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/leo_sand_gazebo/models/sand_terrain/model.sdf#L7C1-L44C1 -
System plugin SDF (attached to terrain model):
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/leo_sand_gazebo/models/sand_terrain/model.sdf#L59-L96 -
GUI plugin SDF:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/leo_sand_gazebo/worlds/leo_sand.sdf#L267-L291
What works so far
1. Server-side: wheel tracking and UV publishing
MeshTracksSystem:
- Attaches correctly to
sand_terrain. - Finds wheel links globally:
[gazebo-1] [Msg] [MeshTracksSystem] ======== FIRST UPDATE - FINDING LINKS ========
[gazebo-1] [Msg] [MeshTracksSystem] Found wheel [0]: [wheel_FL_link] entity=21 pos=(0.16, 0.22)
[gazebo-1] [Msg] [MeshTracksSystem] Found wheel [1]: [wheel_FR_link] entity=30 pos=(0.16, -0.22)
[gazebo-1] [Msg] [MeshTracksSystem] Found wheel [2]: [wheel_RL_link] entity=24 pos=(-0.15, 0.22)
[gazebo-1] [Msg] [MeshTracksSystem] Found wheel [3]: [wheel_RR_link] entity=33 pos=(-0.15, -0.22)
[gazebo-1] [Msg] [MeshTracksSystem] ======== INITIALIZATION COMPLETE ========
- Computes and logs UV coordinates:
[gazebo-1] [Msg] [MeshTracksSystem] Wheel[0] wheel_FL_link: world(0.19, 0.22) -> UV(0.5037, 0.5045) [published: 1]
[gazebo-1] [Msg] [MeshTracksSystem] Wheel[1] wheel_FR_link: world(0.19, -0.22) -> UV(0.5037, 0.4955) [published: 1]
[gazebo-1] [Msg] [MeshTracksSystem] Wheel[2] wheel_RL_link: world(-0.12, 0.22) -> UV(0.4976, 0.5045) [published: 1]
[gazebo-1] [Msg] [MeshTracksSystem] Wheel[3] wheel_RR_link: world(-0.12, -0.22) -> UV(0.4976, 0.4955) [published: 1]
...
So the server-side logic (finding links, computing UVs, publishing messages) appears to be correct.
2. GUI-side: subscriptions and CPU texture
MeshTracksGui + MeshTracksRenderer:
- Subscribes successfully:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksGui.cc#L106-L114
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksGui.cc#L117-L125
[MeshTracksGui] Subscribed to track points: [/gz/mesh_tracks/track_point]
[MeshTracksGui] Subscribed to clear command: [/gz/mesh_tracks/clear_cmd]
- Initializes with Ogre2 scene:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksGui.cc#L162-L166
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L131-L134
[MeshTracksGui] Render engine: ogre2
[MeshTracksRenderer] Scene name: [scene]
- Receives UV messages:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L204-L218
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksGui.cc#L219-L231
[gazebo-1] [GUI] [Msg] [MeshTracksGui] Received track point #1 UV(0.5037, 0.5045) initialized=YES
[gazebo-1] [GUI] [Msg] [MeshTracksRenderer] Track point #1 UV(0.5037, 0.5045) pending=1 bound=YES
-
CPU texture / debug images:
- I can enable a test pattern (
draw_test_pattern = true). - I save debug images from the CPU buffer:
/tmp/mesh_tracks_debug_*.png
Example: https://github.com/khaledgabr77/wheel_track/blob/main/mesh_tracks_debug_486.png
- I can enable a test pattern (
The debug PNG shows exactly what I expect:
- White background
- Black border and diagonal X
- Gray circle in the center
- Darker circular “dents” along the rover’s wheel path
So the pipeline System → GUI → Renderer → CPU texture appears to be correct.
3. Finding the terrain visual and creating the GPU texture
Renderer logs:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L495-L502
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L471-L475
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L265-L272
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L363-L366
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L279-L283
[MeshTracksRenderer] Found terrain visual (exact match): [sand_terrain::terrain_link::terrain_visual]
[MeshTracksRenderer] Created OGRE2 texture: MeshTracksTexture_0
[MeshTracksRenderer] Step 2/4: OGRE2 GPU texture created
[MeshTracksRenderer] Step 4/4: First GPU upload complete!
[MeshTracksRenderer] Step 3/4: Texture BOUND to terrain material!
[MeshTracksRenderer] ======== READY FOR TRACKS ========
So:
- The correct
Visualis found by exact name. - Ogre2 texture + staging texture are created.
- The code reaches an
Ogre::Itemand anOgre::HlmsPbsDatablockand callssetTexture(...)on it.
The problem
Even though:
- The CPU texture (debug PNGs) is correct (pattern + tracks),
- The renderer finds the right visual and datablock,
- The logs say the texture is bound,
the terrain in the GUI never reliably shows this texture.
With the current implementation, the terrain model often appears black in Gazebo:
https://github.com/khaledgabr77/wheel_track/blob/main/Screenshot%20from%202025-12-02%2010-23-02.png
Relevant code path:
https://github.com/khaledgabr77/wheel_track/blob/b7562f601058de9579bdc8fd48e5727e3d53749c/gz_mesh_tracks_plugin/src/MeshTracksRenderer.cc#L645-L683
Observed behavior:
-
PBR
<albedo_map>enabled- In debug mode, I can temporarily set the terrain to magenta by changing the
HlmsPbsDatablockdiffuse color. - However, zooming in / out causes the terrain to snap back to the original PBR albedo (lunar/grass look), as if another material/LOD or cloned datablock is being used.
if (this->debugMode) { // DEBUG MODE: // 1) Set a diffuse color so I can see if we're hitting the right material. _pbs->setDiffuse(Ogre::Vector3(1.0f, 0.0f, 1.0f)); // bright magenta // 2) Also bind our track texture as DIFFUSE (just in case the shader uses it). _pbs->setTexture(Ogre::PBSM_DIFFUSE, this->ogreTexture); gzmsg << "[MeshTracksRenderer] DEBUG: Set DIFFUSE color + texture for [" << _debugName << "]" << std::endl; } else { // NORMAL MODE _pbs->setTexture(Ogre::PBSM_DETAIL0, this->ogreTexture); _pbs->setDetailMapBlendMode(0, Ogre::PBSM_BLEND_MULTIPLY); _pbs->setDetailMapWeight(0, 1.0f); _pbs->setDetailMapOffsetScale(0, Ogre::Vector4(0, 0, 1, 1)); gzmsg << "[MeshTracksRenderer] SUCCESS: Bound track texture to [" << _debugName << "]" << std::endl; gzmsg << "[MeshTracksRenderer] Using DETAIL0 slot with MULTIPLY blend mode" << std::endl; }
Screenshots:
https://github.com/user-attachments/assets/8119a9d2-4438-40ed-a5cf-d2de2ddcdd0b
https://github.com/user-attachments/assets/ae0dc7f9-299b-494c-bad0-42120330f592 - In debug mode, I can temporarily set the terrain to magenta by changing the
-
Using track texture as main diffuse map (debug mode, PBR enabled)
- When I try to use the track texture as the main diffuse map, the terrain does not display the test pattern + tracks like the debug PNG.
- It still mostly looks like the original PBR material.
-
Removing the
<pbr>block completely- If I remove
<pbr>from the terrain material, the terrain becomes solid black at all zoom levels. - No visible pattern or tracks.
- If I remove
In summary:
- Texture data is correct.
- Visual and datablock are found and patched.
- But in the GUI, the final rendered terrain doesn’t show the track texture as expected, and the PBR material seems to “win”, especially after camera zoom changes.
What I’m looking for
1. Correct way to overlay a custom texture on a terrain in gz-sim + Ogre2
- Is using
PBSM_DETAIL0withPBSM_BLEND_MULTIPLYa reasonable way to “darken” tracks on top of an existing<albedo_map>? - Is there a recommended approach in Gazebo Sim for attaching an additional grayscale mask / decal texture to a PBR material (e.g., for tire tracks, footprints, etc.)? should i used custom shaders?
2. Handling material cloning in Ogre2 within gz-sim
-
The fact that color changes (magenta) sometimes stick briefly and then revert after zooming suggests another
HlmsPbsDatablock. -
Is there a recommended hook or pattern in gz-sim for:
- Ensuring that a patched datablock persists across material clones?
- Properly updating all relevant datablocks/items that might be used for rendering the same visual at different distances?
3. Best practice for binding textures from a Gazebo GUI plugin
-
Given a
gz::rendering::VisualPtrfromScene::VisualByName(...), is walking down toOgre::Item/SubItem/HlmsPbsDatablockthe correct and supported approach? -
Are there any examples in gz-sim (core or plugins) that:
- Modify materials at runtime, and/or
- Bind custom textures (e.g., decals, overlays, masks) to terrain or mesh visuals?