# snowflow: 333k triangles in one draw call, no WebGL fallback, and no published frame rate

> snowflow is a browser snow-rendering demo where every pixel is generated at load time, with no textures, meshes, HDR environment maps or animation clips in the repository. It is unusually specific about its internal constants and unusually silent about what any of them costs.

**Noniv/snowflow_demo** — A real-time procedural snow rendering demo built with WebGPU, Babylon.js and hand-written WGSL. Features GPU-generated terrain, snow deformation, procedural characters, cloth, surf wakes, water spells, atmosphere and post-processing—without textures, meshes, HDRIs or animation assets.

- Repository: https://github.com/Noniv/snowflow_demo
- Website: https://snowflow-lilac.vercel.app/
- Stars: 582 · Forks: 153
- Language: JavaScript
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/noniv-snowflow-demo

## There is no fallback path, and the practical browser floor is set by the slowest engine

The support statement names three browsers with three different version numbers: Chrome and Edge at 113 or newer, Firefox at 141 or newer, and Safari at 26 or newer. It also requires a discrete GPU or a recent integrated one.

Then it says there is no WebGL fallback by design, and that if the browser does not expose the WebGPU API the page says so and stops.

Those two facts combine into a narrower statement than the version list suggests. Chrome reached 113 long ago, so Chrome is not the constraint. Firefox 141 and Safari 26 are much later, so the binding requirement is whichever of those two releases most recently. A visitor on an older browser, on a machine without WebGPU, or in an automated environment gets a message instead of a scene, and there is no reduced mode to fall back to.

That is a defensible choice for a demo whose entire point is that the work happens in shaders. A fallback path would mean maintaining a second implementation of every system described in the write-up, and the page would be measuring a different thing.

The cost is that the demo cannot be evaluated on hardware you have not checked, and the overlay that reports frame times lives inside the page that will not load.

## One draw call for 333k triangles, and the price is that nothing about the terrain exists on the CPU

The terrain claim is specific: a nested-ring geometry clipmap of eight rings, 8.5 cm inner spacing, roughly an 870 metre radius, 333 thousand triangles, rendered as one static mesh in one draw call.

What makes that work is stated in the next sentence. Vertices carry only a grid index and a ring level. World placement, level-of-detail morphing and displacement all happen in the vertex shader. So there is no CPU rebuild and no per-frame upload.

And then the cost appears, in the shadow section rather than the terrain section. The write-up says the engine's own cascade generator cannot be used here, because the terrain has no CPU geometry matching what is drawn, so every shadow caster has to register the vertex program it is actually rendered with.

That is the shape of the whole design in one exchange. You get the draw-call count by making the geometry exist only on the GPU, and you pay for it by losing the engine's built-in shadow pipeline and hand-rolling three cascades instead.

The heightfield is described as layered gradient noise with analytic derivatives, anisotropic about a single prevailing wind, baked once into a 4096-square two-channel floating point texture. Broad dune ridges, a long swell, drifts sheared along the wind for asymmetry on the lee face, and sparse rock outcrops all come out of that one bake.

## The heightfield is mirrored back to the CPU, which is a different claim from having nothing on the CPU

It is easy to read the terrain section and conclude that the CPU knows nothing about the surface. That is only true of the mesh.

The heightfield bake ends with a mirror back to the CPU, and the stated reason is that character grounding samples exactly the surface that is drawn rather than a re-implementation of it. So the height data crosses the bus once at load time so the character's feet can find the ground, and the author's stated reason for doing it that way is to avoid maintaining two implementations that can disagree.

That distinction matters more than it sounds. Two data paths exist and they serve different purposes. The mesh is GPU-only and never comes back. The height is baked once, read back, and shared. A reader who quotes the one-draw-call figure as evidence that the CPU is idle has misread the design.

The same care shows up in the shading section, where compression, wetness and ice are described as surface state channels that one shader reads rather than as separate materials, and in the fur, which is a partial torus emitted twenty-two times and alpha-tested against a hashed strand field rather than geometry that was authored.

Every one of those is a decision to represent something as a parameter the shader evaluates, instead of as an asset the pipeline loads.

## Deformation tracks displaced mass separately from depth, which is what makes a berm a berm

The snow deformation is a persistent additive buffer: two 2048-square targets in a half-float format covering 80 metres at 3.9 cm per texel, ping-ponged by a single full-screen pass each frame that scrolls, relaxes and splats in one dispatch.

The addressing is what makes that cheap. A texel's coordinate is the fractional part of the world position divided by the buffer size, so the window follows the player and the buffer is never copied. Newly exposed texels are detected and zeroed by that same pass, which means there is no separate clear step to forget.

Then the channels, and the second one is the interesting one: depression depth, displaced mass, compression, and ice. The write-up says explicitly that the displaced-mass channel is what separates a trail with raised berms from a flat footprint decal.

That is the whole trick in one sentence. If you only track how deep you pressed, you get a dent. If you also track how much material moved, you can render the pile you pushed aside, which is the difference between a footstep and a trench.

Refill is where the numbers live, and they are the project's claims rather than measurements: loose berms slump three times faster than a packed trench floor, wind drives infill from upwind, and about 71 percent of trail depth survives a minute, spreading and softening as it goes. All of it is anisotropic diffusion plus slump plus exponential decay.

## The recurring solution is to delete a degree of freedom rather than constrain one

Three separate places in this write-up solve a problem the same way, and the pattern is more interesting than any one of them.

The character has an eighteen-bone skeleton whose bind pose is a table of numbers, geometry lofted from that table at load time, and locomotion solved from motion state rather than played back. The foot mechanism is the clearest case: a distance-driven stance and swing machine writes a foot's world position exactly once, on touchdown, and holds it fixed while two-bone inverse kinematics reaches for it. The stated consequence is that a planted foot cannot slide, because nothing in the code is able to move it.

Not clamped, not damped, not corrected after the fact. The ability to slide was removed.

Next to that: gait phase advances with ground travelled, so stride length and ground speed are the same number by construction rather than two numbers kept in agreement.

The surf wake does the same thing to its normals. They are differenced out of the same function that places the geometry, so the write-up's claim is that they cannot disagree with it.

Three unrelated subsystems, one idiom. Where most engines add a constraint and then maintain it, this one removes the variable. It costs flexibility, and the trade is that the invariants hold by construction rather than by testing.

## Four of the five spells are the surf wake with different parameters

The spells section is short and it is mostly a reuse claim.

One water material, one mesh, one draw call, eight strands. Four of the five spells move a coherent body of water, and the write-up says they are structurally the same object: a swept surface along a spine, with a radius, a parallel-transported frame and a foam channel. That is the same construction as the surf wake.

A strand that is not in use is switched off by zeroing its rows, so the draw count does not depend on how many spells are active. That is a nice piece of budgeting, and it depends on the shared construction being real rather than approximate.

The wake itself is described as a swept mesh rather than a particle effect. Its spine is the path the board has taken, resampled every 30 cm into a 96 by 3 data texture, while the mesh is a static lattice addressed by column, row and side with every vertex placed in the vertex shader. So a nineteen-metre wake and a two-metre one cost the same buffer and the same 4.6 KB upload.

The cross-section is a breaking wave integrated from a turning tangent, sweeping from just below horizontal at the base to 284 degrees at the tip, so one parameter runs continuously from a low heaped bank to a lip that hangs back across its own face. Peak wall is given as 2.4 metres at a full-speed carve, collapsing 0.88 seconds after it is laid, which is what makes wake length equal to life times speed with no second constant.

## Precise constants throughout, no frame rate anywhere, and a manifest that will publish

The write-up is dense with numbers, and none of them is a performance result.

Texel sizes, ring counts, radii, triangle counts, a 36 by 12 cloth solve rendering as a 72 by 32 surface, nine body collision capsules, four cloth panels, twenty-two fur emissions, 4.6 KB per upload, 2.4 metre peaks, 0.88 second collapse, 71 percent survival. Every one of those is an internal constant or a geometric property.

The project also describes a settings overlay that reports sun angle, wind bearing, subsurface radius, deformation depth, tonemap curve and exposure as live sliders, plus a frame-time graph with median, ninety-fifth percentile and one percent low, draw calls, triangle counts and a per-system CPU breakdown, and debug views for normals, depth, cascade coverage, the deformation buffer and the raw shadow map.

So the instrumentation is thorough and none of its output is published. There is no frame rate, no GPU model, no test methodology, and no statement of what the author measured. The overlay exists for the person running it.

The repository itself is eight entries: a gitignore, a licence, the readme, an HTML entry point, a lockfile, the manifest, a source directory and a Vite config. No tests, no continuous integration, no linter configuration.

The manifest is worth one more look. The package is named `snowflow` at version 1.0.0 with no private flag, no repository field, no author field and no test script, and its whole dependency list is the Babylon core and materials packages plus Vite. Nothing stops `npm publish` from succeeding on a name that describes a private demo.

## Conclusion

Use snowflow as a reading of how much of a real-time renderer can be moved into shaders, because the terrain, the deformation, the character and the surf wake each solve a hard problem by deleting a degree of freedom rather than constraining one, and the reasoning is legible from the write-up alone. Do not use it to judge achievable performance, because nothing in the project states a frame rate, a GPU model, or a test methodology, and every number you will find is an internal constant such as texel size or ring spacing. Before you point anyone else at it, check whether they have WebGPU at all: there is no fallback path, the page stops with a message if the API is missing, and the practical browser floor is set by the least recently updated engine rather than by Chrome.

## FAQ

### What is the snowflow demo and what does it contain?

A real-time procedural snow rendering demo built with WebGPU, Babylon.js and hand-written WGSL. Everything is generated on the GPU at load time, with no textures, meshes, HDR environment maps or animation data in the repository. The live demo is hosted at snowflow-lilac.vercel.app.

### Does snowflow work without WebGPU?

No, and that is a design decision rather than an omission. There is no WebGL fallback, and if the browser does not expose the WebGPU API the page says so and stops. The stated requirements are Chrome or Edge 113 or newer, Firefox 141 or newer, Safari 26 or newer, and a discrete or recent integrated GPU.

### How does snowflow render its terrain?

As a nested-ring geometry clipmap of eight rings at 8.5 cm inner spacing and roughly 870 m radius, 333k triangles, in one static mesh and one draw call. Vertices carry only a grid index and ring level, and placement, level-of-detail morphing and displacement happen in the vertex shader.

### How does snowflow make tracks in the snow?

With a persistent additive buffer of two 2048-square half-float targets covering 80 m at 3.9 cm texels, addressed toroidally so the window follows the player without copying. Channels track depression depth, displaced mass, compression and ice, and the displaced-mass channel is what produces raised berms rather than a flat decal.

### How is the snowflow character animated without animation clips?

There is no rig file, no clips and no authored mesh. An eighteen-bone skeleton is defined by a table of numbers, geometry is lofted from that table at load, and locomotion is solved from motion state. A foot's world position is written once on touchdown and held fixed while two-bone inverse kinematics reaches for it.

### What performance figures does snowflow publish?

None. The write-up gives internal constants and geometry figures such as triangle counts, texel sizes, upload sizes and wave dimensions, and the in-page overlay measures median and percentile frame times, draw calls and per-system CPU cost, but no frame rate, GPU model or measurement methodology is published in the repository.

## Sources

- [Issues](https://github.com/Noniv/snowflow_demo/issues)
- [License: MIT](https://github.com/Noniv/snowflow_demo/blob/main/LICENSE)
- [Noniv/snowflow_demo on GitHub](https://github.com/Noniv/snowflow_demo)
- [Project website](https://snowflow-lilac.vercel.app/)
- [README](https://github.com/Noniv/snowflow_demo/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/noniv-snowflow-demo
