Adding intro and skipIntro option

This commit is contained in:
Rezmason
2022-09-24 06:47:16 -07:00
parent 4ed481c8b5
commit 965e21d3ba
8 changed files with 159 additions and 26 deletions

View File

@@ -24,7 +24,8 @@
- [Trans flag colors](https://rezmason.github.io/matrix/?effect=trans)
- [Custom stripes (`colors=R,G,B,R,G,B,R,G,B, etc`)](https://rezmason.github.io/matrix/?effect=customStripes&colors=1,0,0,1,1,0,0,1,0)
- [Custom image (`url=www.website.com/picture.jpg`)](https://rezmason.github.io/matrix/?effect=image&url=https://upload.wikimedia.org/wikipedia/commons/f/f5/EagleRock.jpg)
- [Raw compute texture (`effect=none`) (_epilepsy warning_: lots of flickering)](https://rezmason.github.io/matrix/?effect=none)
- [Debug view (`effect=none`) (_epilepsy warning_: this once had lots of flickering)](https://rezmason.github.io/matrix/?effect=none)
- [Starting from a blank screen](https://rezmason.github.io/matrix/?skipIntro=false) (which some people really like, but isn't the default mode)
- [The free classic font (TrueType).](https://github.com/Rezmason/matrix/raw/master/assets/Matrix-Code.ttf)
- [The free *Resurrections* font (TrueType).](https://github.com/Rezmason/matrix/raw/master/assets/Matrix-Resurrected.ttf)
@@ -87,6 +88,7 @@ Now you know link fu. Here's a list of customization options:
- "paradise" is how the Matrix's idyllic predecessor may have appeared: warm, simplistic, encompassing.
- "resurrections" is the updated Matrix code
- "palimpsest" is a custom version inspired by the art and sound of [Rob Dougan](https://en.wikipedia.org/wiki/Rob_Dougan)'s [Furious Angels](https://en.wikipedia.org/wiki/Furious_Angels).
- **skipIntro** - whether or not to start from a blank screen. Can be "true" or "false", default is *true*.
- **font** - the set of glyphs to draw. Current options are "matrixcode", "resurrections", "gothic", "coptic", "huberfishA", and "huberfishD".
- **width** - the number of columns (and rows) to draw. Default is 80.
- **volumetric** - when set to "true", this renders the glyphs with depth, slowly approaching the eye. Default is "false".

View File

@@ -112,6 +112,7 @@ const defaults = {
isometric: false,
useHoloplay: false,
loops: false,
skipIntro: true,
};
const versions = {
@@ -410,6 +411,7 @@ const paramMapping = {
glintColor: { key: "glintColor", parser: (s) => s.split(",").map(parseFloat) },
volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") },
loops: { key: "loops", parser: (s) => s.toLowerCase().includes("true") },
skipIntro: { key: "skipIntro", 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") },

View File

@@ -55,17 +55,34 @@ export default ({ regl, config, lkg }) => {
showDebugView,
};
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
const rainPassIntro = loadText("shaders/glsl/rainPass.intro.frag.glsl");
const introUniforms = {
...commonUniforms,
...extractEntries(config, ["fallSpeed", "skipIntro"]),
};
const intro = regl({
frag: regl.prop("frag"),
uniforms: {
...introUniforms,
previousIntroState: introDoubleBuffer.back,
},
framebuffer: introDoubleBuffer.front,
});
const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const rainPassShine = loadText("shaders/glsl/rainPass.raindrop.frag.glsl");
const rainPassRaindrop = loadText("shaders/glsl/rainPass.raindrop.frag.glsl");
const raindropUniforms = {
...commonUniforms,
...extractEntries(config, ["brightnessDecay", "fallSpeed", "raindropLength", "loops"]),
...extractEntries(config, ["brightnessDecay", "fallSpeed", "raindropLength", "loops", "skipIntro"]),
};
const raindrop = regl({
frag: regl.prop("frag"),
uniforms: {
...raindropUniforms,
previousShineState: raindropDoubleBuffer.back,
introState: introDoubleBuffer.front,
previousRaindropState: raindropDoubleBuffer.back,
},
framebuffer: raindropDoubleBuffer.front,
@@ -217,7 +234,8 @@ export default ({ regl, config, lkg }) => {
glintMSDF.loaded,
baseTexture.loaded,
glintTexture.loaded,
rainPassShine.loaded,
rainPassIntro.loaded,
rainPassRaindrop.loaded,
rainPassSymbol.loaded,
rainPassVert.loaded,
rainPassFrag.loaded,
@@ -271,7 +289,8 @@ export default ({ regl, config, lkg }) => {
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
},
() => {
raindrop({ frag: rainPassShine.text() });
intro({ frag: rainPassIntro.text() });
raindrop({ frag: rainPassRaindrop.text() });
symbol({ frag: rainPassSymbol.text() });
effect({ frag: rainPassEffect.text() });
regl.clear({

View File

@@ -96,6 +96,11 @@ export default ({ config, device, timeBuffer }) => {
const rainShaderUniforms = structs.from(rainShader.code);
configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize);
const introCellsBuffer = device.createBuffer({
size: gridSize[0] * rainShaderUniforms.IntroCell.minSize,
usage: GPUBufferUsage.STORAGE,
});
const cellsBuffer = device.createBuffer({
size: numCells * rainShaderUniforms.Cell.minSize,
usage: GPUBufferUsage.STORAGE,
@@ -148,7 +153,7 @@ export default ({ config, device, timeBuffer }) => {
}),
]);
computeBindGroup = makeBindGroup(device, computePipeline, 0, [configBuffer, timeBuffer, cellsBuffer]);
computeBindGroup = makeBindGroup(device, computePipeline, 0, [configBuffer, timeBuffer, cellsBuffer, introCellsBuffer]);
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [
configBuffer,
timeBuffer,

View File

@@ -61,7 +61,7 @@ vec2 getUV(vec2 uv) {
vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth, vec2 uv) {
float base = raindrop.r;
float base = raindrop.r + max(0., 1.0 - raindrop.a * 5.0);
bool isCursor = bool(raindrop.g) && isolateCursor;
float glint = base;
float multipliedEffects = effect.r;
@@ -94,7 +94,7 @@ vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth, vec2 uv) {
return vec3(
(isCursor ? vec2(0.0, 1.0) : vec2(1.0, 0.0)) * base,
glint
);
) * raindrop.b;
}
vec2 getSymbolUV(float index) {

View File

@@ -0,0 +1,59 @@
precision highp float;
// This shader governs the "intro"— the initial stream of rain from a blank screen.
// It writes falling rain to the channels of a data texture:
// R: raindrop length
// G: unused
// B: unused
// A: unused
#define PI 3.14159265359
#define SQRT_2 1.4142135623730951
#define SQRT_5 2.23606797749979
uniform sampler2D previousIntroState;
uniform float numColumns, numRows;
uniform float time, tick;
uniform float animationSpeed, fallSpeed;
uniform bool skipIntro;
// Helper functions for generating randomness, borrowed from elsewhere
highp float randomFloat( const in vec2 uv ) {
const highp float a = 12.9898, b = 78.233, c = 43758.5453;
highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );
return fract(sin(sn) * c);
}
vec2 randomVec2( const in vec2 uv ) {
return fract(vec2(sin(uv.x * 591.32 + uv.y * 154.077), cos(uv.x * 391.32 + uv.y * 49.077)));
}
float wobble(float x) {
return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x);
}
// Main function
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) {
if (skipIntro) {
return vec4(1., 0., 0., 0.);
}
float columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.)) * -10.;
columnTimeOffset += sin(glyphPos.x / numColumns * PI) - 1.;
float introTime = (simTime + columnTimeOffset) * fallSpeed / numRows * 100.;
vec4 result = vec4(introTime, 0., 0., 0.);
return result;
}
void main() {
float simTime = time * animationSpeed;
bool isFirstFrame = tick <= 1.;
vec2 glyphPos = gl_FragCoord.xy;
vec2 screenPos = glyphPos / vec2(numColumns, numRows);
vec4 previous = texture2D( previousIntroState, screenPos );
gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous);
}

View File

@@ -4,7 +4,7 @@ precision highp float;
// It writes falling rain to the channels of a data texture:
// R: raindrop brightness
// G: whether the cell is a "cursor"
// B: unused
// B: whether the cell is "activated" — to animate the intro
// A: unused
// Listen.
@@ -16,15 +16,14 @@ precision highp float;
#define SQRT_2 1.4142135623730951
#define SQRT_5 2.23606797749979
uniform sampler2D previousShineState;
uniform sampler2D previousRaindropState, introState;
uniform float numColumns, numRows;
uniform float time, tick;
uniform float animationSpeed, fallSpeed;
uniform bool loops;
uniform bool loops, skipIntro;
uniform float brightnessDecay;
uniform float baseContrast, baseBrightness;
uniform float raindropLength, glyphHeightToWidth;
uniform float raindropLength;
// Helper functions for generating randomness, borrowed from elsewhere
@@ -61,10 +60,17 @@ float getRainBrightness(float simTime, vec2 glyphPos) {
// Main function
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) {
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec4 previous, vec4 intro) {
float brightness = getRainBrightness(simTime, glyphPos);
float brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.));
float cursor = brightness > brightnessBelow ? 1.0 : 0.0;
float introProgress = intro.r - (1. - glyphPos.y / numRows);
float introProgressBelow = intro.r - (1. - (glyphPos.y - 1.) / numRows);
bool activated = bool(previous.b) || skipIntro || introProgress > 0.;
bool activatedBelow = skipIntro || introProgressBelow > 0.;
bool cursor = brightness > brightnessBelow || (activated && !activatedBelow);
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
if (!isFirstFrame) {
@@ -72,7 +78,7 @@ vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenP
brightness = mix(previousBrightness, brightness, brightnessDecay);
}
vec4 result = vec4(brightness, cursor, 0.0, 0.0);
vec4 result = vec4(brightness, cursor, activated, introProgress);
return result;
}
@@ -81,6 +87,7 @@ void main() {
bool isFirstFrame = tick <= 1.;
vec2 glyphPos = gl_FragCoord.xy;
vec2 screenPos = glyphPos / vec2(numColumns, numRows);
vec4 previous = texture2D( previousShineState, screenPos );
gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous);
vec4 previous = texture2D( previousRaindropState, screenPos );
vec4 intro = texture2D( introState, vec2(screenPos.x, 0.) );
gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, previous, intro);
}

View File

@@ -43,6 +43,7 @@ struct Config {
isolateCursor : i32,
isolateGlint : i32,
loops : i32,
skipIntro : i32,
highPassThreshold : f32,
};
@@ -70,12 +71,22 @@ struct CellData {
cells: array<Cell>,
};
struct IntroCell {
progress : vec4<f32>,
};
// The array of cells that the compute shader updates, and the fragment shader draws.
struct IntroCellData {
cells: array<IntroCell>,
};
// Shared bindings
@group(0) @binding(0) var<uniform> config : Config;
@group(0) @binding(1) var<uniform> time : Time;
// Compute-specific bindings
@group(0) @binding(2) var<storage, read_write> cells_RW : CellData;
@group(0) @binding(3) var<storage, read_write> introCells_RW : IntroCellData;
// Render-specific bindings
@group(0) @binding(2) var<uniform> scene : Scene;
@@ -205,11 +216,32 @@ fn getRipple(simTime : f32, screenPos : vec2<f32>) -> f32 {
// Compute shader main functions
fn computeRaindrop (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>) -> vec4<f32> {
fn computeIntro (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>) -> vec4<f32> {
if (bool(config.skipIntro)) {
return vec4<f32>(1.0, 0.0, 0.0, 0.0);
}
var columnTimeOffset = randomFloat(glyphPos) * -10.0;
columnTimeOffset += sin(glyphPos.x / config.gridSize.x * PI) - 1.0;
var introTime = (simTime + columnTimeOffset) * config.fallSpeed / config.gridSize.y * 100.0;
var result = vec4<f32>(introTime, 0.0, 0.0, 0.0);
return result;
}
fn computeRaindrop (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>, progress : vec4<f32>) -> vec4<f32> {
var brightness = getRainBrightness(simTime, glyphPos);
var brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.));
var cursor = select(0.0, 1.0, brightness > brightnessBelow);
var brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0.0, -1.0));
var introProgress = progress.r - (1.0 - glyphPos.y / config.gridSize.y);
var introProgressBelow = progress.r - (1.0 - (glyphPos.y - 1.0) / config.gridSize.y);
var skipIntro = bool(config.skipIntro);
var activated = bool(previous.b) || skipIntro || introProgress > 0.0;
var activatedBelow = skipIntro || introProgressBelow > 0.0;
var cursor = brightness > brightnessBelow || (activated && !activatedBelow);
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
if (!isFirstFrame) {
@@ -217,7 +249,7 @@ fn computeRaindrop (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, sc
brightness = mix(previousBrightness, brightness, config.brightnessDecay);
}
var result = vec4<f32>(brightness, cursor, 0.0, 0.0);
var result = vec4<f32>(brightness, f32(cursor), f32(activated), introProgress);
return result;
}
@@ -277,8 +309,15 @@ fn computeEffect (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, scre
var glyphPos = vec2<f32>(f32(column), f32(row));
var screenPos = glyphPos / config.gridSize;
var introCell = introCells_RW.cells[column];
if (row == i32(config.gridSize.y - 1)) {
introCell.progress = computeIntro(simTime, isFirstFrame, glyphPos, screenPos, introCell.progress);
introCells_RW.cells[column] = introCell;
}
var cell = cells_RW.cells[i];
cell.raindrop = computeRaindrop(simTime, isFirstFrame, glyphPos, screenPos, cell.raindrop);
cell.raindrop = computeRaindrop(simTime, isFirstFrame, glyphPos, screenPos, cell.raindrop, introCell.progress);
cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.raindrop);
cell.effect = computeEffect(simTime, isFirstFrame, glyphPos, screenPos, cell.effect, cell.raindrop);
cells_RW.cells[i] = cell;
@@ -379,7 +418,7 @@ fn getUV(inputUV : vec2<f32>) -> vec2<f32> {
fn getBrightness(raindrop : vec4<f32>, effect : vec4<f32>, uv : vec2<f32>, quadDepth : f32) -> vec3<f32> {
var base = raindrop.r;
var base = raindrop.r + max(0.0, 1.0 - raindrop.a * 5.0);
var isCursor = bool(raindrop.g) && bool(config.isolateCursor);
var glint = base;
var multipliedEffects = effect.r;
@@ -412,7 +451,7 @@ fn getBrightness(raindrop : vec4<f32>, effect : vec4<f32>, uv : vec2<f32>, quadD
return vec3<f32>(
select(vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0), isCursor) * base,
glint
);
) * raindrop.b;
}
fn getSymbolUV(symbol : i32) -> vec2<f32> {