From 77d6176fd596a28a2a371172ae13022e378e90d9 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Thu, 8 Sep 2022 19:51:39 -0700 Subject: [PATCH] Updating the WebGPU code to match the REGL code --- TODO.txt | 3 - js/webgpu/rainPass.js | 17 +-- shaders/glsl/rainPass.symbol.frag.glsl | 4 +- shaders/wgsl/rainPass.wgsl | 145 +++++++++++++------------ 4 files changed, 85 insertions(+), 84 deletions(-) diff --git a/TODO.txt b/TODO.txt index e6edd6c..b969aab 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,8 +1,5 @@ TODO: -Update WebGPU - Compute shine and symbol in separate methods of compute shader - Reformulate the basis https://buf.com/films/the-matrix-resurrections Base cursors and other colors on BUF clip diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index 4900cd1..7854412 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -1,4 +1,4 @@ -import { structs, byteSizeOf } from "../../lib/gpu-buffer.js"; +import { structs } from "../../lib/gpu-buffer.js"; import { makeRenderTarget, loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; const rippleTypes = { @@ -44,11 +44,6 @@ export default ({ config, device, timeBuffer }) => { // rather than a single quad for our geometry const numQuads = config.volumetric ? numCells : 1; - const cellsBuffer = device.createBuffer({ - size: numCells * byteSizeOf("vec4"), - usage: GPUBufferUsage.STORAGE, - }); - const transform = mat4.create(); if (config.effect === "none") { mat4.rotateX(transform, transform, (Math.PI * 1) / 8); @@ -60,8 +55,9 @@ export default ({ config, device, timeBuffer }) => { } const camera = mat4.create(); - // It's handy to have multiple channels, in case we have - // multiple varieties of code, such as downward and upward flowing + // TODO: vantage points, multiple renders + + // We use the different channels for different parts of the raindrop const renderFormat = "rgba8unorm"; const linearSampler = device.createSampler({ @@ -100,6 +96,11 @@ export default ({ config, device, timeBuffer }) => { const rainShaderUniforms = structs.from(rainShader.code); configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize); + const cellsBuffer = device.createBuffer({ + size: numCells * rainShaderUniforms.Cell.minSize, + usage: GPUBufferUsage.STORAGE, + }); + sceneUniforms = rainShaderUniforms.Scene; sceneBuffer = makeUniformBuffer(device, sceneUniforms); diff --git a/shaders/glsl/rainPass.symbol.frag.glsl b/shaders/glsl/rainPass.symbol.frag.glsl index 7950ee0..e6b1614 100644 --- a/shaders/glsl/rainPass.symbol.frag.glsl +++ b/shaders/glsl/rainPass.symbol.frag.glsl @@ -48,7 +48,7 @@ vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenP resetGlyph = resetGlyph || brightness <= 0.; } if (resetGlyph) { - previousAge = randomFloat(screenPos + vec2(0.5)); + previousAge = randomFloat(screenPos + 0.5); previousSymbol = floor(glyphSequenceLength * randomFloat(screenPos)); } float cycleSpeed = getCycleSpeed(brightness); @@ -61,7 +61,7 @@ vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenP if (cycleStyle == 0) { symbol = mod(symbol + advance, glyphSequenceLength); } else if (cycleStyle == 1 && advance > 0.) { - symbol = floor(glyphSequenceLength * randomFloat(screenPos + vec2(simTime))); + symbol = floor(glyphSequenceLength * randomFloat(screenPos + simTime)); } } diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 0dbc606..24530d1 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -4,7 +4,7 @@ struct Config { // common properties used for compute and rendering animationSpeed : f32, - glyphSequenceLength : i32, + glyphSequenceLength : f32, glyphTextureGridSize : vec2, glyphHeightToWidth : f32, gridSize : vec2, @@ -55,9 +55,14 @@ struct Scene { transform : mat4x4, }; +struct Cell { + shine : vec4, + symbol : vec4, +}; + // The array of cells that the compute shader updates, and the fragment shader draws. struct CellData { - cells: array>, + cells: array, }; // Shared bindings @@ -87,7 +92,7 @@ struct VertOutput { @builtin(position) Position : vec4, @location(0) uv : vec2, @location(1) channel : vec3, - @location(2) glyph : vec4, + @location(2) quadDepth : f32, }; struct FragOutput { @@ -144,12 +149,10 @@ fn getBrightness(rainTime : f32) -> f32 { return value * config.baseContrast + config.baseBrightness; } -fn getCycleSpeed(rainTime : f32, brightness : f32) -> f32 { - var localCycleSpeed = 0.0; +fn getCycleSpeed(brightness : f32) -> f32 { + var localCycleSpeed = 1.0; if (config.cycleStyle == 0 && brightness > 0.0) { localCycleSpeed = pow(1.0 - brightness, 4.0); - } else if (config.cycleStyle == 1) { - localCycleSpeed = fract(rainTime); } return config.animationSpeed * config.cycleSpeed * localCycleSpeed; } @@ -208,26 +211,18 @@ fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2) -> f32 return effect; } -fn applyCursorEffect(effect : f32, brightness : f32) -> f32 { - if (brightness >= config.cursorBrightness) { - return 1.0; - } - return effect; -} - // Compute shader main functions -fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : vec2, screenPos : vec2) -> vec4 { +fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4, previousBelow : vec4) -> vec4 { // Determine the glyph's local time. - var simTime = time.seconds * config.animationSpeed; var rainTime = getRainTime(simTime, glyphPos); // Rain time is the backbone of this effect. // Determine the glyph's brightness. - var previousBrightness = previousResult.r; var brightness = getBrightness(rainTime); + if (bool(config.hasSun)) { brightness = applySunShowerBrightness(brightness, screenPos); } @@ -235,46 +230,52 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve brightness = applyThunderBrightness(brightness, simTime, screenPos); } - // Determine the glyph's cycle— the percent this glyph has progressed through the glyph sequence - var previousCycle = previousResult.g; - var resetGlyph = isFirstFrame; - if (bool(config.loops)) { - resetGlyph = resetGlyph || previousBrightness < 0.0; - } - if (resetGlyph) { - previousCycle = select(randomFloat(screenPos), 0.0, bool(config.showComputationTexture)); - } - var localCycleSpeed = getCycleSpeed(rainTime, brightness); - var cycle = previousCycle; - if (time.frames % config.cycleFrameSkip == 0) { - cycle = fract(previousCycle + 0.005 * localCycleSpeed * f32(config.cycleFrameSkip)); - } - // Determine the glyph's effect— the amount the glyph lights up for other reasons var effect = 0.0; effect = applyRippleEffect(effect, simTime, screenPos); // Round or square ripples across the grid - effect = applyCursorEffect(effect, brightness); // The bright glyphs at the "bottom" of raindrops - // Modes that don't fade glyphs set their actual brightness here - if (config.brightnessOverride > 0.0 && brightness > config.brightnessThreshold) { - brightness = config.brightnessOverride; - } + var previousBrightnessBelow = previousBelow.r; + var cursor = select(0.0, 1.0, brightness > previousBrightnessBelow); // Blend the glyph's brightness with its previous brightness, so it winks on and off organically if (!isFirstFrame) { + var previousBrightness = previous.r; brightness = mix(previousBrightness, brightness, config.brightnessDecay); } - // Determine the glyph depth. This is a static value for each column. - var depth = randomFloat(vec2(screenPos.x, 0.0)); + var result = vec4(brightness, 0.0, cursor, effect); + return result; +} - var result = vec4(brightness, cycle, depth, effect); +fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4, shine : vec4) -> vec4 { - // Better use of the alpha channel, for demonstrating how the glyph cycle works - if (bool(config.showComputationTexture)) { - result.a = min(1.0, localCycleSpeed); + var brightness = shine.r; + + var previousSymbol = previous.r; + var previousAge = previous.g; + var resetGlyph = isFirstFrame; + if (bool(config.loops)) { + resetGlyph = resetGlyph || brightness < 0.0; + } + if (resetGlyph) { + previousAge = randomFloat(screenPos + 0.5); + previousSymbol = floor(config.glyphSequenceLength * randomFloat(screenPos)); + } + var cycleSpeed = getCycleSpeed(brightness); + var age = previousAge; + var symbol = previousSymbol; + if (time.frames % config.cycleFrameSkip == 0) { + age += cycleSpeed * f32(config.cycleFrameSkip); + var advance = floor(age); + age = fract(age); + if (config.cycleStyle == 0) { + symbol = (symbol + advance) % config.glyphSequenceLength; + } else if (config.cycleStyle == 1 && advance > 0.) { + symbol = floor(config.glyphSequenceLength * randomFloat(screenPos + simTime)); + } } + var result = vec4(symbol, age, 0.0, 0.0); return result; } @@ -289,13 +290,19 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve } var i = row * i32(config.gridSize.x) + column; + var below = (row - 1) * i32(config.gridSize.x) + column; + + var simTime = time.seconds * config.animationSpeed; // Update the cell var isFirstFrame = time.frames == 0; var glyphPos = vec2(f32(column), f32(row)); var screenPos = glyphPos / config.gridSize; - var previousResult = cells_RW.cells[i]; - cells_RW.cells[i] = computeResult(isFirstFrame, previousResult, glyphPos, screenPos); + + var cell = cells_RW.cells[i]; + cell.shine = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.shine, cells_RW.cells[below].shine); + cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.shine); + cells_RW.cells[i] = cell; } // Vertex shader @@ -327,14 +334,11 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve // Calculate the vertex's uv var uv = (quadPosition + quadCorner) / quadGridSize; - // Retrieve the quad's glyph data - var glyph = cells_RO.cells[quadIndex]; - - // Calculate the quad's depth + // Determine the quad's depth. This is a static value for each column of quads. var quadDepth = 0.0; if (volumetric) { - quadDepth = fract(glyph.b + time.seconds * config.animationSpeed * config.forwardSpeed); - glyph.b = quadDepth; + var startDepth = randomFloat(vec2(quadPosition.x, 0.0)); + quadDepth = fract(startDepth + time.seconds * config.animationSpeed * config.forwardSpeed); } // Calculate the vertex's world space position @@ -358,7 +362,7 @@ fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : ve screenPosition, uv, channel, - glyph + quadDepth ); } @@ -368,11 +372,9 @@ fn median3(i : vec3) -> f32 { return max(min(i.r, i.g), min(max(i.r, i.g), i.b)); } -fn getSymbolUV(glyphCycle : f32) -> vec2 { - var symbol = i32(f32(config.glyphSequenceLength) * glyphCycle); +fn getSymbolUV(symbol : i32) -> vec2 { var symbolX = symbol % config.glyphTextureGridSize.x; var symbolY = symbol / config.glyphTextureGridSize.x; - // TODO: make sure this is working properly, it had a bug in the GLSL for a while. return vec2(f32(symbolX), f32(symbolY)); } @@ -404,24 +406,25 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { uv.y /= config.glyphHeightToWidth; } - // Retrieve values from the data texture - var glyph : vec4; - if (volumetric) { - glyph = input.glyph; - } else { - var gridCoord : vec2 = vec2(uv * config.gridSize); - var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x; - glyph = cells_RO.cells[gridIndex]; - } - var brightness = glyph.r; - var symbolUV = getSymbolUV(glyph.g); - var quadDepth = glyph.b; - var effect = glyph.a; + // Retrieve cell + var gridCoord : vec2 = vec2(uv * config.gridSize); + var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x; + var cell = cells_RO.cells[gridIndex]; + var symbolUV = getSymbolUV(i32(cell.symbol.r)); + + var brightness = cell.shine.r; + + // Modes that don't fade glyphs set their actual brightness here + if (config.brightnessOverride > 0.0 && brightness > config.brightnessThreshold) { + brightness = config.brightnessOverride; + } + + brightness = max(cell.shine.b * config.cursorBrightness, brightness); + brightness = max(cell.shine.a, brightness); - brightness = max(effect, brightness); // In volumetric mode, distant glyphs are dimmer if (volumetric) { - brightness *= min(1.0, quadDepth); + brightness *= min(1.0, input.quadDepth); } // resolve UV to cropped position of glyph in MSDF texture @@ -440,7 +443,7 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { var output : FragOutput; if (bool(config.showComputationTexture)) { - output.color = vec4(glyph.r - alpha, glyph.g * alpha, glyph.a - alpha, 1.0); + output.color = vec4(cell.shine.r - alpha, cell.shine.g * alpha, cell.shine.a - alpha, 1.0); if (volumetric) { output.color.g *= 0.9 + 0.1; }