FFT ocean surface
A displaced ocean surface driven by a real inverse FFT. A Phillips spectrum evolves in frequency space, two compute passes run a shared-memory radix-2 IFFT into a displacement field, and a procedural grid rides it with per-pixel normals, foam and a Fresnel sky reflection under a tunable sunset. Orbit with the mouse; tweak the sea from the panel.
// Pass 1 of the 2D IFFT: one 256-point inverse transform per ROW.
// dispatch(N, 1) -> workgroup_id.x = row. workgroup_size is 128 (the portable
// max), so each of the 128 invocations handles TWO of the 256 elements.
import { bitrev, fftStages3 } from "./fft-core.wgsl";
import { N } from "./params.wgsl";
@group(0) @binding(0) var<storage, read> inX: array<vec2f>;
@group(0) @binding(1) var<storage, read> inY: array<vec2f>;
@group(0) @binding(2) var<storage, read> inZ: array<vec2f>;
@group(0) @binding(3) var<storage, read_write> outX: array<vec2f>;
@group(0) @binding(4) var<storage, read_write> outY: array<vec2f>;
@group(0) @binding(5) var<storage, read_write> outZ: array<vec2f>;
var<workgroup> shX: array<vec2f, 256>;
var<workgroup> shY: array<vec2f, 256>;
var<workgroup> shZ: array<vec2f, 256>;
@compute @workgroup_size(128)
fn fftRow(
@builtin(workgroup_id) wid: vec3u,
@builtin(local_invocation_id) lid: vec3u,
) {
let base = wid.x * N;
let t = lid.x;
let i0 = t;
let i1 = t + 128u;
// Bit-reversed load of both elements this invocation owns.
let r0 = bitrev(i0);
let r1 = bitrev(i1);
shX[r0] = inX[base + i0]; shY[r0] = inY[base + i0]; shZ[r0] = inZ[base + i0];
shX[r1] = inX[base + i1]; shY[r1] = inY[base + i1]; shZ[r1] = inZ[base + i1];
workgroupBarrier();
fftStages3(&shX, &shY, &shZ, t);
let norm = 1.0 / f32(N);
outX[base + i0] = shX[i0] * norm; outY[base + i0] = shY[i0] * norm; outZ[base + i0] = shZ[i0] * norm;
outX[base + i1] = shX[i1] * norm; outY[base + i1] = shY[i1] * norm; outZ[base + i1] = shZ[i1] * norm;
}