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.)
- 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