diff --git a/README.md b/README.md index af814fb..e95ddc0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ - [3D mode](https://rezmason.github.io/matrix?version=3d) - [Holographic version](https://rezmason.github.io/matrix?version=holoplay) (requires a Looking Glass display; see it in action [here](https://www.youtube.com/watch?v=gwA9hfq1Ing)) - Mirror mode, [with camera](https://rezmason.github.io/matrix/?version=updated&effect=mirror&camera=true) and [without](rezmason.github.io/matrix/?version=updated&effect=mirror). (Click to make ripples.) -- [Matrix Resurrections updated code (WIP)](https://rezmason.github.io/matrix?version=resurrections) +- [Matrix Resurrections updated code](https://rezmason.github.io/matrix?version=resurrections) +- [Trinity mode](https://rezmason.github.io/matrix?version=trinity) - [Operator Matrix code (with ripple effects)](https://rezmason.github.io/matrix?version=operator) - [Code of the "Nightmare Matrix"](https://rezmason.github.io/matrix?version=nightmare) - [(you know, this stuff).](http://matrix.wikia.com/wiki/Nightmare_Matrix) @@ -16,6 +17,8 @@ - [(AKA this stuff).](http://matrix.wikia.com/wiki/Paradise_Matrix) - [A custom variety I call "Palimpsest"](https://rezmason.github.io/matrix?version=palimpsest) - [A custom variety I call "Twilight"](https://rezmason.github.io/matrix?version=twilight) +- [Morpheus mode](https://rezmason.github.io/matrix?version=morpheus) +- [Bugs mode](https://rezmason.github.io/matrix?version=bugs) - [Megacity Mode, as seen in Revolutions](https://rezmason.github.io/matrix?version=megacity) - [Pride flag colors](https://rezmason.github.io/matrix/?effect=pride) - [Trans flag colors](https://rezmason.github.io/matrix/?effect=trans) diff --git a/TODO.txt b/TODO.txt index d385b7e..f77b9cb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,11 +1,5 @@ TODO: -Multiply glints and bases by texture - Base: grid - Trinity glint: worn metal - Morpheus glint: hex mesh - Bugs glint: horizontal grooves - Audio system Toggle (or number representing frequency) Load the sound effect diff --git a/assets/mesh.png b/assets/mesh.png new file mode 100644 index 0000000..16b1451 Binary files /dev/null and b/assets/mesh.png differ diff --git a/assets/metal.png b/assets/metal.png new file mode 100644 index 0000000..26b72c4 Binary files /dev/null and b/assets/metal.png differ diff --git a/assets/pixel_grid.png b/assets/pixel_grid.png new file mode 100644 index 0000000..46db8d8 Binary files /dev/null and b/assets/pixel_grid.png differ diff --git a/assets/sand.png b/assets/sand.png new file mode 100644 index 0000000..fd50c79 Binary files /dev/null and b/assets/sand.png differ diff --git a/js/config.js b/js/config.js index 045ea10..89fe30c 100644 --- a/js/config.js +++ b/js/config.js @@ -1,59 +1,68 @@ const fonts = { coptic: { // The script the Gnostic codices were written in - glyphTexURL: "assets/coptic_msdf.png", + glyphMSDFURL: "assets/coptic_msdf.png", glyphSequenceLength: 32, glyphTextureGridSize: [8, 8], }, gothic: { // The script the Codex Argenteus was written in - glyphTexURL: "assets/gothic_msdf.png", + glyphMSDFURL: "assets/gothic_msdf.png", glyphSequenceLength: 27, glyphTextureGridSize: [8, 8], }, matrixcode: { // The glyphs seen in the film trilogy - glyphTexURL: "assets/matrixcode_msdf.png", + glyphMSDFURL: "assets/matrixcode_msdf.png", glyphSequenceLength: 57, glyphTextureGridSize: [8, 8], }, megacity: { // The glyphs seen in the film trilogy - glyphTexURL: "assets/megacity_msdf.png", + glyphMSDFURL: "assets/megacity_msdf.png", glyphSequenceLength: 64, glyphTextureGridSize: [8, 8], }, resurrections: { // The glyphs seen in the film trilogy - glyphTexURL: "assets/resurrections_msdf.png", - glintTexURL: "assets/resurrections_glint_msdf.png", + glyphMSDFURL: "assets/resurrections_msdf.png", + glintMSDFURL: "assets/resurrections_glint_msdf.png", glyphSequenceLength: 135, glyphTextureGridSize: [13, 12], }, huberfishA: { - glyphTexURL: "assets/huberfish_a_msdf.png", + glyphMSDFURL: "assets/huberfish_a_msdf.png", glyphSequenceLength: 34, glyphTextureGridSize: [6, 6], }, huberfishD: { - glyphTexURL: "assets/huberfish_d_msdf.png", + glyphMSDFURL: "assets/huberfish_d_msdf.png", glyphSequenceLength: 34, glyphTextureGridSize: [6, 6], }, gtarg_tenretniolleh: { - glyphTexURL: "assets/gtarg_tenretniolleh_msdf.png", + glyphMSDFURL: "assets/gtarg_tenretniolleh_msdf.png", glyphSequenceLength: 36, glyphTextureGridSize: [6, 6], }, gtarg_alientext: { - glyphTexURL: "assets/gtarg_alientext_msdf.png", + glyphMSDFURL: "assets/gtarg_alientext_msdf.png", glyphSequenceLength: 38, glyphTextureGridSize: [8, 5], }, }; +const textureURLs = { + sand: "assets/sand.png", + pixels: "assets/pixel_grid.png", + mesh: "assets/mesh.png", + metal: "assets/metal.png", +}; + const defaults = { font: "matrixcode", + baseTexture: null, // The name of the texture to apply to the base layer of the glyphs + glintTexture: null, // The name of the texture to apply to the glint layer of the glyphs useCamera: false, backgroundColor: [0, 0, 0], // The color "behind" the glyphs isolateCursor: true, // Whether the "cursor"— the brightest glyph at the bottom of a raindrop— has its own color @@ -70,8 +79,8 @@ const defaults = { cycleFrameSkip: 1, // The global minimum number of frames between glyphs cycling baseBrightness: -0.5, // The brightness of the glyphs, before any effects are applied baseContrast: 1.1, // The contrast of the glyphs, before any effects are applied - glintBrightness: -2, // The brightness of the glints, before any effects are applied - glintContrast: 3, // The contrast of the glints, before any effects are applied + glintBrightness: -1.5, // The brightness of the glints, before any effects are applied + glintContrast: 2.5, // The contrast of the glints, before any effects are applied brightnessOverride: 0.0, // A global override to the brightness of displayed glyphs. Only used if it is > 0. brightnessThreshold: 0, // The minimum brightness for a glyph to still be considered visible brightnessDecay: 1.0, // The rate at which glyphs light up and dim @@ -195,25 +204,85 @@ const versions = { }, trinity: { font: "resurrections", + glintTexture: "metal", + baseTexture: "pixels", glyphEdgeCrop: 0.1, cursorColor: [1.4, 2, 1.2], isolateGlint: true, - glintColor: [1.6, 1.5, 0.5], - baseBrightness: -0.9, + glintColor: [3, 2.5, 0.6], + glintBrightness: -2, + glintContrast: 3, + baseBrightness: -0.8, baseContrast: 1.5, highPassThreshold: 0, - numColumns: 50, + numColumns: 60, cycleSpeed: 0.03, bloomStrength: 0.7, fallSpeed: 0.3, paletteEntries: [ - { hsl: [0.4, 0.9, 0.0], at: 0.0 }, - { hsl: [0.4, 1.0, 0.5], at: 1.0 }, + { hsl: [0.37, 0.6, 0.0], at: 0.0 }, + { hsl: [0.37, 0.6, 0.5], at: 1.0 }, ], + cycleSpeed: 0.01, volumetric: true, forwardSpeed: 0.2, raindropLength: 0.3, - density: 0.5, + density: 0.75, + }, + morpheus: { + font: "resurrections", + glintTexture: "mesh", + baseTexture: "metal", + glyphEdgeCrop: 0.1, + cursorColor: [1.4, 2, 1.4], + isolateGlint: true, + glintColor: [0, 2, 0.8], + glintBrightness: -1.5, + glintContrast: 3, + baseBrightness: -0.3, + baseContrast: 1.5, + highPassThreshold: 0, + numColumns: 60, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + paletteEntries: [ + { hsl: [0.97, 0.6, 0.0], at: 0.0 }, + { hsl: [0.97, 0.6, 0.5], at: 1.0 }, + ], + cycleSpeed: 0.015, + volumetric: true, + forwardSpeed: 0.1, + raindropLength: 0.4, + density: 0.75, + }, + bugs: { + font: "resurrections", + glintTexture: "sand", + baseTexture: "metal", + glyphEdgeCrop: 0.1, + cursorColor: [0.6, 1, 2], + isolateGlint: true, + glintColor: [0.6, 2, 3], + glintBrightness: -2, + glintContrast: 3, + baseBrightness: -0.3, + baseContrast: 1.5, + highPassThreshold: 0, + numColumns: 60, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + paletteEntries: [ + { hsl: [0.1, 0.7, 0.0], at: 0.0 }, + { hsl: [0.1, 0.7, 0.1], at: 0.3 }, + { hsl: [0.12, 0.7, 0.5], at: 1.0 }, + ], + cycleSpeed: 0.01, + volumetric: true, + forwardSpeed: 0.4, + raindropLength: 0.3, + density: 0.75, }, palimpsest: { font: "huberfishA", @@ -363,11 +432,20 @@ export default (urlParams) => { const fontName = [validParams.font, version.font, defaults.font].find((name) => name in fonts); const font = fonts[fontName]; + const baseTextureURL = textureURLs[[version.baseTexture, defaults.baseTexture].find((name) => name in textureURLs)]; + const hasBaseTexture = baseTextureURL != null; + const glintTextureURL = textureURLs[[version.glintTexture, defaults.glintTexture].find((name) => name in textureURLs)]; + const hasGlintTexture = glintTextureURL != null; + const config = { ...defaults, ...version, ...font, ...validParams, + baseTextureURL, + glintTextureURL, + hasBaseTexture, + hasGlintTexture, }; if (config.bloomSize <= 0) { diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index bbdafb5..838f6fa 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -34,7 +34,7 @@ export default ({ regl, config, lkg }) => { // to reach the desired density, and then overlaps them const volumetric = config.volumetric; const density = volumetric && config.effect !== "none" ? config.density : 1; - const [numRows, numColumns] = [config.numColumns, config.numColumns * density]; + const [numRows, numColumns] = [config.numColumns, Math.floor(config.numColumns * density)]; // The volumetric mode requires us to create a grid of quads, // rather than a single quad for our geometry @@ -115,8 +115,10 @@ export default ({ regl, config, lkg }) => { ); // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen - const msdf = loadImage(regl, config.glyphTexURL); - const glintMSDF = loadImage(regl, config.glintTexURL); + const glyphMSDF = loadImage(regl, config.glyphMSDFURL); + const glintMSDF = loadImage(regl, config.glintMSDFURL); + const baseTexture = loadImage(regl, config.baseTextureURL); + const glintTexture = loadImage(regl, config.glintTextureURL); const rainPassVert = loadText("shaders/glsl/rainPass.vert.glsl"); const rainPassFrag = loadText("shaders/glsl/rainPass.frag.glsl"); const output = makePassFBO(regl, config.useHalfFloat); @@ -131,6 +133,8 @@ export default ({ regl, config, lkg }) => { "baseContrast", "glintBrightness", "glintContrast", + "hasBaseTexture", + "hasGlintTexture", "brightnessThreshold", "brightnessOverride", "isolateCursor", @@ -163,8 +167,10 @@ export default ({ regl, config, lkg }) => { raindropState: raindropDoubleBuffer.front, symbolState: symbolDoubleBuffer.front, effectState: effectDoubleBuffer.front, - glyphTex: msdf.texture, - glintTex: glintMSDF.texture, + glyphMSDF: glyphMSDF.texture, + glintMSDF: glintMSDF.texture, + baseTexture: baseTexture.texture, + glintTexture: glintTexture.texture, camera: regl.prop("camera"), transform: regl.prop("transform"), @@ -206,7 +212,16 @@ export default ({ regl, config, lkg }) => { { primary: output, }, - Promise.all([msdf.loaded, glintMSDF.loaded, rainPassShine.loaded, rainPassSymbol.loaded, rainPassVert.loaded, rainPassFrag.loaded]), + Promise.all([ + glyphMSDF.loaded, + glintMSDF.loaded, + baseTexture.loaded, + glintTexture.loaded, + rainPassShine.loaded, + rainPassSymbol.loaded, + rainPassVert.loaded, + rainPassFrag.loaded, + ]), (w, h) => { output.resize(w, h); const aspectRatio = w / h; diff --git a/js/regl/utils.js b/js/regl/utils.js index a56e31b..f9ce342 100644 --- a/js/regl/utils.js +++ b/js/regl/utils.js @@ -1,11 +1,11 @@ -const makePassTexture = (regl, halfFloat) => +const makePassTexture = (regl, halfFloat, mipmap) => regl.texture({ width: 1, height: 1, type: halfFloat ? "half float" : "uint8", wrap: "clamp", - min: "linear", - mag: "linear", + min: mipmap ? "mipmap" : "linear", + mag: mipmap ? "mipmap" : "linear", }); const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) }); diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index 75b95f3..5d1ef6e 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -26,12 +26,18 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => export default ({ config, device, timeBuffer }) => { const { mat4, vec3 } = glMatrix; - const assets = [loadTexture(device, config.glyphTexURL), loadTexture(device, config.glintTexURL), loadShader(device, "shaders/wgsl/rainPass.wgsl")]; + const assets = [ + loadTexture(device, config.glyphMSDFURL), + loadTexture(device, config.glintMSDFURL), + loadTexture(device, config.baseTextureURL, false, true), + loadTexture(device, config.glintTextureURL, false, true), + loadShader(device, "shaders/wgsl/rainPass.wgsl"), + ]; // The volumetric mode multiplies the number of columns // to reach the desired density, and then overlaps them const density = config.volumetric && config.effect !== "none" ? config.density : 1; - const gridSize = [config.numColumns * density, config.numColumns]; + const gridSize = [Math.floor(config.numColumns * density), config.numColumns]; const numCells = gridSize[0] * gridSize[1]; // The volumetric mode requires us to create a grid of quads, @@ -85,7 +91,7 @@ export default ({ config, device, timeBuffer }) => { let highPassOutput; const loaded = (async () => { - const [msdfTexture, glintMSDFTexture, rainShader] = await Promise.all(assets); + const [msdfTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = await Promise.all(assets); const rainShaderUniforms = structs.from(rainShader.code); configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize); @@ -150,6 +156,8 @@ export default ({ config, device, timeBuffer }) => { linearSampler, msdfTexture.createView(), glintMSDFTexture.createView(), + baseTexture.createView(), + glintTexture.createView(), cellsBuffer, ]); })(); diff --git a/shaders/glsl/rainPass.frag.glsl b/shaders/glsl/rainPass.frag.glsl index bef4014..bd44da6 100644 --- a/shaders/glsl/rainPass.frag.glsl +++ b/shaders/glsl/rainPass.frag.glsl @@ -6,7 +6,8 @@ precision lowp float; uniform sampler2D raindropState, symbolState, effectState; uniform float numColumns, numRows; -uniform sampler2D glyphTex, glintTex; +uniform sampler2D glyphMSDF, glintMSDF, baseTexture, glintTexture; +uniform bool hasBaseTexture, hasGlintTexture; uniform float glyphHeightToWidth, glyphSequenceLength, glyphEdgeCrop; uniform float baseContrast, baseBrightness, glintContrast, glintBrightness; uniform float brightnessOverride, brightnessThreshold; @@ -58,7 +59,7 @@ vec2 getUV(vec2 uv) { return uv; } -vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth) { +vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth, vec2 uv) { float base = raindrop.r; bool isCursor = bool(raindrop.g) && isolateCursor; @@ -66,8 +67,15 @@ vec3 getBrightness(vec4 raindrop, vec4 effect, float quadDepth) { float multipliedEffects = effect.r; float addedEffects = effect.g; + vec2 textureUV = fract(uv * vec2(numColumns, numRows)); base = base * baseContrast + baseBrightness; + if (hasBaseTexture) { + base *= texture2D(baseTexture, textureUV).r; + } glint = glint * glintContrast + glintBrightness; + if (hasGlintTexture) { + glint *= texture2D(glintTexture, textureUV).r; + } // Modes that don't fade glyphs set their actual brightness here if (brightnessOverride > 0. && base > brightnessThreshold && !isCursor) { @@ -107,13 +115,13 @@ vec2 getSymbol(vec2 uv, float index) { // MSDF: calculate brightness of fragment based on distance to shape vec2 symbol; { - vec3 dist = texture2D(glyphTex, uv).rgb; + vec3 dist = texture2D(glyphMSDF, uv).rgb; float sigDist = median3(dist) - 0.5; symbol.r = clamp(sigDist / fwidth(sigDist) + 0.5, 0., 1.); } if (isolateGlint) { - vec3 dist = texture2D(glintTex, uv).rgb; + vec3 dist = texture2D(glintMSDF, uv).rgb; float sigDist = median3(dist) - 0.5; symbol.g = clamp(sigDist / fwidth(sigDist) + 0.5, 0., 1.); } @@ -133,7 +141,8 @@ void main() { vec3 brightness = getBrightness( raindropData, effectData, - vDepth + vDepth, + uv ); vec2 symbol = getSymbol(uv, symbolData.r); @@ -142,8 +151,8 @@ void main() { vec3( raindropData.g, vec2( - 1. - (raindropData.r * 3.), - 1. - (raindropData.r * 8.) + 1. - ((1.0 - raindropData.r) * 3.), + 1. - ((1.0 - raindropData.r) * 8.) ) * (1. - raindropData.g) ) * symbol.r, 1. diff --git a/shaders/wgsl/rainPass.wgsl b/shaders/wgsl/rainPass.wgsl index 14f7ba4..ddd585b 100644 --- a/shaders/wgsl/rainPass.wgsl +++ b/shaders/wgsl/rainPass.wgsl @@ -31,6 +31,8 @@ struct Config { baseContrast : f32, glintBrightness : f32, glintContrast : f32, + hasBaseTexture: i32, + hasGlintTexture: i32, glyphVerticalSpacing : f32, glyphEdgeCrop : f32, isPolar : i32, @@ -80,7 +82,9 @@ struct CellData { @group(0) @binding(3) var linearSampler : sampler; @group(0) @binding(4) var msdfTexture : texture_2d; @group(0) @binding(5) var glintMSDFTexture : texture_2d; -@group(0) @binding(6) var cells_RO : CellData; +@group(0) @binding(6) var baseTexture : texture_2d; +@group(0) @binding(7) var glintTexture : texture_2d; +@group(0) @binding(8) var cells_RO : CellData; // Shader params @@ -373,7 +377,7 @@ fn getUV(inputUV : vec2) -> vec2 { return uv; } -fn getBrightness(raindrop : vec4, effect : vec4, quadDepth : f32) -> vec3 { +fn getBrightness(raindrop : vec4, effect : vec4, uv : vec2, quadDepth : f32) -> vec3 { var base = raindrop.r; var isCursor = bool(raindrop.g) && bool(config.isolateCursor); @@ -381,8 +385,15 @@ fn getBrightness(raindrop : vec4, effect : vec4, quadDepth : f32) -> v var multipliedEffects = effect.r; var addedEffects = effect.g; + var textureUV = fract(uv * config.gridSize); base = base * config.baseContrast + config.baseBrightness; + if (bool(config.hasBaseTexture)) { + base *= textureSample(baseTexture, linearSampler, textureUV).r; + } glint = glint * config.glintContrast + config.glintBrightness; + if (bool(config.hasGlintTexture)) { + glint *= textureSample(glintTexture, linearSampler, textureUV).r; + } // Modes that don't fade glyphs set their actual brightness here if (config.brightnessOverride > 0. && base > config.brightnessThreshold && !isCursor) { @@ -451,6 +462,7 @@ fn getSymbol(cellUV : vec2, index : i32) -> vec2 { var brightness = getBrightness( cell.raindrop, cell.effect, + uv, input.quadDepth ); var symbol = getSymbol(uv, i32(cell.symbol.r)); @@ -462,8 +474,8 @@ fn getSymbol(cellUV : vec2, index : i32) -> vec2 { vec3( cell.raindrop.g, vec2( - 1.0 - (cell.raindrop.r * 3.0), - 1.0 - (cell.raindrop.r * 8.0) + 1.0 - ((1.0 - cell.raindrop.r) * 3.0), + 1.0 - ((1.0 - cell.raindrop.r) * 8.0) ) * (1.0 - cell.raindrop.g) ) * symbol.r, 1.0