Particles ocean

A deep-water surface driven by a real inverse FFT. A Phillips spectrum evolves in frequency space, Stockham passes produce a displacement field, and half a million particles ride the waves through an HDR bloom chain.

Open fullscreen
// Evolves the FFT ocean spectrum each frame.
//
// `initial-spectrum.wgsl` seeds h0(k) and h0(-k) from the Phillips-style
// wind spectrum. This pass applies deep-water dispersion over time to produce
// the frequency-domain height and horizontal displacement channels consumed by
// the IFFT passes; later passes transform them into spatial displacement,
// normals/foam, and particles.
 
import { PI, G, cmul, fullscreenPosition } from "./ocean-common.wgsl";
 
struct SpectrumUniforms {
  resolution: f32,
  size: f32,
  time: f32,
  choppiness: f32,
};
@group(0) @binding(0) var<uniform> u: SpectrumUniforms;
@group(0) @binding(1) var u_initialSpectrum: texture_2d<f32>;
 
struct VSOut { @builtin(position) pos: vec4f };
 
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
  var out: VSOut;
  out.pos = fullscreenPosition(vi);
  return out;
}
 
@fragment fn fs_main(in: VSOut) -> @location(0) vec4f {
  let coord = in.pos.xy - vec2f(0.5);
  let n = select(coord.x - u.resolution, coord.x, coord.x < u.resolution * 0.5);
  let m = select(coord.y - u.resolution, coord.y, coord.y < u.resolution * 0.5);
  let k = (2.0 * PI / u.size) * vec2f(n, m);
  let kLen = length(k);
 
  let h0 = textureLoad(u_initialSpectrum, vec2u(coord), 0);
  let w = sqrt(G * kLen) * u.time;
  let expp = vec2f(cos(w),  sin(w));
  let expm = vec2f(cos(w), -sin(w));
 
  let h = cmul(h0.rg, expp) + cmul(h0.ba, expm);
 
  // Convert the evolved height spectrum into slope/height and choppy
  // horizontal displacement spectra before the inverse FFT stages.
  var kn = vec2f(0.0);
  if (kLen > 0.0) { kn = k / kLen; }
  let negI_h = vec2f(h.y, -h.x);
  let hx = negI_h * kn.x * u.choppiness;
  let hz = negI_h * kn.y * u.choppiness;
 
  let cA = vec2f(hx.x - h.y, hx.y + h.x);
  let cB = hz;
  return vec4f(cA, cB);
}