Hi all,
We’ve been building a virtual shadow map for Babylon.js on WebGPU, and it’s now public:
-
npm:
@poqpoq/sundial0.2 (Babylon 9.17.1+ as a peer) -
Live: it’s the default sun shadow for every WebGPU visitor to poqpoq World ( poqpoq World - AI-Enhanced Open Metaverse | BlackBox Creative Suite )
poqpoq World on WebGPU: the tree trunks and the dappled canopy on the hillside are Sundial shadows.
What it is. One directional light, shadowed through a clipmap of 128×128 pages kept in a single depth pool. A compute pass reads the camera depth each frame and asks for the page whose texels match each visible surface’s pixel footprint. A page is drawn once and then kept, so a static scene costs almost nothing to keep shadowed. Marking, paging, culling and rasterisation all run on the GPU: three indirect draws per frame, whatever the scene size. The finest texels are 7.8 mm.
Moving things are cheap too (new in 0.2). Each page also keeps a copy of its static-only depth. When a door, a vehicle or a character moves, the pages it crosses get that copy pasted back and only the moving caster is redrawn on top, not every tree and building under it. With 40 movers walking through our test forest, shadow drawing went from 0.37 ms to 0.05 ms a frame on an RTX 5060. Skinned meshes cast too: the GPU skins the real mesh in the shadow pass (up to 8 influences, 256 bones).
It started from a practical problem. Our worlds are forests and prim-heavy builds, and on integrated GPUs CSM was eating half the frame, so our watchdog kept switching shadows off for iGPU players.
Numbers (lab scene, 1600×900, 4× MSAA, frame ms, medians of 5 interleaved rounds):
| Intel Xe-LPG | RTX 5060 | |
|---|---|---|
| shadows off | 3.8–4.8 | 0.6–0.9 |
| CSM, 3×2048² | 8.8–10.5 | 2.9–3.4 |
| Sundial | 6.1–7.3 | 1.5–2.4 |
On a busy community sim in World, the frame rate went from 29 fps (CSM) to 41 fps (Sundial).
Using it:
import { SundialBabylon } from "@poqpoq/sundial/babylon";
if (SundialBabylon.isSupported(engine)) { // keep your CSM for WebGL2
const sundial = new SundialBabylon(scene, sun, { sceneMin: [-128, -10, -128], sceneMax: [128, 60, 128] });
sundial.addCaster(terrain);
sundial.addCaster(leaves); // alpha-tested casters just work
sundial.addCaster(windmill, { dynamic: true }); // moves: redraws only itself
sundial.addCaster(hero, { dynamic: true }); // has a skeleton: casts its skinned pose
sundial.addReceivers(scene.materials); // Standard and PBR, via a material plugin
sundial.start();
}
The core has no Babylon imports (it only needs a GPUDevice), so a three.js adapter is possible. If that would interest you, please say so; it helps us decide what to build next.
Things we learned the hard way on the Intel iGPU, which may be useful to anyone writing custom shadow or material code on WebGPU:
-
A storage write in a material shader, even one that never runs, turns off early-Z for the whole draw. Receivers have to be strictly read-only.
-
StandardMaterial’s
MATERIAL_ALPHATESTdiscards at the end of the shader, so guard any costly per-fragment work on the alpha test yourself. -
A dynamically indexed local array in a hot fragment path can spill on Intel (it cost us ~45 ms).
-
iGPU A/B needs warm-ups and “flush” pages between runs, or you’ll measure clock-state transients.
Limits (0.2): WebGPU only; one directional light. Skinned casters don’t apply morph targets, and a big rig costs more than it should (every piece of the mesh is culled against the whole mesh’s box), so for many characters a small CSM alongside is still a fine choice: the receiver combines the two with min(), so nothing is darkened twice. That’s what poqpoq World does for its avatars. The adapter uses a few private Babylon internals, so the peer range is pinned to Babylon 9.
Along the way we sent two fixes upstream, and both shipped in Babylon 9.28.0: #18934 (the atmosphere’s PBR plugin now attaches only to materials in its own scene) and #18936 (StandardMaterial’s depth pre-pass honours every alpha source). Big thanks to @Evgeni_Popov and @Deltakosh for the careful reviews, and to @RaananW for merging.
Feedback and bug reports are very welcome, on GitHub or here.
Allen (p0qp0q)
