Fix ghost shadows from clipped geometry in shadow cast pass#13388
Fix ghost shadows from clipped geometry in shadow cast pass#13388Masty88 wants to merge 11 commits intoCesiumGS:mainfrom
Conversation
…ate fragment shader logic
…stead of combined sources
|
Thank you for the pull request, @Masty88! ✅ We can confirm we have a CLA on file for you. |
|
(Don't worry about the test that is currently failing. That's a known issue) |
Performance noteTo quantify the trade-off introduced by the On main, the markers wrap the existing computation in // Model.js — updateReferenceMatrices()
if (model.isClippingEnabled()) {
performance.mark("clippingPlanesMatrix-start");
// ... multiply + inverseTranspose ...
performance.mark("clippingPlanesMatrix-end");
}On the fix branch, the markers wrap the uniform function in // ModelClippingPlanesPipelineStage.js — model_clippingPlanesMatrix uniform
model_clippingPlanesMatrix: function () {
performance.mark("clippingPlanesMatrix-start");
// ... multiply + inverseTranspose ...
performance.mark("clippingPlanesMatrix-end");
return result;
},
The fix introduces a ~5–6× increase in matrix computations per frame (one per Alternatives considered:
|
Description
Geometry clipped via
ClippingPolygonCollectionorClippingPlaneCollectioncontinued to cast shadows as if it were still present, making shadow/sun studies
unusable in AEC and BIM workflows where clipping replaces an existing photomesh
with a proposed design.
Root cause —
ClippingPolygonCollectionFor opaque models, the shadow cast shader skipped calling the original fragment
shader as an optimization (no color output needed in the depth pass). This meant
discardstatements injected by the clipping stage never executed during theshadow pass.
The fix inspects the fragment shader source for
discardbefore caching thederived cast shader. If found (
hasDiscard = true), the cast shader callsczm_shadow_cast_main()— the renamed original fragment main — so that clippingdiscards execute correctly during the shadow pass. For plain opaque shaders
without
discard, the previous fast path is preserved. The flag is included inthe shader cache key to avoid collisions between the two variants.
BEFORE:

AFTER:

Root cause —
ClippingPlaneCollectionThe clipping planes matrix (
model_clippingPlanesMatrix) was computed once perframe inside
updateReferenceMatricesusing the camera view matrix and cached onthe model. Since the shadow pass runs before
updateReferenceMatricesiscalled each frame, the cached matrix always reflected the camera view of the
previous frame. During the shadow cast pass
czm_viewrepresents the lightdirection, not the camera, so the planes were evaluated in the wrong coordinate
space — the GPU received the wrong matrix and could not tell which pixels should
be clipped.
The fix removes the per-frame computation from
Model.jsand moves it into theuniform function inside
ModelClippingPlanesPipelineStage, wherecontext.uniformState.view3Dalways reflects the active pass (camera or light)at the exact moment each draw call is issued.
BEFORE:

AFTER:

Issue number and link
Fixes #6261
Testing plan
ShadowMapShaderSpec.jscovering thehasDiscardbranchingfor both opaque and translucent cases.
ClippingPolygonCollection— toggle Clippingoff to see ghost shadows reappear.
CesiumAirmodel with rear half clipped by aClippingPlaneCollection—mirrors the screenshot from the original bug report.
npm run test -- --grep ShadowMapAuthor checklist
CONTRIBUTORS.mdCHANGES.mdwith a short summary of my changeAI acknowledgment
Tools: Claude
Model: Claude Sonnet 4.6