fft water: the plan
7 Aug 2026
About a year ago I took a stab at reimplementing Tessendorf’s ocean
water. I got some decent results, but the
implementation was sloppy and not particularly organized. I also never
implemented Bruneton’s “geometry to BRDF” method ,
partially because the implementation was sloppy.
I’ve gotten the itch to take another stab at implementing this water
system. This time, I will try harder to proceed along principled,
logical steps, and check my work more thoroughly along the way.
I’ve also decided to document this process. I expect it to take a few
months to complete this project. Hopefully in the future, these notes
might help someone trying to implement some nice deep-ocean water in
their engine/game/whatever.
I’ll implement this renderer as follows (this list is likely to
change):
- Derive a high-performance GPU-based FFT implementation on CPU.
- Check against a simple reference implementation of
Cooley-Tukey.
- Measure the impact of radix on the numerical precision of the FFT.
- Ideally, generate some graphs.
- Also look at the impact of float precision - 8-bit, 16-bit,
etc.
- Implement this FFT algorithm in slang + webGPU. Render unlit.
Measure performance.
- Implement image export from webGPU harness. Measure error - verify
that it matches expectations.
- Generate a wave energy spectrum using Horvath’s viscous shallow
water wave dispersion relation.
- Generate one frame of wave displacement using slang + webGPU.
- Validate feature scale, energy, etc. You probably want some
histograms.
- Generate one frame of analytic normals using slang + webGPU.
- Validate using finite differences of the heightmap as an
approximation of ground truth. The two images should match within some
small epsilon.
- Generate chop and chop normals.
- Validate feature size and normals using finite differences
(again).
- Implement stdev (per geometry-to-brdf paper).
- (Note to self: this is a static image based on the energy spectrum.
We calculate the ddx/ddy of the offset [meters/px], then divide 2 * pi
by that number to get a wave number. That is then used as the index to
the LUT. The LUT contains, for each wave number, the sum of the
variances of all waves with higher or equal wave numbers.)
- Render a simple scene in webGPU and in Mitsuba 3.
- Implement a simple brdf.
- Implement frame export.
- Implement image diffing / measurement.
- Implement hard shadows.
- Implement soft shadows.
- Validate point lighting.
- Validate directional lighting.
- Implement and validate IBL.
- Implement and validate DFG LUT (energy-preserving roughness).
- Implement vertex deformation and normals using baked heightmap &
tangents. Validate against Mitsuba.
- Make a new scene with a highly subdivided quad.
- Port to Unity.
- Implement tooling to blit a texture through a RenderTexture using a
shader.
- Automation should generate quads, materials, and rendertextures on
behalf of the user.
- Port compute shader to shaderlab pixel shader. Validate.
- Port lit shader to shaderlab.
- Sample scene, frame export, exhaustive validation… the works.
- Validate point, directional, and IBL.
- Add light volumes.
- Add LTCGI.
So… yeah. A lot of work. I’ll get started tomorrow!
back to main page