-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add blend modes for raster layers #13583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add `raster-blend-mode` property to raster layers, enabling hardware-accelerated blend modes with zero performance overhead. ### Features - **New Property**: `raster-blend-mode` with values: `multiply`, `screen`, `darken`, `lighten` - **Zero Overhead**: Uses native WebGL blend functions (`gl.blendFunc`, `gl.blendEquation`) - **No FBOs**: Avoids render-to-texture approach that caused 33% performance drop in 2015 ### Implementation - **Neutral Color Blending**: Interpolates source toward neutral colors before hardware blending - Multiply: blends toward white (1.0) - Screen/Lighten: blend toward black (0.0) - Darken: direct output (limited opacity support) - **WebGL 1.0 Compatible**: Works on all platforms without extensions - **Premultiplied Alpha Handling**: Automatic switching between premultiplied and non-premultiplied ### Use Cases - Hillshade overlays on satellite imagery (multiply) - Lightening/darkening raster layers (screen/multiply) - Combining multiple raster sources (darken/lighten) - Cartographic design with advanced blending ### Limitations - **Non-linear opacity**: Multiply/screen show increased brightness at middle opacity values - **Darken mode**: Limited opacity support (use multiply instead) - **W3C compliance**: Approximation without framebuffer reads - **Advanced modes**: Overlay, soft-light, hard-light not supported ### Historical Context Revives blend mode support removed in v0.11.3 (2015) due to FBO performance issues: - Previous: 33% performance drop (30fps → 40fps when removed) - Current: Zero overhead using hardware blend functions - Addresses GitHub issues mapbox#368 (2014), mapbox#6818 (2018), MapLibre mapbox#48 (2020+) 🤖 Generated with Claude Code
|
Thanks @JoAllg. |
|
@ibesora Examples from our website:opacity=0, no blend mode: Basemap is not visible
Opacity=0.75, no blend mode: Basemap barely visible, original weather colors are washed out
opacity=0, blend mode=multiply: vibrant original colors and basemap is fully visible
So in general, if you have large overlays then opacity is not very helpful if you want the map below it to be clearly visible. Especially if it is important to see the (3D) terrain. Previously we were using Leaflet together with mapbox to show our weather forecast. Leaflet uses/offers multiply blend mode by default. When switching to mapbox-gl-js we were startled that we don't have that option here. |
|
It is true that the mentioned issues were not about raster layers in particular. |
|
just wanted to chime in about my own use case – a few years ago I was trying to blend OSM I discovered that no existing styling option (including swapping layer order, using opacity etc.) would fulfill my needs. Having I do understand that render-to-texture is a non-starter for performance reasons (and probably a huge undertaking to implement), but a decently working |
|
I would also like to chip in with my use case: a raster noise map of Berlin (data source here). The image is noise levels from white (zero) to dark (noisy). I would like to add this layer to my map without hiding what's underneath. The "darken" blending mode would be ideal for that. |












Adds
raster-blend-modeproperty to raster layers, enabling CSS-like blend modes for map layers with zero performance overhead.What This Adds
New Property:
raster-blend-modewith four blend modes:multiply- Darkens the image (useful for hillshade/elevation overlays)screen- Lightens the image (opposite of multiply)darken- Shows the darker pixels from each layerlighten- Shows the lighter pixels from each layerExample Usage:
{ "id": "hillshade-overlay", "type": "raster", "source": "terrain-rgb", "paint": { "raster-blend-mode": "multiply", "raster-opacity": 0.8 } }Key Benefits
Important Limitations
multiplyinsteadBackground
This feature was highly requested (issues dating back to 2014) but previously removed due to performance concerns. This implementation uses a different approach that has zero performance impact.
Disclaimer: Generated with Claude Code
Details and technical explanation:
Overview
Mapbox GL JS implements CSS-like blend modes for raster layers using native WebGL blend functions. This provides zero-overhead performance while supporting the most common blend modes.
Supported Modes:
multiply,screen,darken,lightenDefault Behavior: When
raster-blend-modeis not specified, standard alpha blending is used (controlled only byraster-opacity).Key Design Decision: Uses WebGL blend functions instead of render-to-texture, avoiding the 33% performance penalty of the previous compositing implementation.
Implementation Approach
Neutral Color Blending Technique
Since WebGL blend functions cannot read the destination framebuffer, we use a "neutral color blending" approach:
This blends the source layer toward a color that has "no effect" in the blend operation:
x × 1 = xxMAX(0, x) = xDifferences from W3C Standard
W3C Formula
Where:
Cs= source colorCb= backdrop color (already in framebuffer)αb= backdrop alphaB(Cb, Cs)= blend functionRequirements: Must read backdrop color and alpha from framebuffer.
Our Implementation
Key Difference: We manipulate the source before blending, rather than interpolating the blend result with the backdrop. This requires no framebuffer reads.
Mathematical Equivalence (for multiply/screen)
For multiply mode:
This approximates the W3C formula but interpolates with the destination color instead of the source color.
Blend Mode Behavior & Limitations
Multiply ✓ Excellent Opacity Support
Implementation:
[DST_COLOR, ZERO]for RGB[SRC_ALPHA, ONE_MINUS_SRC_ALPHA]Behavior:
Limitation: Non-linear brightness behavior. Middle opacity values (0.3-0.7) appear brighter than CSS multiply with equivalent opacity because the source is blended toward white before multiplication.
Screen ✓ Excellent Opacity Support
Implementation:
[ONE, ONE_MINUS_SRC_COLOR]for RGBBehavior:
Limitation: Similar non-linear behavior as multiply. Source blending toward black before screen operation affects the visual result at intermediate opacity values.
Lighten ✓ Excellent Opacity Support
Implementation:
[ONE, ONE]with MAX equation for RGBBehavior:
Why It Works: MAX equation selects the lighter value. When source is blended toward black (darkened), satellite/backdrop is more likely to be selected, allowing it to show through at low opacity.
Darken⚠️ Limited Opacity Support
Implementation:
[ONE, ONE]with MIN equation for RGBBehavior:
Limitation: MIN equation cannot be interpolated without reading the framebuffer. Any attempt to manipulate source colors creates visible overlays (bright or dark depending on approach).
Console Warning: Issued when darken is used with opacity between 0 and 1.
Recommendation: Use multiply mode instead for opacity-controlled darkening.
Expected Visual Behavior
Non-Linear Opacity Response (Multiply/Screen)
Users will observe increased brightness at middle opacity values for multiply and screen modes:
Why This Happens:
The neutral color blending approach (blending toward white for multiply, toward black for screen) inherently creates non-linear opacity behavior.
Why We Can't "Fix" This:
The W3C-compliant approach would require:
mix(destination, blend_result, opacity)This requires render-to-texture with FBOs, which causes a 33% performance penalty (the reason the original compositing implementation was removed in 2015).
The Trade-off:
We chose non-linear opacity behavior to achieve:
This is expected behavior, not a bug. Users who need more linear opacity can use lighten mode or adjust opacity in the 0.7-1.0 range.
Linear Opacity Response (Lighten)
Lighten provides more intuitive linear opacity behavior because MAX naturally rejects the darkened source at low opacity.
No Opacity Response (Darken)
Darken maintains full blend strength across all opacity values (except 0.0 and 1.0).
Usage Recommendations
When to Use Each Mode
Multiply:
Screen:
Lighten:
Darken:
Performance Characteristics
All blend modes have zero performance overhead:
Technical Details
Premultiplied vs Non-Premultiplied Alpha
Standard Alpha Blending (when
raster-blend-modeis not set): Uses premultiplied alphavec4(color × alpha, alpha)[ONE, ONE_MINUS_SRC_ALPHA]Blend Modes (
multiply,screen,darken,lighten): Use non-premultiplied alphavec4(color, alpha)orvec4(mix(neutral, color, alpha), alpha)Blend Function Definitions
Key Pattern: All modes use
[SRC_ALPHA, ONE_MINUS_SRC_ALPHA]for alpha channel (standard alpha compositing), while RGB channels use mode-specific formulas.Shader Implementation
Limitations Summary
File Locations
src/shaders/raster.fragment.glsl(lines 124-141)src/render/draw_raster.ts(lines 93-115)src/gl/color_mode.ts(lines 47-51)src/render/program/raster_program.ts(lines 80-81)src/style/style_layer/raster_style_layer.ts(lines 83-101)References
W3C Standards and WebGL Documentation
Historical Context - Original Compositing Implementation (2014-2015)
Mapbox GL JS originally attempted to implement blend modes using a compositing approach with framebuffer objects (FBOs). This implementation was removed in November 2015 (v0.11.3) due to severe performance issues:
Performance Impact:
Related Issues and Pull Requests:
CHANGELOG Entry (v0.11.3, November 10, 2015):
User Demand for Blend Mode Feature
Mapbox GL JS:
MapLibre GL JS (Fork of Mapbox GL JS):
Observable Notebook:
Why This Implementation Is Different
Previous approach (removed in 2015):
Current implementation (2025):
gl.blendFunc,gl.blendEquation)Additional Resources