Skip to content

Conversation

@acalcutt
Copy link
Collaborator

@acalcutt acalcutt commented Nov 22, 2025

Advanced Terrain Rendering: Multiple Hillshade Algorithms & Color-Relief Layer

Overview

This PR introduces advanced terrain rendering capabilities to MapLibre Native, implementing multiple hillshade algorithms and a new color-relief layer type for hypsometric tinting. These features enable advanced cartographic workflows using terrain-RGB tiles.

Closes

Features Implemented

🏔️ Multiple Hillshade Algorithms

Added support for five hillshade rendering algorithms, matching GDAL's capabilities:

  1. Standard (default) - MapLibre's traditional hillshade algorithm
  2. Basic - Simple physics-based model (similar to GDAL default)
  3. Combined - Combines slope and oblique shading for enhanced terrain visualization
  4. Igor - Minimizes effects on underlying map features (based on [Maperitive's algorithm](http://maperitive.net/docs/Commands/GenerateReliefImageIgor.html))
  5. Multidirectional - Combines illumination from multiple light sources (based on [USGS methodology](http://pubs.usgs.gov/of/1992/of92-422/of92-422.pdf))

Style API:

{
  "type": "hillshade",
  "paint": {
    "hillshade-method": "multidirectional",
    "hillshade-illumination-direction": [225, 270, 315, 0],
    "hillshade-illumination-altitude": [30, 30, 30, 30],
    "hillshade-highlight-color": ["#FF0000", "#00FF00", "#0000FF", "#FFFF00"],
    "hillshade-shadow-color": ["#000088", "#008800", "#880000", "#888800"],
    "hillshade-exaggeration": 0.5
  }
}

🎨 Color-Relief Layer (Hypsometric Tinting)

New layer type for elevation-based color mapping, similar to GDAL's color-relief command. Enables hypsometric tinting and custom elevation visualizations.

Style API:

{
  "type": "color-relief",
  "source": "terrain",
  "paint": {
    "color-relief-color": [
      "interpolate",
      ["linear"],
      ["elevation"],
      0, "#0066cc",
      1000, "#00cc66",
      2000, "#ffff00",
      3000, "#cc6600",
      4000, "#ffffff"
    ],
    "color-relief-opacity": 0.8
  }
}

Technical Implementation

Hillshade Enhancements

  • Implemented all five algorithms in hillshade.fragment.glsl with runtime selection
  • Extended illumination system to support multiple independent light sources
  • Added hillshade-method paint property with validation
  • Support for array-valued properties for multi-source lighting

Color-Relief Layer

  • New layer type with complete rendering pipeline
  • Texture-based elevation-to-color mapping using binary search in shaders
  • Extracts stops directly from interpolate expressions (supports any number of stops)
  • Dynamic expression evaluation using the ["elevation"] operator
  • Compatible with both Terrain-RGB and Terrarium tile formats

Implementation details:

  • Efficient GPU-based binary search for color lookup (O(log n))
  • RGBA32F elevation texture format for broad compatibility
  • Straight-alpha color storage for proper interpolation
  • Minimal memory overhead (~100-500 bytes typical, scales with stop count)

Usage Examples

Traditional Hillshade

{
  "id": "hillshade",
  "type": "hillshade",
  "source": "terrain",
  "paint": {
    "hillshade-method": "standard",
    "hillshade-exaggeration": 1.0
  }
}

Multidirectional Hillshade

{
  "id": "hillshade-multi",
  "type": "hillshade",
  "source": "terrain",
  "paint": {
    "hillshade-method": "multidirectional",
    "hillshade-illumination-direction": [225, 270, 315, 0],
    "hillshade-exaggeration": 0.5
  }
}

Hypsometric Tint

{
  "id": "elevation-colors",
  "type": "color-relief",
  "source": "terrain",
  "paint": {
    "color-relief-color": [
      "interpolate", ["linear"], ["elevation"],
      -100, "#1a1a2e",
      0, "#0066cc",
      500, "#00cc66",
      1500, "#66cc00",
      2500, "#cccc00",
      3500, "#cc6600",
      5000, "#ffffff"
    ],
    "color-relief-opacity": 0.7
  }
}

Combined: Multidirectional Hillshade + Hypsometric Tint

{
  "layers": [
    {
      "id": "elevation-tint",
      "type": "color-relief",
      "source": "terrain",
      "paint": {
        "color-relief-color": [
          "interpolate", ["linear"], ["elevation"],
          0, "#70d1ff",
          1000, "#86d7ab",
          2000, "#ffb281"
        ],
        "color-relief-opacity": 0.6
      }
    },
    {
      "id": "hillshade",
      "type": "hillshade",
      "source": "terrain",
      "paint": {
        "hillshade-method": "multidirectional",
        "hillshade-exaggeration": 0.3
      }
    }
  ]
}

Performance

  • Hillshade algorithms: All methods have similar performance characteristics
  • Color-relief: Efficient binary search (8 iterations for 256 stops), comparable to hillshade performance
  • Memory: Minimal overhead - typical color ramps use 100-500 bytes, scales linearly with stop count
  • Compatibility: Works on hardware and software renderers (tested with Mesa llvmpipe)

Testing

Comprehensive test coverage:

  • ✅ Hillshade algorithms
  • ✅ Color-relief with various gradients and opacity settings
  • ✅ Combined hillshade + color-relief layers
  • ✅ Expression evaluation with dynamic elevation values
  • ✅ Both Terrain-RGB and Terrarium tile formats
  • ✅ Software renderer compatibility

Credits

Implementation based on:

The color-relief layer has been ported from MapLibre GL JS with a texture-based approach that removes the uniform array size limitations. While GL JS is constrained by GL_MAX_FRAGMENT_UNIFORM_VECTORS (limiting stops on low-end hardware), the Native implementation can handle extensive color ramps using efficient GPU texture lookups.

Development Assistance:

  • Claude AI Sonnet 4.5 - Primary development assistance
  • Google Gemini 2.5 - Additional code assistance

@github-actions github-actions bot added the iOS label Nov 27, 2025
@acalcutt
Copy link
Collaborator Author

For some testing of the node version I created @acalcutt/maplibre-gl-native-test v6.3.0-pre.1 and a version of tileserver-gl which uses it and also remove the exclusion of hillshade methos at ( https://github.com/WifiDB/tileserver-gl/tree/test-color-relief )

So far I think the hillshade is working mostly correct, for example

Multidirectional
image

standard
image

basic
image

igor
image

combined
image

I have not been able to get my color-relief style work yet but it may be an issue with the style since it doesn't seem to load in the jg side either (maybe it is based off a test version of color relief and needs to be updated)

@acalcutt
Copy link
Collaborator Author

acalcutt commented Nov 28, 2025

One thing to note is the style-spec version used here also includes some changes that were for line-dash array, which I did not include from the generate-style-spec script since it breaks the build. I started to try and do this in a PR at WifiDB#2 , but i don't think it is ready yet and I have run out of my month of Claud AI pro, so what is left could use some assistance.

I think the missing line changes cause some issues on other platforms where those files are getting generated on the fly with their respective generate-style-spec (which i tried to update in the other PR)

@louwers and @birkskyum I did try to make metal/vulkan/webgpu shaders but I don't have anything set up to test them to see if they work. I am guessing the render tests that exists should be able to get run with those renders maybe?

I have been runnning the tests on linux with
cmake --preset linux-opengl-node -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
cd build
xvfb-run --auto-servernum ./mbgl-render-test-runner --manifestPath ../metrics/macos-xcode11-release-style.json --filter "color-relief*"
xvfb-run --auto-servernum ./mbgl-render-test-runner --manifestPath ../metrics/macos-xcode11-release-style.json --filter "hillshade*"

@github-actions github-actions bot added the node label Nov 28, 2025
@louwers
Copy link
Member

louwers commented Nov 28, 2025

Yes these render tests will run on CI. You can patch the style spec JSON to make changes to revert changes that are not implemented yet.

I can help fix the iOS build. Do you have any public styles that use these hillshade algorithms?

@acalcutt
Copy link
Collaborator Author

Something like these is what I am using locally, though changing them so the sources were specified in the style found a tileserver-gl bug i think ( I had to fix tileserver-gl so i could use this file, but it should work with regular maplibre-native)

style-basic.json
style-combined.json
style-igor.json
style-multidirectional.json
style-standard.json

@acalcutt
Copy link
Collaborator Author

acalcutt commented Nov 28, 2025

the intensity of the terrain might be a bit high at lower zoom compare to maplibre-gl-js.

maplibre-native
image

maplibre-gl-js
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android build Related to build, configuration or CI/CD cpp-core iOS node OpenGL Issues related to the OpenGL renderer backend Vulkan WebGPU

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hypsometric Tint from terrain-RGB tiles Additional Hill-shade Options

2 participants