Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## 1.136 - 2025-12-01

### @cesium/engine

#### Fixes :wrench:

- Fixed depth testing bug with billboards and labels clipping through models [#13012](https://github.com/CesiumGS/cesium/issues/13012)

## 1.135 - 2025-11-03

### @cesium/engine
Expand Down
9 changes: 7 additions & 2 deletions packages/engine/Source/Scene/BillboardCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import SDFSettings from "./SDFSettings.js";
import TextureAtlas from "../Renderer/TextureAtlas.js";
import VerticalOrigin from "./VerticalOrigin.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import WebGLConstants from "../Core/WebGLConstants.js";

const SHOW_INDEX = Billboard.SHOW_INDEX;
const POSITION_INDEX = Billboard.POSITION_INDEX;
Expand Down Expand Up @@ -2039,7 +2040,8 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsOpaque = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: WebGLConstants.LESS,
},
depthMask: true,
});
Expand All @@ -2059,7 +2061,10 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsTranslucent = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: useTranslucentDepthMask
? WebGLConstants.LEQUAL
: WebGLConstants.LESS,
Copy link
Contributor Author

@mzschwartz5 mzschwartz5 Nov 5, 2025

Choose a reason for hiding this comment

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

Renabled as it was before #12821 (yes, that is LEQUAL)

},
depthMask: useTranslucentDepthMask,
blending: BlendingState.ALPHA_BLEND,
Expand Down
31 changes: 22 additions & 9 deletions packages/engine/Source/Shaders/BillboardCollectionFS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ void doThreePointDepthTest(float eyeDepth, bool applyTranslate) {
}
#endif

void doDepthTest() {
// Extra manual depth testing is done to allow more control over how a billboard is occluded
// by the globe when near and far from the camera.
void doDepthTest(float globeDepth) {
float temp = v_compressed.y;
temp = temp * SHIFT_RIGHT1;
float temp2 = (temp - floor(temp)) * SHIFT_LEFT1;
Expand All @@ -156,14 +158,10 @@ void doDepthTest() {
}
#endif

// Automatic depth testing of billboards is disabled (@see BillboardCollection#update).
// Instead, we do one of two types of manual depth tests (potentially in addition to the test above), depending on the camera's distance to the billboard fragment.
// If we're far away, we just compare against a flat, camera-facing depth-plane at the ellipsoid's center.
// If we're close, we compare against the globe depth texture (which includes depth from the 3D tile pass).
vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
if (globeDepth == 0.0) return; // Not on globe


if (globeDepth == 0.0) return; // Not on globe
float distanceToEllipsoidCenter = -length(czm_viewerPositionWC); // depth is negative by convention
float testDistance = (eyeDepth > -u_coarseDepthTestDistance) ? globeDepth : distanceToEllipsoidCenter;
if (eyeDepth < testDistance) {
Expand All @@ -175,7 +173,10 @@ void main()
{
if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard;
if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard;
doDepthTest();

vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
doDepthTest(globeDepth);

vec4 color = texture(u_atlas, v_textureCoordinates);

Expand Down Expand Up @@ -243,6 +244,18 @@ void main()
out_FragColor = color;

#ifdef LOG_DEPTH
czm_writeLogDepth();
// If we've made it here, we passed our manual depth test, above. But the automatic depth test will
// still run, and some fragments of the billboard may clip against the globe. To prevent that,
// ensure the depth value we write out is in front of the globe depth.
float depthArg = v_depthFromNearPlusOne;

if (globeDepth != 0.0) { // On the globe
float globeDepthFromNearPlusOne = (-globeDepth - czm_currentFrustum.x) + 1.0;
float nudge = max(globeDepthFromNearPlusOne * 5e-6, czm_epsilon7);
float globeOnTop = max(1.0, globeDepthFromNearPlusOne - nudge);
depthArg = min(depthArg, globeOnTop);
}

czm_writeLogDepth(depthArg);
#endif
}