mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-17 13:59:30 -07:00
Added some documentation, cleaned up some code, fleshed out the remaining work to make the project a little easier for newcomers to approach
This commit is contained in:
21
TODO.txt
21
TODO.txt
@@ -1,5 +1,21 @@
|
|||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
|
|
||||||
|
Improve forkability
|
||||||
|
Document every variable in config.js
|
||||||
|
Document every variable, method, and section of the main function in compute.frag
|
||||||
|
Maybe rewrite it? Make the time based stuff easier to read?
|
||||||
|
Document resurrectionPass
|
||||||
|
Label it a WIP
|
||||||
|
List intended characteristics
|
||||||
|
Comment makePalette
|
||||||
|
|
||||||
|
Write a document (and include images) that explains the underlying principle of the rain pass
|
||||||
|
|
||||||
|
Create interactive controls?
|
||||||
|
Is the pipeline stuff just too bizarre?
|
||||||
|
|
||||||
|
|
||||||
Resurrection
|
Resurrection
|
||||||
Modified glyph order?
|
Modified glyph order?
|
||||||
|
|
||||||
@@ -14,6 +30,11 @@ Resurrection
|
|||||||
|
|
||||||
New glyphs?
|
New glyphs?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Experiment with varying the colors in the palette pass
|
Experiment with varying the colors in the palette pass
|
||||||
Maybe a separate palette for the non-bloom
|
Maybe a separate palette for the non-bloom
|
||||||
Maybe dim and widen the bloom
|
Maybe dim and widen the bloom
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { loadText, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js";
|
import { loadText, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js";
|
||||||
|
|
||||||
// The bloom pass is basically an added high-pass blur.
|
// The bloom pass is basically an added high-pass blur.
|
||||||
|
// The blur approximation is the sum of a pyramid of downscaled textures.
|
||||||
|
|
||||||
const pyramidHeight = 5;
|
const pyramidHeight = 5;
|
||||||
const levelStrengths = Array(pyramidHeight)
|
const levelStrengths = Array(pyramidHeight)
|
||||||
@@ -9,8 +10,10 @@ const levelStrengths = Array(pyramidHeight)
|
|||||||
.reverse();
|
.reverse();
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
export default (regl, config, inputs) => {
|
||||||
const enabled = config.bloomSize > 0 && config.bloomStrength > 0;
|
const { bloomStrength, bloomSize, highPassThreshold } = config;
|
||||||
|
const enabled = bloomSize > 0 && bloomStrength > 0;
|
||||||
|
|
||||||
|
// If there's no bloom to apply, return a no-op pass with an empty bloom texture
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return makePass({
|
return makePass({
|
||||||
primary: inputs.primary,
|
primary: inputs.primary,
|
||||||
@@ -18,16 +21,14 @@ export default (regl, config, inputs) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bloomStrength, highPassThreshold } = config;
|
// Build three pyramids of FBOs, one for each step in the process
|
||||||
|
|
||||||
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
|
||||||
const highPassFrag = loadText("shaders/highPass.frag");
|
|
||||||
|
|
||||||
// The high pass restricts the blur to bright things in our input texture.
|
// The high pass restricts the blur to bright things in our input texture.
|
||||||
|
const highPassFrag = loadText("shaders/highPass.frag");
|
||||||
const highPass = regl({
|
const highPass = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
uniforms: {
|
uniforms: {
|
||||||
@@ -39,7 +40,8 @@ export default (regl, config, inputs) => {
|
|||||||
|
|
||||||
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
|
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
|
||||||
// The FBO pyramid's levels represent separate levels of detail;
|
// The FBO pyramid's levels represent separate levels of detail;
|
||||||
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
|
// by blurring them all, this basic blur approximates a more complex gaussian:
|
||||||
|
// https://software.intel.com/en-us/articles/compute-shader-hdr-and-bloom
|
||||||
|
|
||||||
const blurFrag = loadText("shaders/blur.frag");
|
const blurFrag = loadText("shaders/blur.frag");
|
||||||
const blur = regl({
|
const blur = regl({
|
||||||
@@ -53,8 +55,8 @@ export default (regl, config, inputs) => {
|
|||||||
framebuffer: regl.prop("fbo"),
|
framebuffer: regl.prop("fbo"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// The pyramid of textures gets flattened onto the source texture.
|
// The pyramid of textures gets flattened (summed) into a final blurry "bloom" texture
|
||||||
const flattenPyramid = regl({
|
const sumPyramid = regl({
|
||||||
frag: `
|
frag: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec2 vUV;
|
varying vec2 vUV;
|
||||||
@@ -88,15 +90,15 @@ export default (regl, config, inputs) => {
|
|||||||
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
|
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
|
||||||
}
|
}
|
||||||
|
|
||||||
flattenPyramid();
|
sumPyramid();
|
||||||
},
|
},
|
||||||
(w, h) => {
|
(w, h) => {
|
||||||
// The blur pyramids can be lower resolution than the screen.
|
// The blur pyramids can be lower resolution than the screen.
|
||||||
resizePyramid(highPassPyramid, w, h, config.bloomSize);
|
resizePyramid(highPassPyramid, w, h, bloomSize);
|
||||||
resizePyramid(hBlurPyramid, w, h, config.bloomSize);
|
resizePyramid(hBlurPyramid, w, h, bloomSize);
|
||||||
resizePyramid(vBlurPyramid, w, h, config.bloomSize);
|
resizePyramid(vBlurPyramid, w, h, bloomSize);
|
||||||
output.resize(w, h);
|
output.resize(w, h);
|
||||||
},
|
},
|
||||||
[highPassFrag.laoded, blurFrag.loaded]
|
[highPassFrag.loaded, blurFrag.loaded]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
|
// Multiplies the rendered rain and bloom by a loaded in image
|
||||||
|
|
||||||
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
|
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
export default (regl, config, inputs) => {
|
||||||
|
|||||||
21
js/main.js
21
js/main.js
@@ -1,5 +1,7 @@
|
|||||||
import { makeFullScreenQuad, makePipeline } from "./utils.js";
|
import { makeFullScreenQuad, makePipeline } from "./utils.js";
|
||||||
import makeConfig from "./config.js";
|
|
||||||
|
import makeConfig from "./config.js"; // The settings of the effect, specified in the URL query params
|
||||||
|
|
||||||
import makeRain from "./rainPass.js";
|
import makeRain from "./rainPass.js";
|
||||||
import makeBloomPass from "./bloomPass.js";
|
import makeBloomPass from "./bloomPass.js";
|
||||||
import makePalettePass from "./palettePass.js";
|
import makePalettePass from "./palettePass.js";
|
||||||
@@ -34,12 +36,10 @@ const effects = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const config = makeConfig(window.location.search);
|
const config = makeConfig(window.location.search);
|
||||||
const resolution = config.resolution;
|
|
||||||
const effect = config.effect in effects ? config.effect : "plain";
|
|
||||||
|
|
||||||
const resize = () => {
|
const resize = () => {
|
||||||
canvas.width = Math.ceil(canvas.clientWidth * resolution);
|
canvas.width = Math.ceil(canvas.clientWidth * config.resolution);
|
||||||
canvas.height = Math.ceil(canvas.clientHeight * resolution);
|
canvas.height = Math.ceil(canvas.clientHeight * config.resolution);
|
||||||
};
|
};
|
||||||
window.onresize = resize;
|
window.onresize = resize;
|
||||||
resize();
|
resize();
|
||||||
@@ -49,12 +49,11 @@ const dimensions = { width: 1, height: 1 };
|
|||||||
document.body.onload = async () => {
|
document.body.onload = async () => {
|
||||||
// All this takes place in a full screen quad.
|
// All this takes place in a full screen quad.
|
||||||
const fullScreenQuad = makeFullScreenQuad(regl);
|
const fullScreenQuad = makeFullScreenQuad(regl);
|
||||||
|
const effectName = config.effect in effects ? config.effect : "plain";
|
||||||
const bloomPass = effect === "none" ? null : makeBloomPass;
|
const pipeline = makePipeline([makeRain, makeBloomPass, effects[effectName]], (p) => p.outputs, regl, config);
|
||||||
const pipeline = makePipeline([makeRain, bloomPass, effects[effect]], (p) => p.outputs, regl, config);
|
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||||
const uniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
const drawToScreen = regl({ uniforms: screenUniforms });
|
||||||
const drawToScreen = regl({ uniforms });
|
await Promise.all(pipeline.map((step) => step.ready));
|
||||||
await Promise.all(pipeline.map(({ ready }) => ready));
|
|
||||||
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||||
// tick.cancel();
|
// tick.cancel();
|
||||||
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
|
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
|
// Maps the brightness of the rendered rain and bloom to colors
|
||||||
|
// in a 1D gradient palette texture generated from the passed-in color sequence
|
||||||
|
|
||||||
|
// This shader introduces noise into the renders, to avoid banding
|
||||||
|
|
||||||
const colorToRGB = ([hue, saturation, lightness]) => {
|
const colorToRGB = ([hue, saturation, lightness]) => {
|
||||||
const a = saturation * Math.min(lightness, 1 - lightness);
|
const a = saturation * Math.min(lightness, 1 - lightness);
|
||||||
const f = (n) => {
|
const f = (n) => {
|
||||||
|
|||||||
105
js/rainPass.js
105
js/rainPass.js
@@ -20,64 +20,32 @@ const brVert = [1, 1];
|
|||||||
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
||||||
|
|
||||||
export default (regl, config) => {
|
export default (regl, config) => {
|
||||||
|
// The volumetric mode multiplies the number of columns
|
||||||
|
// to reach the desired density, and then overlaps them
|
||||||
const volumetric = config.volumetric;
|
const volumetric = config.volumetric;
|
||||||
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
||||||
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
|
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
|
||||||
|
|
||||||
|
// The volumetric mode requires us to create a grid of quads,
|
||||||
|
// rather than a single quad for our geometry
|
||||||
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
|
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
|
||||||
const numQuads = numQuadRows * numQuadColumns;
|
const numQuads = numQuadRows * numQuadColumns;
|
||||||
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
||||||
|
|
||||||
|
// Various effect-related values
|
||||||
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
|
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
|
||||||
const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0;
|
const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0;
|
||||||
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
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 slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||||
const showComputationTexture = config.effect === "none";
|
const showComputationTexture = config.effect === "none";
|
||||||
|
|
||||||
const uniforms = {
|
const commonUniforms = {
|
||||||
...extractEntries(config, [
|
...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureColumns", "resurrectingCodeRatio"]),
|
||||||
// general
|
|
||||||
"glyphHeightToWidth",
|
|
||||||
"glyphTextureColumns",
|
|
||||||
// compute
|
|
||||||
"animationSpeed",
|
|
||||||
"brightnessMinimum",
|
|
||||||
"brightnessMix",
|
|
||||||
"brightnessMultiplier",
|
|
||||||
"brightnessOffset",
|
|
||||||
"cursorEffectThreshold",
|
|
||||||
"cycleSpeed",
|
|
||||||
"fallSpeed",
|
|
||||||
"glyphSequenceLength",
|
|
||||||
"hasSun",
|
|
||||||
"hasThunder",
|
|
||||||
"raindropLength",
|
|
||||||
"rippleScale",
|
|
||||||
"rippleSpeed",
|
|
||||||
"rippleThickness",
|
|
||||||
"resurrectingCodeRatio",
|
|
||||||
// render vertex
|
|
||||||
"forwardSpeed",
|
|
||||||
// render fragment
|
|
||||||
"glyphEdgeCrop",
|
|
||||||
"isPolar",
|
|
||||||
]),
|
|
||||||
density,
|
|
||||||
numRows,
|
|
||||||
numColumns,
|
numColumns,
|
||||||
numQuadRows,
|
numRows,
|
||||||
numQuadColumns,
|
|
||||||
quadSize,
|
|
||||||
volumetric,
|
|
||||||
|
|
||||||
rippleType,
|
|
||||||
cycleStyle,
|
|
||||||
slantVec,
|
|
||||||
slantScale,
|
|
||||||
showComputationTexture,
|
showComputationTexture,
|
||||||
};
|
};
|
||||||
|
|
||||||
const msdf = loadImage(regl, config.glyphTexURL);
|
|
||||||
|
|
||||||
// These two framebuffers are used to compute the raining code.
|
// These two framebuffers are used to compute the raining code.
|
||||||
// they take turns being the source and destination of the "compute" shader.
|
// they take turns being the source and destination of the "compute" shader.
|
||||||
// The half float data type is crucial! It lets us store almost any real number,
|
// The half float data type is crucial! It lets us store almost any real number,
|
||||||
@@ -91,14 +59,31 @@ export default (regl, config) => {
|
|||||||
wrapT: "clamp",
|
wrapT: "clamp",
|
||||||
type: "half float",
|
type: "half float",
|
||||||
});
|
});
|
||||||
|
const computeFrag = loadText("shaders/compute.frag");
|
||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const computeUniforms = {
|
||||||
|
...commonUniforms,
|
||||||
const updateFrag = loadText("shaders/compute.frag");
|
...extractEntries(config, [
|
||||||
const update = regl({
|
"brightnessMinimum",
|
||||||
|
"brightnessMix",
|
||||||
|
"brightnessMultiplier",
|
||||||
|
"brightnessOffset",
|
||||||
|
"cursorEffectThreshold",
|
||||||
|
"cycleSpeed",
|
||||||
|
"fallSpeed",
|
||||||
|
"hasSun",
|
||||||
|
"hasThunder",
|
||||||
|
"raindropLength",
|
||||||
|
"rippleScale",
|
||||||
|
"rippleSpeed",
|
||||||
|
"rippleThickness",
|
||||||
|
]),
|
||||||
|
cycleStyle,
|
||||||
|
rippleType,
|
||||||
|
};
|
||||||
|
const compute = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...uniforms,
|
...computeUniforms,
|
||||||
lastState: doubleBuffer.back,
|
lastState: doubleBuffer.back,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -114,8 +99,27 @@ export default (regl, config) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||||
|
const msdf = loadImage(regl, config.glyphTexURL);
|
||||||
const renderVert = loadText("shaders/rain.vert");
|
const renderVert = loadText("shaders/rain.vert");
|
||||||
const renderFrag = loadText("shaders/rain.frag");
|
const renderFrag = loadText("shaders/rain.frag");
|
||||||
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
const renderUniforms = {
|
||||||
|
...commonUniforms,
|
||||||
|
...extractEntries(config, [
|
||||||
|
// vertex
|
||||||
|
"forwardSpeed",
|
||||||
|
// fragment
|
||||||
|
"glyphEdgeCrop",
|
||||||
|
"isPolar",
|
||||||
|
]),
|
||||||
|
density,
|
||||||
|
numQuadColumns,
|
||||||
|
numQuadRows,
|
||||||
|
quadSize,
|
||||||
|
slantScale,
|
||||||
|
slantVec,
|
||||||
|
volumetric,
|
||||||
|
};
|
||||||
const render = regl({
|
const render = regl({
|
||||||
blend: {
|
blend: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -128,7 +132,7 @@ export default (regl, config) => {
|
|||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...uniforms,
|
...renderUniforms,
|
||||||
|
|
||||||
lastState: doubleBuffer.front,
|
lastState: doubleBuffer.front,
|
||||||
glyphTex: msdf.texture,
|
glyphTex: msdf.texture,
|
||||||
@@ -147,6 +151,7 @@ export default (regl, config) => {
|
|||||||
framebuffer: output,
|
framebuffer: output,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Camera and transform math for the volumetric mode
|
||||||
const screenSize = [1, 1];
|
const screenSize = [1, 1];
|
||||||
const { mat4, vec3 } = glMatrix;
|
const { mat4, vec3 } = glMatrix;
|
||||||
const camera = mat4.create();
|
const camera = mat4.create();
|
||||||
@@ -161,7 +166,7 @@ export default (regl, config) => {
|
|||||||
primary: output,
|
primary: output,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
update({ frag: updateFrag.text() });
|
compute({ frag: computeFrag.text() });
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
color: [0, 0, 0, 1],
|
color: [0, 0, 0, 1],
|
||||||
@@ -175,6 +180,6 @@ export default (regl, config) => {
|
|||||||
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||||
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||||
},
|
},
|
||||||
[msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded]
|
[msdf.loaded, computeFrag.loaded, renderVert.loaded, renderFrag.loaded]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
|
// Multiplies the rendered rain and bloom by a 1D gradient texture
|
||||||
|
// generated from the passed-in color sequence
|
||||||
|
|
||||||
|
// This shader introduces noise into the renders, to avoid banding
|
||||||
|
|
||||||
const transPrideStripeColors = [
|
const transPrideStripeColors = [
|
||||||
[0.3, 1.0, 1.0],
|
[0.3, 1.0, 1.0],
|
||||||
[0.3, 1.0, 1.0],
|
[0.3, 1.0, 1.0],
|
||||||
@@ -27,6 +32,8 @@ export default (regl, config, inputs) => {
|
|||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
|
||||||
const { backgroundColor } = config;
|
const { backgroundColor } = config;
|
||||||
|
|
||||||
|
// Expand and convert stripe colors into 1D texture data
|
||||||
const stripeColors =
|
const stripeColors =
|
||||||
"stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors;
|
"stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors;
|
||||||
const numStripeColors = Math.floor(stripeColors.length / 3);
|
const numStripeColors = Math.floor(stripeColors.length / 3);
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ varying vec2 vUV;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 bgColor = texture2D(backgroundTex, vUV).rgb;
|
vec3 bgColor = texture2D(backgroundTex, vUV).rgb;
|
||||||
|
|
||||||
|
// Combine the texture and bloom, then blow it out to reveal more of the image
|
||||||
float brightness = pow(min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r, 1.5);
|
float brightness = pow(min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r, 1.5);
|
||||||
|
|
||||||
gl_FragColor = vec4(bgColor * brightness, 1.0);
|
gl_FragColor = vec4(bgColor * brightness, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ highp float rand( const in vec2 uv, const in float t ) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 brightnessRGB = texture2D( tex, vUV ) + texture2D( bloomTex, vUV );
|
vec4 brightnessRGB = texture2D( tex, vUV ) + texture2D( bloomTex, vUV );
|
||||||
|
|
||||||
|
// Combine the texture and bloom
|
||||||
float brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b;
|
float brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b;
|
||||||
float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
|
||||||
gl_FragColor = texture2D( palette, vec2(at, 0.0)) + vec4(backgroundColor, 0.0);
|
// Dither: subtract a random value from the brightness
|
||||||
|
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||||
|
|
||||||
|
// Map the brightness to a position in the palette texture
|
||||||
|
gl_FragColor = texture2D( palette, vec2(brightness, 0.0)) + vec4(backgroundColor, 0.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ void main() {
|
|||||||
|
|
||||||
vec2 uv = vUV;
|
vec2 uv = vUV;
|
||||||
|
|
||||||
|
// In normal mode, derives the current glyph and UV from vUV
|
||||||
if (!volumetric) {
|
if (!volumetric) {
|
||||||
if (isPolar) {
|
if (isPolar) {
|
||||||
// Curves the UV space to make letters appear to radiate from up above
|
// Curved space that makes letters appear to radiate from up above
|
||||||
uv -= 0.5;
|
uv -= 0.5;
|
||||||
uv *= 0.5;
|
uv *= 0.5;
|
||||||
uv.y -= 0.5;
|
uv.y -= 0.5;
|
||||||
@@ -43,35 +44,29 @@ void main() {
|
|||||||
float angle = atan(uv.y, uv.x) / (2. * PI) + 0.5;
|
float angle = atan(uv.y, uv.x) / (2. * PI) + 0.5;
|
||||||
uv = vec2(angle * 4. - 0.5, 1.5 - pow(radius, 0.5) * 1.5);
|
uv = vec2(angle * 4. - 0.5, 1.5 - pow(radius, 0.5) * 1.5);
|
||||||
} else {
|
} else {
|
||||||
// Applies the slant, scaling the UV space
|
// Applies the slant and scales space so the viewport is fully covered
|
||||||
// to guarantee the viewport is still covered
|
|
||||||
uv = vec2(
|
uv = vec2(
|
||||||
(uv.x - 0.5) * slantVec.x + (uv.y - 0.5) * slantVec.y,
|
(uv.x - 0.5) * slantVec.x + (uv.y - 0.5) * slantVec.y,
|
||||||
(uv.y - 0.5) * slantVec.x - (uv.x - 0.5) * slantVec.y
|
(uv.y - 0.5) * slantVec.x - (uv.x - 0.5) * slantVec.y
|
||||||
) * slantScale + 0.5;
|
) * slantScale + 0.5;
|
||||||
}
|
}
|
||||||
uv.y /= glyphHeightToWidth;
|
uv.y /= glyphHeightToWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unpack the values from the data texture
|
||||||
vec4 glyph = volumetric ? vGlyph : texture2D(lastState, uv);
|
vec4 glyph = volumetric ? vGlyph : texture2D(lastState, uv);
|
||||||
|
|
||||||
if (showComputationTexture) {
|
|
||||||
gl_FragColor = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpack the values from the font texture
|
|
||||||
float brightness = glyph.r;
|
float brightness = glyph.r;
|
||||||
float symbolIndex = getSymbolIndex(glyph.g);
|
float symbolIndex = getSymbolIndex(glyph.g);
|
||||||
float quadDepth = glyph.b;
|
float quadDepth = glyph.b;
|
||||||
float effect = glyph.a;
|
float effect = glyph.a;
|
||||||
|
|
||||||
brightness = max(effect, brightness);
|
brightness = max(effect, brightness);
|
||||||
|
// In volumetric mode, distant glyphs are dimmer
|
||||||
if (volumetric) {
|
if (volumetric) {
|
||||||
brightness = min(1.0, brightness * quadDepth * 1.25);
|
brightness = brightness * min(1.0, quadDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve UV to MSDF texture coord
|
// resolve UV to position of glyph in MSDF texture
|
||||||
vec2 symbolUV = vec2(mod(symbolIndex, glyphTextureColumns), floor(symbolIndex / glyphTextureColumns));
|
vec2 symbolUV = vec2(mod(symbolIndex, glyphTextureColumns), floor(symbolIndex / glyphTextureColumns));
|
||||||
vec2 glyphUV = fract(uv * vec2(numColumns, numRows));
|
vec2 glyphUV = fract(uv * vec2(numColumns, numRows));
|
||||||
glyphUV -= 0.5;
|
glyphUV -= 0.5;
|
||||||
@@ -79,10 +74,15 @@ void main() {
|
|||||||
glyphUV += 0.5;
|
glyphUV += 0.5;
|
||||||
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureColumns;
|
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureColumns;
|
||||||
|
|
||||||
// MSDF
|
// MSDF: calculate brightness of fragment based on distance to shape
|
||||||
vec3 dist = texture2D(glyphTex, msdfUV).rgb;
|
vec3 dist = texture2D(glyphTex, msdfUV).rgb;
|
||||||
float sigDist = median3(dist) - 0.5;
|
float sigDist = median3(dist) - 0.5;
|
||||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
||||||
|
|
||||||
gl_FragColor = vec4(vChannel * brightness * alpha, 1.0);
|
if (showComputationTexture) {
|
||||||
|
gl_FragColor = vec4(glyph.rgb * alpha, 1.0);
|
||||||
|
} else {
|
||||||
|
gl_FragColor = vec4(vChannel * brightness * alpha, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ void main() {
|
|||||||
vUV = (aPosition + aCorner) * quadSize;
|
vUV = (aPosition + aCorner) * quadSize;
|
||||||
vGlyph = texture2D(lastState, aPosition * quadSize);
|
vGlyph = texture2D(lastState, aPosition * quadSize);
|
||||||
|
|
||||||
|
// Calculate the world space position
|
||||||
float quadDepth = 0.0;
|
float quadDepth = 0.0;
|
||||||
if (volumetric && !showComputationTexture) {
|
if (volumetric && !showComputationTexture) {
|
||||||
quadDepth = fract(vGlyph.b + time * animationSpeed * forwardSpeed);
|
quadDepth = fract(vGlyph.b + time * animationSpeed * forwardSpeed);
|
||||||
@@ -34,16 +35,17 @@ void main() {
|
|||||||
vec2 position = (aPosition + aCorner * vec2(density, 1.)) * quadSize;
|
vec2 position = (aPosition + aCorner * vec2(density, 1.)) * quadSize;
|
||||||
vec4 pos = vec4((position - 0.5) * 2.0, quadDepth, 1.0);
|
vec4 pos = vec4((position - 0.5) * 2.0, quadDepth, 1.0);
|
||||||
|
|
||||||
|
// "Resurrected" columns are in the green channel,
|
||||||
|
// and are vertically flipped (along with their glyphs)
|
||||||
vChannel = vec3(1.0, 0.0, 0.0);
|
vChannel = vec3(1.0, 0.0, 0.0);
|
||||||
|
if (volumetric && rand(vec2(aPosition.x, 0)) < resurrectingCodeRatio) {
|
||||||
|
pos.y = -pos.y;
|
||||||
|
vChannel = vec3(0.0, 1.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the world space position to screen space
|
||||||
if (volumetric) {
|
if (volumetric) {
|
||||||
if (rand(vec2(aPosition.x, 0)) < resurrectingCodeRatio) {
|
|
||||||
pos.y = -pos.y;
|
|
||||||
vChannel = vec3(0.0, 1.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos.x /= glyphHeightToWidth;
|
pos.x /= glyphHeightToWidth;
|
||||||
|
|
||||||
pos = camera * transform * pos;
|
pos = camera * transform * pos;
|
||||||
} else {
|
} else {
|
||||||
pos.xy *= screenSize;
|
pos.xy *= screenSize;
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ highp float rand( const in vec2 uv, const in float t ) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 color = texture2D(stripes, vUV).rgb;
|
vec3 color = texture2D(stripes, vUV).rgb;
|
||||||
|
// Combine the texture and bloom
|
||||||
float brightness = min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r;
|
float brightness = min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r;
|
||||||
float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
|
||||||
gl_FragColor = vec4(color * at + backgroundColor, 1.0);
|
// Dither: subtract a random value from the brightness
|
||||||
|
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||||
|
|
||||||
|
gl_FragColor = vec4(color * brightness + backgroundColor, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user