From 6460f4401ace869fe431bff0b66bd41ce3adb3a5 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Mon, 1 Nov 2021 01:52:57 -0700 Subject: [PATCH] Ported the rain compute shader to wgsl. Tons of inverted-y errors abound. --- TODO.txt | 4 +- shaders/wgsl/rainPass.wgsl | 188 ++++++++++++++++++++++++++++++++++--- 2 files changed, 178 insertions(+), 14 deletions(-) diff --git a/TODO.txt b/TODO.txt index 8ade7f2..6e28cf7 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,7 +2,9 @@ TODO: WebGPU First decent rainRender - Port compute pass 100% + Fix the rain direction + Fix polar glyph direction + Maybe re-flip everything Render target Resize accordingly Put in its own module diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 02ae58c..3a9fd39 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -116,17 +116,177 @@ fn wobble(x : f32) -> f32 { // 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. +fn getRainTime(simTime : f32, glyphPos : vec2) -> f32 { + var columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.0)) * 1000.0; + var columnSpeedOffset = randomFloat(vec2(glyphPos.x + 0.1, 0.0)) * 0.5 + 0.5; + // columnSpeedOffset = 0.0; // TODO: loop + var columnTime = columnTimeOffset + simTime * config.fallSpeed * columnSpeedOffset; + return (glyphPos.y * 0.01 + columnTime) / config.raindropLength; +} + +fn getBrightness(rainTime : f32) -> f32 { + var value = 1.0 - fract(wobble(rainTime)); + // value = 1.0 - fract(rainTime); // TODO: loop + return log(value * 1.25) * 3.0; +} + +fn getCycleSpeed(rainTime : f32, brightness : f32) -> f32 { + var localCycleSpeed = 0.0; + if (config.cycleStyle == 0 && brightness > 0.0) { + localCycleSpeed = pow(1.0 - brightness, 4.0); + } elseif (config.cycleStyle == 1) { + localCycleSpeed = fract(rainTime); + } + return config.animationSpeed * config.cycleSpeed * localCycleSpeed; +} + +// Additional effects + +fn applySunShowerBrightness(brightness : f32, screenPos : vec2) -> f32 { + if (brightness >= -4.0) { + return pow(fract(brightness * 0.5), 3.0) * screenPos.y * 1.5; + } + return brightness; +} + +fn applyThunderBrightness(brightness : f32, simTime : f32, screenPos : vec2) -> f32 { + var thunderTime = simTime * 0.5; + var thunder = 1.0 - fract(wobble(thunderTime)); + // thunder = 1.0 - fract(thunderTime + 0.3); // TODO: loop + + thunder = log(thunder * 1.5) * 4.0; + thunder = clamp(thunder, 0.0, 1.0); + thunder = thunder * pow(screenPos.y, 2.0) * 3.0; + return brightness + thunder; +} + +fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2) -> f32 { + if (config.rippleType == -1) { + return effect; + } + + var rippleTime = (simTime * 0.5 + sin(simTime) * 0.2) * config.rippleSpeed + 1.0; // TODO: clarify + // rippleTime = (simTime * 0.5) * config.rippleSpeed + 1.0; // TODO: loop + + var offset = randomVec2(vec2(floor(rippleTime), 0.0)) - 0.5; + // offset = vec2(0.0); // TODO: loop + var ripplePos = screenPos * 2.0 - 1.0 + offset; + var rippleDistance : f32; + if (config.rippleType == 0) { + var boxDistance = abs(ripplePos) * vec2(1.0, config.glyphHeightToWidth); + rippleDistance = max(boxDistance.x, boxDistance.y); + } elseif (config.rippleType == 1) { + rippleDistance = length(ripplePos); + } + + var rippleValue = fract(rippleTime) * config.rippleScale - rippleDistance; + + if (rippleValue > 0.0 && rippleValue < config.rippleThickness) { + return effect + 0.75; + } + + return effect; +} + +fn applyCursorEffect(effect : f32, brightness : f32) -> f32 { + if (brightness >= config.cursorEffectThreshold) { + return 1.0; + } + return effect; +} + +// Main function + +fn computeResult (isFirstFrame : bool, previousResult : vec4, glyphPos : vec2, screenPos : vec2) -> 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); + } + if (bool(config.hasThunder)) { + 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; // || previousBrightness <= 0.0; // TODO: loop + if (resetGlyph) { + if (bool(config.showComputationTexture)) { + previousCycle = 0.0; + } else { + previousCycle = randomFloat(screenPos); + } + } + 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; + } + + // Blend the glyph's brightness with its previous brightness, so it winks on and off organically + if (!isFirstFrame) { + 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, cycle, depth, effect); + + // Better use of the blue channel, for demonstrating how the glyph cycle works + if (bool(config.showComputationTexture)) { + result.b = min(1.0, localCycleSpeed); + } + + return result; +} + [[stage(compute), workgroup_size(1, 1, 1)]] fn computeMain(input : ComputeInput) { - var animationSpeed = config.animationSpeed; // TODO: remove - var hasSun = bool(config.hasSun); // TODO: remove - var seconds = time.seconds; // TODO: remove var row = i32(input.id.y); var column = i32(input.id.x); - var i = row * i32(config.gridSize.x); + var i = row * i32(config.gridSize.x) + column; - cellsPing_RW.cells[i] = vec4((1.0 + time.seconds * 0.1) % 1.0, 0.0, 0.0, 0.0); - cellsPong_RW.cells[i] = vec4((0.5 + time.seconds * 0.1) % 1.0, 0.5, 0.0, 0.0); + var isFirstFrame = time.frames == 0; + var glyphPos = vec2(f32(column), f32(row)); + var screenPos = glyphPos / config.gridSize; + var previousResult : vec4; + + if (time.frames % 2 == 0) { + previousResult = cellsPong_RW.cells[i]; + } else { + previousResult = cellsPing_RW.cells[i]; + } + + var result = computeResult(isFirstFrame, previousResult, glyphPos, screenPos); + + if (time.frames % 2 == 0) { + cellsPing_RW.cells[i] = result; + } else { + cellsPong_RW.cells[i] = result; + } } // Vertex shader @@ -159,7 +319,7 @@ fn wobble(x : f32) -> f32 { // Retrieve the quad's glyph data var vGlyph: vec4; - if ((time.frames / 100) % 2 == 0) { + if (time.frames % 2 == 0) { vGlyph = cellsPing_RO.cells[quadIndex]; } else { vGlyph = cellsPong_RO.cells[quadIndex]; @@ -221,9 +381,6 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { [[stage(fragment)]] fn fragMain(input : VertOutput) -> FragOutput { - var firstCellA = cellsPing_RO.cells[0]; // TODO: remove - var firstCellB = cellsPong_RO.cells[0]; // TODO: remove - var volumetric = bool(config.volumetric); var uv = input.uv; @@ -235,7 +392,7 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { uv.y = uv.y + 0.5; var radius = length(uv); var angle = atan2(uv.y, uv.x) / (2.0 * PI) + 0.5; - uv = -vec2(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius))); + uv = vec2(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius))); } else { // Apply the slant and a scale to space so the viewport is still fully covered by the geometry uv = vec2( @@ -251,9 +408,14 @@ fn getSymbolUV(glyphCycle : f32) -> vec2 { if (volumetric) { glyph = input.glyph; } else { - glyph = vec4(1.0); // TODO : texture2D(state, uv); + var gridCoord : vec2 = vec2(uv * config.gridSize); + var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x; + if (time.frames % 2 == 0) { + glyph = cellsPing_RO.cells[gridIndex]; + } else { + glyph = cellsPong_RO.cells[gridIndex]; + } } - glyph = input.glyph; // TODO : remove var brightness = glyph.r; var symbolUV = getSymbolUV(glyph.g); var quadDepth = glyph.b;