Wind field simulation
Sine wind sways every blade in step. A wind field remembers where air was pushed: a blast spreads out, a rolling rock leaves a wake, a vortex swirls and then fades.
The problem
Most grass wind is one sine wave plus noise. It looks fine until something happens in the scene. An explosion, a character running through, a spell: the grass ignores it, or each effect gets its own one-off bend code that knows nothing about the others.
How it works
The source (a Unity project) keeps a small 3D grid of wind velocity around the camera, 32 x 16 x 32 cells of 1 m, and updates it every frame:
- Shift: when the camera moves a whole cell, the grid's contents slide so the wind stays put in the world.
- Diffuse: each cell moves a little toward the average of its 6 neighbours (factor 0.00335, 3 passes).
- Motors: effects add velocity. Omni pushes outward, directional pushes one way, vortex spins around an axis, moving pushes along its path.
- Advect: the velocity carries itself along, forward and then backward.
- Grass samples the grid at its root and bends: the bend angle grows with wind strength, up to 72°.
There is no damping term. Wind fades by spreading thin, and values below the fixed-point step (1/4096) are cut to zero.
What I hit
The source writes advection with compute shaders and atomic adds: each cell scatters into the cells it moves to. WebGL2 has no compute shaders and no atomics, so every pass is a fragment shader rendering into a float texture, ping-ponged between two. I rewrote both advection passes as gathers: each cell reads the neighbours within 2 cells that could have sent it velocity. That matches the scatter as long as nothing moves more than 2 cells in one step, and at these settings nothing gets close.
My build
three.js 0.170, WebGL2. The 3D grid is a 256 x 64 float texture holding the 32 slices as tiles. Seven passes per step at 60 steps per second. 110,000 instanced blades read the field in the vertex shader with the source's bend formula.
Both halves of the video see the same three events. In my run, with no motors left, the average wind speed fell from 0.33 to 0.05 within 7 s and to zero within 17 s.
Trade-offs
- Any number of effects share one cheap grid instead of each bending grass itself
- Wind lingers and spreads, so effects leave traces after they end
- 1 m cells: detail smaller than a cell is lost
- Only covers 32 m around the camera; outside it the grass gets ambient wind only
- Several full-grid passes per frame, every frame
- I rendered on a software GPU, so no timings
When to use it
Open scenes with lots of grass, cloth or particles where gameplay events should visibly disturb the air. For a static backdrop, a sine wave is enough.
Source and credits
Technique source: GodOfWarWindSimulation by SaberZG, https://github.com/SaberZG/GodOfWarWindSimulation, MIT license, commit f34e6f7f75d01d56200f127519045ce5979d9c81. Ported to three.js and GLSL from its compute shaders and C# manager: the grid shift, the 6-neighbour diffusion, the omni / directional / vortex / moving wind motors, the forward and reverse advection, the fixed-point storage and channel merge clamp, and the grass bend function from its WindSystem shader include. The grid size, diffusion and advection factors and the motor force clamp come from the source. The advection passes were rewritten from scatter (atomic adds) to gather because WebGL2 has no compute shaders. No files were copied unchanged.
Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, npm package version 0.170.0. Used for rendering, float render targets and instancing. It is not the source of the wind technique.
My own code: the global sine wind baseline, the event script (shockwave, rolling rock, vortex), the procedural noise in place of the source's noise texture, the grass blade mesh and its static lean, the markers, the debug arrow view, the split screen, labels and video. No external models or assets. No IA model.