Skip to content

Wheel Tracks #186

@khaledgabr77

Description

@khaledgabr77

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.
  • MeshTracksGui + MeshTracksRenderer (GUI-side plugin)

    • Runs in the GUI process.
    • Subscribes to /gz/mesh_tracks/track_point and /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 / HlmsPbs material 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


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:

[MeshTracksGui] Subscribed to track points: [/gz/mesh_tracks/track_point]
[MeshTracksGui] Subscribed to clear command: [/gz/mesh_tracks/clear_cmd]
[MeshTracksGui] Render engine: ogre2
[MeshTracksRenderer] Scene name: [scene]
[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

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 Visual is found by exact name.
  • Ogre2 texture + staging texture are created.
  • The code reaches an Ogre::Item and an Ogre::HlmsPbsDatablock and calls setTexture(...) 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:

  1. PBR <albedo_map> enabled

    • In debug mode, I can temporarily set the terrain to magenta by changing the HlmsPbsDatablock diffuse 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

  2. 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.
  3. 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.

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_DETAIL0 with PBSM_BLEND_MULTIPLY a 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::VisualPtr from Scene::VisualByName(...), is walking down to Ogre::Item / SubItem / HlmsPbsDatablock the 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions