Skip to content

Comments

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

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

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

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Feb 1, 2026

Fixes #9777 and #9592.

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 for billboard geometry:

  • Added project_globe_is_occluded() function to the projection shader module (both GLSL and WGSL)
  • IconLayer, TextLayer (via MultiIconLayer), and TextBackgroundLayer 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 billboard geometry where screen-space pixel offsets bypass the depth test - the anchor point may be behind the globe while the visual appears in front.

Layer Type Needs Shader Occlusion? Reason
Billboard geometry (IconLayer, TextLayer) ✅ Yes Screen-space pixel offsets bypass the depth test
Ground-level geometry (ScatterplotLayer, PolygonLayer, etc.) ❌ No Depth buffer handles occlusion automatically

Usage Pattern

For billboard layers, use the anchor position without pixel 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
}

https://claude.ai/code/session_01P6canyQPTd6nckN66pauLK


Note

Medium Risk
Touches core projection shader code and vertex shaders for IconLayer/TextLayer rendering on globe projection, which could affect visibility/positioning across WebGL and WebGPU backends.

Overview
Adds shader-based globe occlusion for billboarded geometry by introducing project_globe_is_occluded() in the projection shader module (GLSL + WGSL) and using it in IconLayer (GLSL + WGSL) and TextBackgroundLayer vertex shaders to clip icons/text that are on the back side of the globe.

Removes reliance on GPU back-face culling for globe mode by dropping the automatic cullMode: 'back' from Mapbox/MapLibre integration (getDefaultParameters) and cleaning up example/test workarounds that set special cullMode values; the basemap-browser example also adds a billboard dimension + UI/URL toggle to control icon/text billboarding.

Written by Cursor Bugbot for commit 7d342b7. This will update automatically on new commits. Configure here.

@coveralls
Copy link

coveralls commented Feb 1, 2026

Coverage Status

coverage: 91.072% (+0.008%) from 91.064%
when pulling 7d342b7 on claude/fix-deck-rendering-issues-XAMSd
into 224e1b6 on master.

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

@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


// Helper function to check if position is on the back of the globe
bool project_globe_is_occluded(vec3 commonPosition) {
return project_globe_get_occlusion(commonPosition) > 0.5;
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

gl_Position = curr + vec4(project_pixel_size_to_clipspace(offset.xy), 0.0, 0.0);

// Hide arc segments that are occluded by the globe (on the back side)
if (project_globe_is_occluded(geometry.position.xyz)) {
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

@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

@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
Collaborator

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.

@charlieforward9
Copy link
Collaborator

@copilot remove ArcLayer from scope

Copy link
Contributor

Copilot AI commented Feb 20, 2026

@charlieforward9 I've opened a new pull request, #10018, to work on those changes. Once the pull request is ready, I'll request review from you.

@charlieforward9 charlieforward9 linked an issue Feb 20, 2026 that may be closed by this pull request
7 tasks
claude and others added 5 commits February 20, 2026 08:32
… 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>
- 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>
ArcLayer occlusion will be addressed separately. The depth buffer
handles most cases for arc geometry, and the segment boundary
artifacts need more investigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chrisgervang chrisgervang force-pushed the claude/fix-deck-rendering-issues-XAMSd branch from 458ff97 to 7d342b7 Compare February 20, 2026 16:33
@chrisgervang chrisgervang changed the title fix(core): add shader-based globe occlusion for IconLayer, TextLayer, and ArcLayer fix(core): add shader-based globe occlusion for IconLayer and TextLayer Feb 20, 2026
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 2 potential issues.

<>
<DeckGL
controller
parameters={{cullMode: 'back'}}
Copy link

Choose a reason for hiding this comment

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

Removed globe culling from standalone projection test app

Low Severity

Removing parameters={{cullMode: 'back'}} from the standalone DeckGL component causes a regression for the globe view in this test app. The app uses ScatterplotLayer, which has no shader-based globe occlusion. In overlaid mode (not interleaved), there's no shared depth buffer from the basemap, so scatter points on the back of the globe will now be visible — the only prior occlusion mechanism was the cullMode: 'back' parameter.

Fix in Cursor Fix in Web

Copy link
Collaborator

@charlieforward9 charlieforward9 Feb 20, 2026

Choose a reason for hiding this comment

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

GlobeView.tranparent.buffer.mov

If it looks like this old screen recording - I consider that a feature demo given the non interleaved config

Copy link
Collaborator Author

@chrisgervang chrisgervang Feb 20, 2026

Choose a reason for hiding this comment

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

Good point. It looks like this example could be rewritten to use interleaved, or the cullMode config should be kept

interleaved: boolean;
globe: boolean;
multiView: boolean;
billboard: boolean;
Copy link

Choose a reason for hiding this comment

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

Unused globe field in LayerBuildOptions type

Low Severity

The globe field remains in the LayerBuildOptions type and is still passed by buildConfig, but the PR removed its only usage (for arcParameters) without removing the field itself. globe is never destructured or referenced in buildLayers, making it dead code that could mislead readers into thinking globe-awareness is handled in the layer building logic.

Additional Locations (1)

Fix in Cursor Fix in Web

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) [Bug] Icon Layer on Globe Projection

6 participants