[WebGPU] CascadedShadowGenerator/ShadowGenerator: shadow missing (x<0) or mirrored (z>0) depending on DirectionalLight direction sign — repro for #18653

Hi @Evgeni_Popov @sebavan — here is the minimal Playground repro that was requested on GitHub issue #18653.

Repro (WebGPU): https://playground.babylonjs.com/#Z6CF57#0
Switch the engine to WebGPU (top toolbar), then press the 3 buttons.

In WebGPU, a DirectionalLight + CascadedShadowGenerator (and a plain ShadowGenerator) only projects the shadow correctly when light.direction.x > 0 AND light.direction.z <= 0:

  • direction.x < 0 → no shadow at all
  • direction.z > 0 → shadow is mirrored
  • direction.x > 0 && z <= 0 → correct

The exact same code renders correctly at every angle in WebGL. Reproduced on 9.3.3, 9.14.0, 9.15.0 and 9.16.0. stabilizeCascades / depthClamp / autoCalcDepthBounds make no difference.

This breaks any day/night cycle — the shadow disappears or flips as soon as the sun crosses to the other half. Our current workaround is to keep the whole solar arc inside the safe quadrant (+x, −z).

Is this a known limitation of the WGSL shadow depth pass, or is there a setting we’re missing? Thanks!

Welcome aboard!

I’m seeing the same thing with both WebGL and WebGPU (tested with latest version of Babylon and 9.16.0):

x>0, z<0:

x<0:

z>0:

It works in Chrome, Edge, and Firefox (Windows 11).

What is your operating system/browser?

1 Like

Hi @Evgeni_Popov — thanks a lot for confirming it, and sorry for the slow reply.

Here is the full setup you asked about. Note it reproduces on both of my GPUs, and as you
saw, on WebGL as well as WebGPU, so it does not look driver-specific:

OS

  • Windows 11 Pro, build 26200 (x64)

Browser

  • Chrome 147.0.0.0
  • WebGPU enabled (default flags, no overrides)

GPUs on this machine (reproduces on both)

  • NVIDIA GeForce RTX 3080 Laptop GPU — driver 32.0.15.9608 (2026-03-30) — this is the
    adapter Chrome picks: adapterInfo reports vendor “nvidia”, architecture “ampere”
  • Intel UHD Graphics — driver 32.0.101.7084 (2026-01-14)

Babylon version

  • Currently reproducing on 9.18.0. Also seen on 9.3.3 → 9.17.0.

The repro

What we see, restated
ShadowGenerator / CascadedShadowGenerator only project correctly when the DirectionalLight
direction has x > 0 and z ≤ 0. Outside that quadrant the shadow is either absent (x < 0)
or mirrored (z > 0).

Already ruled out on our side (no change in behaviour)
stabilizeCascades, depthClamp, autoCalcDepthBounds, and upgrading Babylon (9.16.0 → 9.18.0).

One more data point that may help narrow it down.

We have a working client-side workaround, and how it works is the interesting part:

  • The real sun keeps its true direction (so cardinal directions stay correct).
  • A second, shadow-only DirectionalLight (intensity 0) receives that same direction
    rotated into the safe quadrant, with a pure Y rotation:
    G = RotationY( 3*PI/4 - atan2(dir.x, dir.z) )
  • G is baked into the generator’s transform: getTransformMatrix = _orig().multiply(G)
  • G is then cancelled again on the receiver side.

Result: a correct, true east-west shadow at any sun angle, including the worst case
(x < 0 AND z > 0), with no engine changes.

Why I think this is a useful clue: if simply re-orienting the light into the (+x, -z)
quadrant and undoing that rotation downstream produces a correct shadow, then the shadow
map itself appears to be rendered fine — what breaks looks like the orientation / mapping
stage, not the depth pass. And the failure depends purely on the sign of the light
direction components, not on scene content.

Why it matters for us
We run a full day/night cycle, so the sun sweeps every quadrant. Without the workaround we
have to keep the whole solar arc inside the safe quadrant, which constrains the world’s
orientation — cardinal north ends up wrong relative to the terrain.

Happy to share the workaround module if it helps, and as said, happy to test any patch
against a real WebGPU scene rather than only the Playground.

I’m not really sure what to think, because at first you said:

but in the new post:

In any case, the PG you posted is the same as the one in your first message, so my initial response still stands: I can clearly see shadows in all three cases.

You should take some screenshots of what you’re seeing on your end; that might help us understand what’s going on => I guess the PG isn’t working for you, is it?

In addition, it would be helpful if other people could test the PG and share their observations with us.

Didnt read the whole thread, hope this is the correct test:

So looks the same as in the 2nd post.

Hi @Evgeni_Popov — here are the screenshots you asked for, plus pixel measurements, because “looks the same” is hard to judge by eye on this one.

On Babylon 9.18.2 I can no longer reproduce any WebGL vs WebGPU difference.

WebGL2 vs WebGPU1, Babylon 9.18.2

Top row WebGL2, bottom row WebGPU1, same PG (#Z6CF57#0), same machine, same session.

Method — instead of clicking the GUI buttons I set light.direction from the console (my synthetic clicks silently did nothing, and I got three identical screenshots that fooled me for a minute), then counted shadow pixels on the ground (luma between 90 and 200) and took the centroid:

light.direction WebGL2 WebGPU1 delta
(1, -1.3, 0) 16540 px, cx 472.3 16571 px, cx 472.0 0.2 %
(-1, -1.3, 0) 14513 px, cx 291.9 14544 px, cx 292.2 0.2 %
(0.6, -1.3, 0.7) 30805 px, cx 405.7 30841 px, cx 405.7 0.1 %

That 0.1–0.2 % is antialiasing noise.

Is the shadow actually correct, or just consistently wrong? I checked it against geometry rather than against expectation. The box centre is at y = 4, ground at y = 0, so the shadow of the centre must land at world.xz = dir.xz * (4 / |dir.y|). Projecting that point with Vector3.Project and comparing to the measured centroid, in WebGPU:

  • (1, -1.3, 0) → predicted (1270, 477), measured (1252, 454)
  • (-1, -1.3, 0) → predicted (1076, 459), measured (1072, 435)
  • (0.6, -1.3, 0.7) → predicted (1238, 439), measured (1186, 394)

The middle one is the interesting case: it is the one that used to render no shadow at all, and it is now within 4 px in x on a 777 px canvas. The residual y offset is expected — my mask includes the penumbra, so the centroid sits slightly above the analytic point.

Machine: Windows 11 Pro 26200 x64 · Chrome 147 · WebGPU default, no flags · NVIDIA RTX 3080 Laptop (driver 32.0.15.9608) — also reproduced the original bug on the integrated Intel UHD, so it was never driver-specific · Babylon 9.18.2 (the PG default today).

So from here it looks fixed somewhere between the versions I originally filed against and 9.18.2. If it still fails for you and for @Joe_Kerr on the same 9.18.2, then it is narrower than we thought and the differentiator is the machine, not the backend — happy to run any test build or PG you want and report numbers rather than impressions.

For context, we had been shipping a workaround: a shadow-only proxy light rotated into the safe quadrant, with the same rotation baked into the shared transform matrix so it cancels at the receiver. It cost us the cascades (single ortho instead of CSM). If this is really fixed we can drop it, which is a real win — thanks for chasing it.

Thanks @Joe_Kerr for testing!

It doesn’t fail for us, as you can see in the screenshots.

You can pass ?version=X.Y.Z to the PG url to easily test with another version, if you want to bisect the first version with the problem.

Anyway, I’m glad it’s working out for you now!

1 Like

Confirmed fixed on our side too, on 9.18.2.

We have removed the workaround from post #3 (a shadow-only proxy light rotated into the x > 0, z <= 0 quadrant). Our sun had been pinned to a reduced 74° azimuth arc since 9 July purely to stay out of the failing quadrant. It now runs the real east→south→west arc (azimuth 92° → 175° → 267°), so the DirectionalLight sweeps every quadrant over a full day cycle.

Shadow pixel counts measured in our production scene — CascadedShadowGenerator, 3 cascades, 2048 map, WebGPU, Chrome 147 / RTX 3080 Laptop. Control = toggling darkness 0.03 → 1.0 on the same frame and diffing:

time light dir shadow px
07:00 x- z- 30
10:00 x- z- 50
13:00 x+ z- 140
16:00 x+ z- 72
18:00 x+ z- 96

x < 0 is the case that rendered no shadow at all before. It renders now.

No bisect yet — happy to run the Playground across 9.16 → 9.18.2 and post the table if that is useful.

1 Like

babylon https://youtu.be/OkOJXvOb018

1 Like

This is Beautiful !!!