diff --git a/TODO.txt b/TODO.txt index b85c909..ce244c2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -8,6 +8,8 @@ WebGPU Try to change post processing to compute shaders once they're easier to support std140 + Expand it to add array support + Contemplate adding struct support Document and share it diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index a76e187..9dd720d 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -16,6 +16,7 @@ const cycleStyles = { const numVerticesPerQuad = 2 * 3; const makeConfigBuffer = (device, config, density, gridSize) => { + // Various effect-related values const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1; const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0; const slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; @@ -178,7 +179,7 @@ export default (context, getInputs) => { })(); const setSize = (width, height) => { - // Update scene buffer + // Update scene buffer: camera and transform math for the volumetric mode const aspectRatio = width / height; mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; @@ -194,6 +195,8 @@ export default (context, getInputs) => { }); const execute = (encoder) => { + // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen + const computePass = encoder.beginComputePass(); computePass.setPipeline(computePipeline); computePass.setBindGroup(0, computeBindGroup); diff --git a/shaders/glsl/rainPass.frag.glsl b/shaders/glsl/rainPass.frag.glsl index 60a175e..52ebe8b 100644 --- a/shaders/glsl/rainPass.frag.glsl +++ b/shaders/glsl/rainPass.frag.glsl @@ -79,7 +79,7 @@ void main() { float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0); if (showComputationTexture) { - gl_FragColor = vec4(glyph.rgb * alpha, 1.0); + gl_FragColor = vec4(glyph.rgb - alpha, 1.0); } else { gl_FragColor = vec4(vChannel * brightness * alpha, 1.0); } diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index a7b1267..f057d16 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -1,5 +1,8 @@ +// This shader module is the star of the show. +// It is where the cell states update and the symbols get drawn to the screen. + [[block]] struct Config { - // common + // common properties used for compute and rendering animationSpeed : f32; glyphSequenceLength : i32; glyphTextureColumns : i32; @@ -8,7 +11,7 @@ gridSize : vec2; showComputationTexture : i32; - // compute + // compute-specific properties brightnessThreshold : f32; brightnessOverride : f32; brightnessDecay : f32; @@ -25,7 +28,7 @@ cycleStyle : i32; rippleType : i32; - // render + // render-specific properties forwardSpeed : f32; glyphVerticalSpacing : f32; glyphEdgeCrop : f32; @@ -36,17 +39,20 @@ volumetric : i32; }; +// The properties that change over time get their own buffer. [[block]] struct Time { seconds : f32; frames : i32; }; +// The properties related to the size of the canvas get their own buffer. [[block]] struct Scene { screenSize : vec2; camera : mat4x4; transform : mat4x4; }; +// The array of cells that the compute shader updates, and the fragment shader draws. [[block]] struct CellData { cells: array>; }; @@ -55,10 +61,10 @@ [[group(0), binding(0)]] var config : Config; [[group(0), binding(1)]] var time : Time; -// Compute bindings +// Compute-specific bindings [[group(0), binding(2)]] var cells_RW : CellData; -// Render bindings +// Render-specific bindings [[group(0), binding(2)]] var scene : Scene; [[group(0), binding(3)]] var linearSampler : sampler; [[group(0), binding(4)]] var msdfTexture : texture_2d; @@ -112,9 +118,7 @@ fn wobble(x : f32) -> f32 { return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x); } -// Compute shader - -// Core functions +// Compute shader core functions // Rain time is the shader's key underlying concept. // It's why glyphs that share a column are lit simultaneously, and are brighter toward the bottom. @@ -142,7 +146,7 @@ fn getCycleSpeed(rainTime : f32, brightness : f32) -> f32 { return config.animationSpeed * config.cycleSpeed * localCycleSpeed; } -// Additional effects +// Compute shader additional effects fn applySunShowerBrightness(brightness : f32, screenPos : vec2) -> f32 { if (brightness >= -4.0) { @@ -197,7 +201,7 @@ fn applyCursorEffect(effect : f32, brightness : f32) -> f32 { return effect; } -// Main function +// Compute shader main functions fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : vec2, screenPos : vec2) -> vec4 { @@ -263,6 +267,7 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve [[stage(compute), workgroup_size(32, 1, 1)]] fn computeMain(input : ComputeInput) { + // Resolve the invocation ID to a single cell var row = i32(input.id.y); var column = i32(input.id.x); @@ -272,6 +277,7 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve var i = row * i32(config.gridSize.x) + column; + // Update the cell var isFirstFrame = time.frames == 0; var glyphPos = vec2(f32(column), f32(row)); var screenPos = glyphPos / config.gridSize; @@ -348,7 +354,7 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve ); } -// Fragment shader +// Fragment shader core functions fn median3(i : vec3) -> f32 { return max(min(i.r, i.g), min(max(i.r, i.g), i.b)); @@ -361,6 +367,8 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { return vec2(f32(symbolX), f32(symbolY)); } +// Fragment shader + [[stage(fragment)]] fn fragMain(input : VertOutput) -> FragOutput { var volumetric = bool(config.volumetric); @@ -423,7 +431,7 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { var output : FragOutput; if (bool(config.showComputationTexture)) { - output.color = vec4(glyph.rgb * alpha, 1.0); + output.color = vec4(glyph.rgb - alpha, 1.0); } else { output.color = vec4(input.channel * brightness * alpha, 1.0); }