Skip to content

fix(core): add shader-based globe occlusion for IconLayer, TextLayer, and ArcLayer#9975

Open
chrisgervang wants to merge 4 commits intomasterfrom
claude/fix-deck-rendering-issues-XAMSd
Open

fix(core): add shader-based globe occlusion for IconLayer, TextLayer, and ArcLayer#9975
chrisgervang wants to merge 4 commits intomasterfrom
claude/fix-deck-rendering-issues-XAMSd

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Feb 1, 2026

Fixes #9777, #9592, and #9554.

Problem

The previous approach of setting cullMode: 'back' globally for globe projections had two issues:

  1. Billboard geometry (IconLayer, TextLayer) doesn't have proper back faces, causing icons and text to be incorrectly culled and disappear
  2. The coordinate system handedness could cause culling to work in unexpected ways

Solution

This change implements shader-based globe occlusion that works for all geometry types:

  • Added project_globe_is_occluded() function to the projection shader module (both GLSL and WGSL)
  • IconLayer, TextLayer (via MultiIconLayer), TextBackgroundLayer, and ArcLayer now automatically hide geometry that is on the back side of the globe
  • Removed the global cullMode: 'back' parameter from getDefaultParameters() in deck-utils.ts
  • Updated examples to remove manual cullMode: 'none' workarounds

The solution is transparent to users - no API changes or special configuration needed.

When to use project_globe_is_occluded()

In interleaved mode with basemaps (MapLibre, Mapbox), the basemap's depth buffer provides occlusion for most geometry automatically. The shader-based project_globe_is_occluded() function is needed for specific cases where depth buffer occlusion doesn't work correctly:

Layer Type Needs Shader Occlusion? Reason
Billboard geometry (IconLayer, TextLayer) ✅ Yes Screen-space pixel offsets bypass the depth test - the anchor point may be behind the globe while the visual appears in front
Elevated arcs/paths (ArcLayer) ✅ Yes Rise above the globe surface and can "peek" over the horizon before depth catches them
Ground-level geometry (ScatterplotLayer, PolygonLayer, etc.) ❌ No Depth buffer handles occlusion automatically

Usage Pattern

For instance-based layers (IconLayer, TextLayer), use the anchor position without offsets:

vec3 commonPosition = project_position(instancePositions, instancePositions64Low);
// ... calculate gl_Position with offsets ...
if (project_globe_is_occluded(commonPosition)) {
  gl_Position = vec4(0.0, 0.0, 2.0, 1.0); // Clip the vertex
}

For segment-based layers (ArcLayer), use the actual vertex position:

// geometry.position is set by project_position_to_clipspace
if (project_globe_is_occluded(geometry.position.xyz)) {
  widthPixels = 0.0; // Collapse the segment instead of hard clipping
}

https://claude.ai/code/session_01P6canyQPTd6nckN66pauLK

@coveralls
Copy link

coveralls commented Feb 1, 2026

Coverage Status

coverage: 91.1% (+0.009%) from 91.091%
when pulling 763eb7c on claude/fix-deck-rendering-issues-XAMSd
into b830b0e on master.

@chrisgervang chrisgervang added this to the v9.3 milestone Feb 2, 2026
Copy link
Contributor

@charlieforward9 charlieforward9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a nice cleanup for dependent projects building workarounds to deal with this.

A UX issue ive had to accept with the Text culling issue is not culling the GlobeView, which leads to this:

20260203-2059-29.4561020.mp4

Ill pull this version into my repo and test it out.

(is there an easier way for production builds to depend on deck.gl feature branches alternative to the submodule setup?)

Copy link
Collaborator

@felixpalmer felixpalmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hunch still is that there should be a way to fix this by setting up the camera parameters (#9592 (comment)) correctly - but for now this does resolve the issue.

In general we have a number of z-indexing issues when integrating the the maplibre globe, so perhaps in the future we'll be able to safely remove this again

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems a waste to do return visibility > 0.0 ? 0.0 : 1.0; only to undo it with project_globe_get_occlusion(commonPosition) > 0.5

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that. I was exploring the idea of fading around the horizon and this got left over since I decided to start with a simple fix for now

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry that this will cause artifacts when one end of a line segment will be occluded, while the other isn't. Instead you could set the widthPixels to 0 when occluded

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I'll switch to doing that

claude and others added 2 commits February 6, 2026 14:28
… and ArcLayer

Fixes #9777 and #9592.

The previous approach of setting cullMode: 'back' globally for globe projections
had two issues:
1. Billboard geometry (IconLayer, TextLayer) doesn't have proper back faces,
   causing icons and text to be incorrectly culled and disappear
2. The coordinate system handedness could cause culling to work in unexpected ways

This change implements shader-based globe occlusion that works for all geometry types:
- Added project_globe_get_occlusion() and project_globe_is_occluded() functions to
  the projection shader module (both GLSL and WGSL)
- IconLayer, TextLayer (via MultiIconLayer), and ArcLayer now automatically hide
  geometry that is on the back side of the globe
- Removed the global cullMode: 'back' parameter from getDefaultParameters() in
  deck-utils.ts
- Updated examples to remove manual cullMode: 'none' workarounds

The solution is transparent to users - no API changes or special configuration needed.

https://claude.ai/code/session_01P6canyQPTd6nckN66pauLK
Adds a billboard dimension to basemap-browser example to test IconLayer
and TextLayer behavior with billboard: true vs false on globe projection.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chrisgervang chrisgervang force-pushed the claude/fix-deck-rendering-issues-XAMSd branch from abe4b3c to 79f4262 Compare February 6, 2026 22:28
@chrisgervang
Copy link
Collaborator Author

Here's what the maplibre website example and get-started maplibre globe examples now look like without cullMode: 'none'. Does this look like you'd expect @felixpalmer?

Screenshot 2026-02-06 at 2 27 20 PM Screenshot 2026-02-06 at 2 27 25 PM

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

chrisgervang and others added 2 commits February 6, 2026 14:51
- Simplify project_globe_is_occluded to return bool directly (remove
  redundant float conversion via project_globe_get_occlusion)
- Fix GLSL icon-layer to use anchor position without offset for
  occlusion check, matching WGSL behavior
- Fix arc-layer to set widthPixels=0 when occluded instead of hard
  clipping vertices, avoiding artifacts at segment boundaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TextBackgroundLayer was missing the project_globe_is_occluded check,
causing ghost background rectangles to appear on the back of the globe
while the text itself was hidden.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@felixpalmer
Copy link
Collaborator

felixpalmer commented Feb 9, 2026

Does this look like you'd expect?

@chrisgervang actually, no it looks broken to me as the flights are getting cut off too early. The current code does it correctly:

Screen.Recording.2026-02-09.at.11.41.02.mov

with the shader addition in this PR, we are losing the flights near the horizon - so I'm thinking this approach isn't going to work :/

Actually, I'm a bit confused why this PR needs to fix ArcLayer, that seems to work OK doesn't it? https://deck.gl/examples/maplibre

@charlieforward9
Copy link
Contributor

I had an approach in my workaround that blended opacity-fading with a more generous occlusion threshold for smoother transition - but I agree the ArcLayer is a difficult edge case since it is not a layer that sits on the surface of the globe.

@chrisgervang
Copy link
Collaborator Author

chrisgervang commented Feb 9, 2026

Ah true, we can remove ArcLayer from the scope of this PR. I included it because it had the cullMode overrides in all of the examples.

But looking again, those were setting it to "none" which the system does again anyways now I'm removing the "back" override in MapboxOverlay.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IconLayer / TextLayer not rendering under GlobeView (non-Mercator projection)

5 participants

Comments