diff --git a/README.md b/README.md index b13e8af..5a1481c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The following the criteria that guided the development process: - **Make it look sweet in 3D, too.** To facilitate future support of stereoscopic and holographic displays, it made sense to nail down a 3D variation, but it looks pretty on any kind of display. - **The 2D glyphs are in a *fixed grid* and *don't move*.** The "raindrops" we see in the effect are simply waves of illumination of stationary symbols that occupy a column. To get a better look at this, try setting the `fallSpeed` to a number close to 0. - **Get the glow and color right.** Matrix symbols aren't just some shade of phosphorous green; they're first given a bloom effect, and then get tone-mapped to the green color palette. -- **Multiple "raindrops" often occupy the same column.** This is complicated, because we can't allow them to collide. A useful approach to thinking about this is, each column's glyph brightness is a kind of [sawtooth wave](http://mathworld.wolfram.com/SawtoothWave.html), where the width of the teeth subtly fluctuate to keep things interesting. +- **Multiple "raindrops" often occupy the same column.** This is complicated, because we can't allow them to collide. The solution in this project is to model each column's glyph brightness is a kind of [sawtooth wave](http://mathworld.wolfram.com/SawtoothWave.html), where the width of the teeth subtly fluctuate to keep things interesting. The tips of those teeth— the cells in the grid where the sawtooth dips— are where we put the "cursors" (or "tracers") at the bottom of each raindrop. - **Capture the glyph cycling sequence.** The symbols in the sequels' opening titles, which are arguably the highest quality versions of the 2D effect, change according to a repeating sequence (see `glyph order.txt`). This is only a technical detail, and only applies to *Reloaded* and *Revolutions*— everyplace else, the symbols change randomly. - **Whip up some artistic license and depict the *previous* Matrix versions.** The sequels describe [a paradisiacal predecessor](https://rezmason.github.io/matrix?version=paradise) to the Matrix that was too idyllic, [and another earlier, nightmarish Hobbesian version](https://rezmason.github.io/matrix?version=nightmare) that proved too campy. They depict some programs running older, differently colored code, so it's time someone tried rendering them. - **Heck, try building some homemade varieties that have nothing to do with the franchise.** See the list of links above for the full set of available versions. diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index 29a548b..31e4f6d 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -55,20 +55,20 @@ export default ({ regl, config, lkg }) => { showDebugView, }; - const shineDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); - const rainPassShine = loadText("shaders/glsl/rainPass.shine.frag.glsl"); - const shineUniforms = { + const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); + const rainPassShine = loadText("shaders/glsl/rainPass.raindrop.frag.glsl"); + const raindropUniforms = { ...commonUniforms, ...extractEntries(config, ["brightnessDecay", "fallSpeed", "raindropLength", "loops"]), }; - const shine = regl({ + const raindrop = regl({ frag: regl.prop("frag"), uniforms: { - ...shineUniforms, - previousShineState: shineDoubleBuffer.back, + ...raindropUniforms, + previousShineState: raindropDoubleBuffer.back, }, - framebuffer: shineDoubleBuffer.front, + framebuffer: raindropDoubleBuffer.front, }); const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); @@ -81,7 +81,7 @@ export default ({ regl, config, lkg }) => { frag: regl.prop("frag"), uniforms: { ...symbolUniforms, - shineState: shineDoubleBuffer.front, + raindropState: raindropDoubleBuffer.front, previousSymbolState: symbolDoubleBuffer.back, }, @@ -99,7 +99,7 @@ export default ({ regl, config, lkg }) => { frag: regl.prop("frag"), uniforms: { ...effectUniforms, - shineState: shineDoubleBuffer.front, + raindropState: raindropDoubleBuffer.front, previousEffectState: effectDoubleBuffer.back, }, @@ -156,7 +156,7 @@ export default ({ regl, config, lkg }) => { uniforms: { ...renderUniforms, - shineState: shineDoubleBuffer.front, + raindropState: raindropDoubleBuffer.front, symbolState: symbolDoubleBuffer.front, effectState: effectDoubleBuffer.front, glyphTex: msdf.texture, @@ -251,7 +251,7 @@ export default ({ regl, config, lkg }) => { [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; }, () => { - shine({ frag: rainPassShine.text() }); + raindrop({ frag: rainPassShine.text() }); symbol({ frag: rainPassSymbol.text() }); effect({ frag: rainPassEffect.text() }); regl.clear({ diff --git a/shaders/glsl/rainPass.frag.glsl b/shaders/glsl/rainPass.frag.glsl index 2f461a4..867a741 100644 --- a/shaders/glsl/rainPass.frag.glsl +++ b/shaders/glsl/rainPass.frag.glsl @@ -4,7 +4,7 @@ #endif precision lowp float; -uniform sampler2D shineState, symbolState, effectState; +uniform sampler2D raindropState, symbolState, effectState; uniform float numColumns, numRows; uniform sampler2D glyphTex; uniform float glyphHeightToWidth, glyphSequenceLength, glyphEdgeCrop; @@ -106,21 +106,21 @@ void main() { vec2 uv = getUV(vUV); // Unpack the values from the data textures - vec4 shineData = volumetric ? vShine : texture2D( shineState, uv); + vec4 raindropData = volumetric ? vShine : texture2D( raindropState, uv); vec4 symbolData = volumetric ? vSymbol : texture2D(symbolState, uv); vec4 effectData = volumetric ? vEffect : texture2D(effectState, uv); - vec2 brightness = getBrightness(shineData.r, shineData.g, effectData.r, effectData.g); + vec2 brightness = getBrightness(raindropData.r, raindropData.g, effectData.r, effectData.g); float symbol = getSymbol(uv, symbolData.r); if (showDebugView) { gl_FragColor = vec4( vec3( - shineData.g, + raindropData.g, vec2( - 1.0 - (shineData.r * 3.0), - 1.0 - (shineData.r * 8.0) - ) * (1.0 - shineData.g) + 1.0 - (raindropData.r * 3.0), + 1.0 - (raindropData.r * 8.0) + ) * (1.0 - raindropData.g) ) * symbol, 1. ); diff --git a/shaders/glsl/rainPass.shine.frag.glsl b/shaders/glsl/rainPass.raindrop.frag.glsl similarity index 93% rename from shaders/glsl/rainPass.shine.frag.glsl rename to shaders/glsl/rainPass.raindrop.frag.glsl index e63f670..df884e7 100644 --- a/shaders/glsl/rainPass.shine.frag.glsl +++ b/shaders/glsl/rainPass.raindrop.frag.glsl @@ -45,7 +45,7 @@ float wobble(float x) { // This is the code rain's key underlying concept. // It's why glyphs that share a column are lit simultaneously, and are brighter toward the bottom. // It's also why those bright areas are truncated into raindrops. -float getBrightness(float simTime, vec2 glyphPos) { +float getRainBrightness(float simTime, vec2 glyphPos) { float columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.)) * 1000.; float columnSpeedOffset = randomFloat(vec2(glyphPos.x + 0.1, 0.)) * 0.5 + 0.5; if (loops) { @@ -62,8 +62,8 @@ float getBrightness(float simTime, vec2 glyphPos) { // Main function vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) { - float brightness = getBrightness(simTime, glyphPos); - float brightnessBelow = getBrightness(simTime, glyphPos + vec2(0., -1.)); + float brightness = getRainBrightness(simTime, glyphPos); + float brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.)); float cursor = brightness < brightnessBelow ? 1.0 : 0.0; // Blend the glyph's brightness with its previous brightness, so it winks on and off organically diff --git a/shaders/glsl/rainPass.symbol.frag.glsl b/shaders/glsl/rainPass.symbol.frag.glsl index 87eb849..93ba162 100644 --- a/shaders/glsl/rainPass.symbol.frag.glsl +++ b/shaders/glsl/rainPass.symbol.frag.glsl @@ -9,7 +9,7 @@ precision highp float; #define PI 3.14159265359 -uniform sampler2D previousSymbolState, shineState; +uniform sampler2D previousSymbolState, raindropState; uniform float numColumns, numRows; uniform float time, tick, cycleFrameSkip; uniform float animationSpeed, cycleSpeed; @@ -26,13 +26,13 @@ highp float randomFloat( const in vec2 uv ) { // Main function -vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous, vec4 shine) { +vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous, vec4 raindrop) { float previousSymbol = previous.r; float previousAge = previous.g; bool resetGlyph = isFirstFrame; if (loops) { - resetGlyph = resetGlyph || shine.r <= 0.; + resetGlyph = resetGlyph || raindrop.r <= 0.; } if (resetGlyph) { previousAge = randomFloat(screenPos + 0.5); @@ -60,6 +60,6 @@ void main() { vec2 glyphPos = gl_FragCoord.xy; vec2 screenPos = glyphPos / vec2(numColumns, numRows); vec4 previous = texture2D( previousSymbolState, screenPos ); - vec4 shine = texture2D( shineState, screenPos ); - gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous, shine); + vec4 raindrop = texture2D( raindropState, screenPos ); + gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous, raindrop); } diff --git a/shaders/glsl/rainPass.vert.glsl b/shaders/glsl/rainPass.vert.glsl index 07537c9..04a00ff 100644 --- a/shaders/glsl/rainPass.vert.glsl +++ b/shaders/glsl/rainPass.vert.glsl @@ -1,7 +1,7 @@ #define PI 3.14159265359 precision lowp float; attribute vec2 aPosition, aCorner; -uniform sampler2D shineState, symbolState, effectState; +uniform sampler2D raindropState, symbolState, effectState; uniform float density; uniform vec2 quadSize; uniform float glyphHeightToWidth, glyphVerticalSpacing; @@ -22,7 +22,7 @@ highp float rand( const in vec2 uv ) { void main() { vUV = (aPosition + aCorner) * quadSize; - vShine = texture2D(shineState, aPosition * quadSize); + vShine = texture2D(raindropState, aPosition * quadSize); vSymbol = texture2D(symbolState, aPosition * quadSize); vEffect = texture2D(effectState, aPosition * quadSize); diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 711a037..36ca8ac 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -56,7 +56,7 @@ struct Scene { }; struct Cell { - shine : vec4, + raindrop : vec4, symbol : vec4, }; @@ -246,9 +246,9 @@ fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scree return result; } -fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4, shine : vec4) -> vec4 { +fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2, screenPos : vec2, previous : vec4, raindrop : vec4) -> vec4 { - var brightness = shine.r; + var brightness = raindrop.r; var previousSymbol = previous.r; var previousAge = previous.g; @@ -298,8 +298,8 @@ fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2, scre var screenPos = glyphPos / config.gridSize; var cell = cells_RW.cells[i]; - cell.shine = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.shine); - cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.shine); + cell.raindrop = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.raindrop); + cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.raindrop); cells_RW.cells[i] = cell; } @@ -407,15 +407,15 @@ fn getSymbolUV(symbol : i32) -> vec2 { var cell = cells_RO.cells[gridIndex]; var symbolUV = getSymbolUV(i32(cell.symbol.r)); - var brightness = cell.shine.r; + var brightness = cell.raindrop.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(cell.raindrop.b * config.cursorBrightness, brightness); + brightness = max(cell.raindrop.a, brightness); // In volumetric mode, distant glyphs are dimmer if (volumetric) { @@ -440,11 +440,11 @@ fn getSymbolUV(symbol : i32) -> vec2 { if (bool(config.showDebugView)) { output.color = vec4( vec3( - cell.shine.b, + cell.raindrop.b, vec2( - 1.0 - (cell.shine.g * 3.0), - 1.0 - (cell.shine.g * 10.0) - ) * (1.0 - cell.shine.b) + 1.0 - (cell.raindrop.g * 3.0), + 1.0 - (cell.raindrop.g * 10.0) + ) * (1.0 - cell.raindrop.b) ) * alpha, 1. );