diff --git a/TODO.txt b/TODO.txt index b969aab..e94841a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,9 +1,13 @@ TODO: +Create a Resurrections font + Icomoon + Unicode PUA + Reformulate the basis https://buf.com/films/the-matrix-resurrections Base cursors and other colors on BUF clip - Show this stuff for showComputationTexture + Show this stuff for showDebugView Pixel grill? Tune the colors Maybe glow can be an SDF-derived effect instead, look into it @@ -29,10 +33,6 @@ Playdate version https://sdk.play.date/1.12.3/Inside%20Playdate.html#pdxinfo Menu later? -Create a Resurrections font - Icomoon - Unicode PUA - Resurrections Support anomaly streaks MSDF diff --git a/js/config.js b/js/config.js index 5178a57..18c5b60 100644 --- a/js/config.js +++ b/js/config.js @@ -96,6 +96,7 @@ const defaults = { resolution: 0.75, // An overall scale multiplier useHalfFloat: false, renderer: "webgpu", // The preferred web graphics API + isometric: false, useHoloplay: false, loops: false, }; @@ -308,6 +309,7 @@ const paramMapping = { loops: { key: "loops", parser: (s) => s.toLowerCase().includes("true") }, renderer: { key: "renderer", parser: (s) => s }, once: { key: "once", parser: (s) => s.toLowerCase().includes("true") }, + isometric: { key: "isometric", parser: (s) => s.toLowerCase().includes("true") }, }; paramMapping.dropLength = paramMapping.raindropLength; paramMapping.angle = paramMapping.slant; diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index ca5fa7c..64b3870 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -37,13 +37,13 @@ export default ({ regl, config, lkg }) => { const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0; const slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); - const showComputationTexture = config.effect === "none"; + const showDebugView = config.effect === "none"; const commonUniforms = { ...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureGridSize"]), numColumns, numRows, - showComputationTexture, + showDebugView, }; // These two framebuffers are used to compute the raining code. @@ -182,7 +182,7 @@ export default ({ regl, config, lkg }) => { const screenSize = [1, 1]; const { mat4, vec3 } = glMatrix; const transform = mat4.create(); - if (config.effect === "none") { + if (volumetric && config.isometric) { mat4.rotateX(transform, transform, (Math.PI * 1) / 8); mat4.rotateY(transform, transform, (Math.PI * 1) / 4); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); @@ -217,7 +217,7 @@ export default ({ regl, config, lkg }) => { const index = column + row * numTileColumns; const camera = mat4.create(); - if (config.effect === "none") { + if (volumetric && config.isometric) { if (aspectRatio > 1) { mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000); } else { diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index 7854412..2b6d795 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -18,7 +18,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => ...config, gridSize, density, - showComputationTexture: config.effect === "none", + showDebugView: config.effect === "none", cycleStyle: config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0, rippleType: config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1, slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1), @@ -45,7 +45,7 @@ export default ({ config, device, timeBuffer }) => { const numQuads = config.volumetric ? numCells : 1; const transform = mat4.create(); - if (config.effect === "none") { + if (config.volumetric && config.isometric) { mat4.rotateX(transform, transform, (Math.PI * 1) / 8); mat4.rotateY(transform, transform, (Math.PI * 1) / 4); mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); @@ -153,7 +153,7 @@ export default ({ config, device, timeBuffer }) => { const build = (size) => { // Update scene buffer: camera and transform math for the volumetric mode const aspectRatio = size[0] / size[1]; - if (config.effect === "none") { + if (config.volumetric && config.isometric) { if (aspectRatio > 1) { mat4.orthoZO(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000); } else { diff --git a/shaders/glsl/rainPass.frag.glsl b/shaders/glsl/rainPass.frag.glsl index 3f12e71..4a11c46 100644 --- a/shaders/glsl/rainPass.frag.glsl +++ b/shaders/glsl/rainPass.frag.glsl @@ -13,7 +13,7 @@ uniform vec2 glyphTextureGridSize; uniform vec2 slantVec; uniform float slantScale; uniform bool isPolar; -uniform bool showComputationTexture; +uniform bool showDebugView; uniform bool volumetric; varying vec2 vUV; @@ -76,7 +76,7 @@ void main() { brightness = max(shine.b * cursorBrightness, brightness); brightness = max(shine.a, brightness); // In volumetric mode, distant glyphs are dimmer - if (volumetric) { + if (volumetric && !showDebugView) { brightness = brightness * min(1., vDepth); } @@ -92,12 +92,18 @@ void main() { float sigDist = median3(dist) - 0.5; float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0., 1.); - if (showComputationTexture) { - vec4 debugColor = vec4(shine.r - alpha, shine.g * alpha, shine.a - alpha, 1.); - if (volumetric) { - debugColor.g = debugColor.g * 0.9 + 0.1; - } - gl_FragColor = debugColor; + if (showDebugView) { + brightness *= 2.; + gl_FragColor = vec4( + vec3( + shine.b, + vec2( + brightness, + clamp(0., 1., pow(brightness * 0.9, 6.0)) + ) * (1.0 - shine.b) + ) * alpha, + 1. + ); } else { gl_FragColor = vec4(vChannel * brightness * alpha, 1.); } diff --git a/shaders/glsl/rainPass.shine.frag.glsl b/shaders/glsl/rainPass.shine.frag.glsl index e0ef010..8b28668 100644 --- a/shaders/glsl/rainPass.shine.frag.glsl +++ b/shaders/glsl/rainPass.shine.frag.glsl @@ -55,11 +55,11 @@ float getRainTime(float simTime, vec2 glyphPos) { columnSpeedOffset = 0.5; } float columnTime = columnTimeOffset + simTime * fallSpeed * columnSpeedOffset; - return (glyphPos.y * 0.01 + columnTime) / raindropLength; + return wobble((glyphPos.y * 0.01 + columnTime) / raindropLength); } float getBrightness(float rainTime) { - float value = 1. - fract(wobble(rainTime)); + float value = 1. - fract(rainTime); if (loops) { value = 1. - fract(rainTime); } @@ -122,10 +122,12 @@ float applyRippleEffect(float effect, float simTime, vec2 screenPos) { // Main function -vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous, vec4 previousBelow) { +vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) { // Determine the glyph's local time. float rainTime = getRainTime(simTime, glyphPos); + float rainTimeBelow = getRainTime(simTime, glyphPos + vec2(0., -1.)); + float cursor = fract(rainTime) < fract(rainTimeBelow) ? 1.0 : 0.0; // Rain time is the backbone of this effect. @@ -139,9 +141,6 @@ vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenP float effect = 0.; effect = applyRippleEffect(effect, simTime, screenPos); // Round or square ripples across the grid - float previousBrightnessBelow = previousBelow.r; - float cursor = brightness > previousBrightnessBelow ? 1.0 : 0.0; - // Blend the glyph's brightness with its previous brightness, so it winks on and off organically if (!isFirstFrame) { float previousBrightness = previous.r; @@ -158,6 +157,5 @@ void main() { vec2 glyphPos = gl_FragCoord.xy; vec2 screenPos = glyphPos / vec2(numColumns, numRows); vec4 previous = texture2D( previousShineState, screenPos ); - vec4 previousBelow = texture2D( previousShineState, screenPos + vec2(0., -1. / numRows)); - gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous, previousBelow); + gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous); } diff --git a/shaders/glsl/rainPass.symbol.frag.glsl b/shaders/glsl/rainPass.symbol.frag.glsl index e6b1614..3cecf59 100644 --- a/shaders/glsl/rainPass.symbol.frag.glsl +++ b/shaders/glsl/rainPass.symbol.frag.glsl @@ -13,7 +13,7 @@ uniform sampler2D previousSymbolState, shineState; uniform float numColumns, numRows; uniform float time, tick, cycleFrameSkip; uniform float animationSpeed, cycleSpeed; -uniform bool loops, showComputationTexture; +uniform bool loops, showDebugView; uniform float glyphSequenceLength; uniform int cycleStyle; diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 24530d1..12baea3 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -8,7 +8,7 @@ struct Config { glyphTextureGridSize : vec2, glyphHeightToWidth : f32, gridSize : vec2, - showComputationTexture : i32, + showDebugView : i32, // compute-specific properties brightnessThreshold : f32, @@ -138,11 +138,11 @@ fn getRainTime(simTime : f32, glyphPos : vec2) -> f32 { columnSpeedOffset = 0.5; } var columnTime = columnTimeOffset + simTime * config.fallSpeed * columnSpeedOffset; - return (glyphPos.y * 0.01 + columnTime) / config.raindropLength; + return wobble((glyphPos.y * 0.01 + columnTime) / config.raindropLength); } fn getBrightness(rainTime : f32) -> f32 { - var value = 1.0 - fract(wobble(rainTime)); + var value = 1.0 - fract(rainTime); if (bool(config.loops)) { value = 1.0 - fract(rainTime); } @@ -213,10 +213,12 @@ fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2) -> f32 // Compute shader main functions -fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4, previousBelow : vec4) -> vec4 { +fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4) -> vec4 { // Determine the glyph's local time. var rainTime = getRainTime(simTime, glyphPos); + var rainTimeBelow = getRainTime(simTime, glyphPos + vec2(0., -1.)); + var cursor = select(0.0, 1.0, fract(rainTime) < fract(rainTimeBelow)); // Rain time is the backbone of this effect. @@ -234,8 +236,6 @@ fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scree var effect = 0.0; effect = applyRippleEffect(effect, simTime, screenPos); // Round or square ripples across the grid - 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) { @@ -290,17 +290,16 @@ fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scre } var i = row * i32(config.gridSize.x) + column; - var below = (row - 1) * i32(config.gridSize.x) + column; var simTime = time.seconds * config.animationSpeed; + var isFirstFrame = time.frames == 0; // Update the cell - var isFirstFrame = time.frames == 0; var glyphPos = vec2(f32(column), f32(row)); var screenPos = glyphPos / config.gridSize; var cell = cells_RW.cells[i]; - cell.shine = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.shine, cells_RW.cells[below].shine); + cell.shine = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.shine); cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.shine); cells_RW.cells[i] = cell; } @@ -442,11 +441,18 @@ fn getSymbolUV(symbol : i32) -> vec2 { var output : FragOutput; - if (bool(config.showComputationTexture)) { - 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; - } + if (bool(config.showDebugView)) { + brightness *= 2.0; + output.color = vec4( + vec3( + cell.shine.b, + vec2( + brightness, + clamp(0.0, 1.0, pow(brightness * 0.9, 6.0)) + ) * (1.0 - cell.shine.b) + ) * alpha, + 1.0 + ); } else { output.color = vec4(input.channel * brightness * alpha, 1.0); }