Skip to content

Fix: Clamped billboards did not render in 2D/CV scene modes#13639

Open
harrythemorris wants to merge 5 commits into
CesiumGS:mainfrom
harrythemorris:fix/clamped-billboard-2d
Open

Fix: Clamped billboards did not render in 2D/CV scene modes#13639
harrythemorris wants to merge 5 commits into
CesiumGS:mainfrom
harrythemorris:fix/clamped-billboard-2d

Conversation

@harrythemorris

@harrythemorris harrythemorris commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Terrain-clamped billboards were invisible in 2D and Columbus View (or, more accurately, drawn in the wrong place and failing depth test). Un-clamped billboards had peculiar rendering issues in 2D.

image

Broken 2D left, 3D right

And, lastly, the bounding sphere of clamped billboards in 2D/CV mode often covered the majority of the globe.
image

A few separate issues contributed to the problem

Position / coordinate frame confusion

Initial debugging with spector.js revealed that no draw commands were submitted after the globe pass, which led me to suspect frustum culling as the culprit.

Logging the 'actualPosition` revealed that the position was outside of the expected range

Cartographic 3D 2D
(-2.1250727546554997, 0.8177268971321392, 4500) (-2301506.550232819, -3718076.377732224, 4633834.269943927) (-5232539.533463152, -8453150.669048188, 10574895.589471862)

In 2D and columbus, the coordinate is meant to be in the projected-map frame (swizzled so X=height), but what we got was dramatically out of range height values.

There are two positions attached to billboards when clamped:

The clamped position is the position on the terrain that the billboard should clamp to, and it is updated by scene.updateHeight callbacks.

The actual position, conversely, is meant to reflect the position that the billboard should actually render in the current rendering frame, as it is the vertex loaded onto the GPU and used for frustum culling.

The breakdown comes from the ambiguity of the coordinate frame - it seems like a lot of code was written assuming only ECEF exists in a bunch of places, which is not true and which I've now learnt debugging this issue 😁

Billboard Clamped Position

The space this is intended to be in is not clear as the updateFunction converted it to ECEF, but then acted upon it as if it were in the projected map frame as it incremented the X to apply a vertical offset.

computeScreenSpacePosition assumed that the coordinate was in the current mode frame, and did an unproject step to convert back to ECEF if not 3D. This was not the case and the coordinate was already in ECEF, so did not require any transformation at all. This caused the screen position calculated to be very much incorrect too.

Fixes:

  • The clamped position was changed to always be in ECEF, and the extra conversions removed.
  • Actual position is kept as an accurate "rendered position". The naming was not changed to prevent any breakages, but could be improved to reduce ambiguity.

Tile pick positions in update height callbacks

QuadTreePrimitive.updateHeights assumed coordinates coming back from GlobeSurfaceTile.pick were in ECEF. However they also reflect the current projected map frame. They were always passed into cartesianToCartographic to get a cartographic out, which converted assuming ECEF and gave the wrong cartographic back.

Fixes:

  • Unproject/swizzle the position to get the cartographic instead of cartesianToCartographic.
  • Update cache to store Cartographic to prevent caching of a different frame returning inappropriate positions.

Three point check at smaller distances

When the camera was close to the billboard, it would not render, but draw commands were submitted. This meant it was likely getting discarded in the shader.

In 2D mode, the globe and billboard are (theoretically) coplanar. But in reality, getGlobeDepth is relatively imprecise when we are in 2D. The error it produced was greater than the built in "depthsilon" of 10 units used in the three point check, which caused the verts to be erroneously discarded.

This was confirmed with a debug shader colouring the billboards by this distance.

image

3D, green in range, yellow within depthsilon

image

2D, all red = discarded

BillboardCollectionVS.glsl has a max distance check when determining whether to use the three point check or not, which meant that this symptom did not show at moderate to far distances as the check wasn't run.

The proposed fix is to skip the three-point check when in 2D mode, as we are expecting all billboards to pass the depth check against the globe. (This may be a faulty assumption!)

Issue number and link

Fixes #5042
Fixes #12531
Fixes #5041 (I believe, sandcastle and forum link are both dead)
Fixes #8307

Testing plan

Unit tests:

  • BillboardCollection spec: a CLAMP_TO_GROUND billboard reports the projected map position (not the raw ECEF clamped position) in 2D.
  • QuadtreePrimitive spec: the height cache stores a distinct cartographic per entry and is not mutated by callbacks.

Manually verified with this sandcastle, which was AI generated.

Video Before and After

Shows the scenes working in 3D, then 2D, then the red wireframe shows the bounding sphere.

Kapture.2026-07-21.at.23.30.59.mp4

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

AI acknowledgment

  • I used AI to generate content in this PR
  • If yes, I have reviewed the AI-generated content before submitting

If yes, I used the following Tools(s) and/or Service(s):

Crush was used to generate the sandcastle and unit tests.

If yes, I used the following Model(s):

Claude Opus 4.8

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for the pull request, @harrythemorris!

✅ We can confirm we have a CLA on file for you.

@harrythemorris
harrythemorris marked this pull request as draft July 21, 2026 07:08
@harrythemorris harrythemorris changed the title Render terrain-clamped billboards and labels in 2D and Columbus View Fix: Clamped billboards did not render in 2D/CV scene modes Jul 21, 2026
@harrythemorris
harrythemorris force-pushed the fix/clamped-billboard-2d branch 7 times, most recently from 9bda7bb to 05f0be6 Compare July 21, 2026 13:19
@harrythemorris
harrythemorris force-pushed the fix/clamped-billboard-2d branch from 05f0be6 to 5b762b7 Compare July 21, 2026 13:26
@harrythemorris
harrythemorris marked this pull request as ready for review July 21, 2026 13:29
Add coverage for the two subtlest parts of the fix: that updateHeights
un-projects a tile pick result back to the correct cartographic in 2D and
Columbus View, and that a clamped billboard reports the projected map
position (with height preserved) in Columbus View.
@chanwooro

Copy link
Copy Markdown

👍

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

Labels

None yet

Projects

None yet

2 participants