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
// Coefficients after each level's canonical radius are zero, so one fixed
// 22-tap loop reproduces front's 6/10/14/18/22 specialized pipelines.
const KERNEL_RADIUS: u32 = 22u;
struct VSOut {
  @builtin(position) pos: vec4f,
  @location(0) uv: vec2f,
};
struct BlurUniforms {
  direction: vec2f,
  invSize: vec2f,
  gaussianCoefficients0: vec4f,
  gaussianCoefficients1: vec4f,
  gaussianCoefficients2: vec4f,
  gaussianCoefficients3: vec4f,
  gaussianCoefficients4: vec4f,
  gaussianCoefficients5: vec4f,
};
@group(0) @binding(0) var<uniform> uniforms: BlurUniforms;
@group(0) @binding(1) var colorTexture: texture_2d<f32>;
@group(0) @binding(2) var linearSampler: sampler;
 
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
  var p = array<vec2f, 3>(vec2f(-1.0, -3.0), vec2f(-1.0, 1.0), vec2f(3.0, 1.0));
  var out: VSOut;
  out.pos = vec4f(p[vi], 0.0, 1.0);
  out.uv = vec2f(p[vi].x * 0.5 + 0.5, 0.5 - p[vi].y * 0.5);
  return out;
}
 
fn coefficient(i: u32) -> f32 {
  let v = array<f32, 24>(
    uniforms.gaussianCoefficients0.x, uniforms.gaussianCoefficients0.y, uniforms.gaussianCoefficients0.z, uniforms.gaussianCoefficients0.w,
    uniforms.gaussianCoefficients1.x, uniforms.gaussianCoefficients1.y, uniforms.gaussianCoefficients1.z, uniforms.gaussianCoefficients1.w,
    uniforms.gaussianCoefficients2.x, uniforms.gaussianCoefficients2.y, uniforms.gaussianCoefficients2.z, uniforms.gaussianCoefficients2.w,
    uniforms.gaussianCoefficients3.x, uniforms.gaussianCoefficients3.y, uniforms.gaussianCoefficients3.z, uniforms.gaussianCoefficients3.w,
    uniforms.gaussianCoefficients4.x, uniforms.gaussianCoefficients4.y, uniforms.gaussianCoefficients4.z, uniforms.gaussianCoefficients4.w,
    uniforms.gaussianCoefficients5.x, uniforms.gaussianCoefficients5.y, uniforms.gaussianCoefficients5.z, uniforms.gaussianCoefficients5.w
  );
  return v[i];
}
 
@fragment fn fs_main(in: VSOut) -> @location(0) vec4f {
  // UnrealBloomPass._getSeparableBlurMaterial @ three 0.184.0.
  var weightSum = coefficient(0u);
  var diffuseSum = textureSample(colorTexture, linearSampler, in.uv).rgb * weightSum;
  for (var i = 1u; i < KERNEL_RADIUS; i = i + 1u) {
    let x = f32(i);
    let w = coefficient(i);
    let uvOffset = uniforms.direction * uniforms.invSize * x;
    let sample1 = textureSample(colorTexture, linearSampler, in.uv + uvOffset).rgb;
    let sample2 = textureSample(colorTexture, linearSampler, in.uv - uvOffset).rgb;
    diffuseSum = diffuseSum + (sample1 + sample2) * w;
  }
  return vec4f(diffuseSum, 1.0);
}