Babylon.js gave splats a hard ceiling

Images: BabylonJS · Tools

Aug 28, 2026, 1:05 PM ETTools
Babylon.js shipped 9.23.0 on Wednesday with four Gaussian splat changes, two of which are substantial. The first admits something the field has been quiet about: distance-based level of detail does not actually bound how much you draw. The second fixes a shadow bug with an unusually clean diagnosis — the randomness was in the wrong place.
Why it matters: Streamed splat scenes can render an unbounded number of primitives. On a workstation that is a feature. On a laptop it is dropped frames, and on a phone it is a coin toss, because the only lever available was to reduce detail with distance — which lowers the count without capping it.
The new splat budget inverts that. You tell the scene the maximum number of splats you are willing to render and it stays underneath, spending the allowance where it does the most good: close and large stays sharp, small and distant quietly degrades. The result is a predictable frame cost rather than a hope that the scene happens to be light enough.
This is the second browser rendering stack in a week to reach that conclusion. luma.gl's splat module, published to npm days earlier, streams its Coit Tower scene through a bounded residency window — a million rows out of fifty million authored. Different layer of the problem, same admission: somebody has to name a ceiling, and it cannot be the scene.
What shipped:
- `splatBudget` is optional and takes a number or `"auto"` for a per-device default. Omit it and you get the old distance-only behaviour. It can be changed at runtime, and the stream reports back `renderedSplatCount` and `effectiveSplatBudget`.
- Compound scenes assembled from several streamed parts share one budget across the whole thing, apportioned between parts by demand rather than split evenly.
- Level of detail and culling now serve every active camera rather than one, which is what split-view and multi-view rendering needed.
- Detail also refreshes on zoom, on resize, and when the splat itself moves — previously only camera movement triggered it.
- Malformed streaming metadata — degenerate bounds, out-of-range or non-monotonic levels, counts arriving as strings — is sanitised rather than producing holes or silently wrong arithmetic.
- The LOD work is 1,306 added lines across four files; the shadow fix touches twenty files for a net 289.
The shadow bug: The image-based-lighting shadow voxeliser stored binary occupancy, deciding whether a cell was blocked using a stochastic discard seeded by the cell's own position. That last detail is the bug. Because the randomness was a fixed function of position, it produced the same answer every frame, so a semi-transparent splat that happened to lose its coin toss became a permanently, fully blocking voxel. No amount of sample accumulation could average it away — the shadow simply never converged.
The fix moves the randomness from bake time to sample time. Voxels now store real opacity, cell coverage multiplied by splat alpha, and the roulette happens per sample during the shadow ray-march. Accumulated over the existing multi-sample and temporal passes, occlusion converges to the true transmittance rather than to an arbitrary frozen pattern. Fully opaque content takes an early-out and is unchanged.
The two backends had to get there differently, which is a nice illustration of where WebGPU is still awkward: WebGL2 uses a MAX blend into the existing single-channel grids, while WebGPU needs an atomicMax into a packed storage buffer followed by a one-shot copy compute, because storage textures can neither blend nor perform floating-point atomics. A latent WebGL2 bug got swept up too — the 3D voxel textures never clamped their depth axis, so voxels could wrap onto the opposite face.
Yes, but: A budget is a rationing mechanism, not a compression one. Nothing here makes a scene smaller or cheaper to fetch; it decides what to skip once the data has arrived, which means the bandwidth problem is untouched and the quality cost lands on exactly the content a viewer might be about to walk towards.
The budget is also opt-in and defaults to off, so the unbounded behaviour remains what most existing projects will get. `"auto"` will do the sensible thing per device, but somebody has to know to ask for it.
And the shadow work is confined to the IBL path. Splat lighting in general remains the unsolved part — this makes an existing feature converge correctly rather than adding a new one.



