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
import { PI, G, cmul, fullscreenPosition } from "./ocean-common.wgsl";
 
struct InitialSpectrumUniforms {
  resolution: f32,
  size: f32,
  windSpeed: f32,
  windAngle: f32,
  amplitude: f32,
};
@group(0) @binding(0) var<uniform> u: InitialSpectrumUniforms;
@group(0) @binding(1) var u_noise: 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;
}
 
fn phillips(k: vec2f) -> f32 {
  let kk = dot(k, k);
  if (kk < 1e-8) { return 0.0; }
  let w = vec2f(cos(u.windAngle), sin(u.windAngle));
  let L = (u.windSpeed * u.windSpeed) / G;
  let kdotw = dot(normalize(k), w);
  var ph = u.amplitude * exp(-1.0 / (kk * L * L)) / (kk * kk) * (kdotw * kdotw);
  let l = L * 0.001;
  ph *= exp(-kk * l * l);
  if (kdotw < 0.0) { ph *= 0.07; }
  return ph;
}
 
@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 rnd = textureLoad(u_noise, vec2u(coord), 0);
 
  let h0k    = (1.0 / sqrt(2.0)) * vec2f(rnd.x, rnd.y) * sqrt(phillips(k));
  let h0negk = (1.0 / sqrt(2.0)) * vec2f(rnd.z, rnd.w) * sqrt(phillips(-k));
 
  return vec4f(h0k, h0negk.x, -h0negk.y);
}