mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-14 12:29:30 -07:00
Adding intro and skipIntro option
This commit is contained in:
@@ -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".
|
||||
|
||||
@@ -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") },
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
59
shaders/glsl/rainPass.intro.frag.glsl
Normal file
59
shaders/glsl/rainPass.intro.frag.glsl
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user