Cleaned up config by moving its responsibilities into the passes

This commit is contained in:
Rezmason
2020-01-26 13:53:19 -08:00
parent 9a8638976d
commit a48b8dffbe
9 changed files with 249 additions and 229 deletions

View File

@@ -1,34 +1,11 @@
TODO: TODO:
Clean up config.js Deja vu effect: flashing rows
Too many responsibilities Make them flash all the time
Pass-specific properties should be made into uniforms in the passes Then use a thunder-like pattern to show and hide the flash
Reach out to someone about producing sounds Sounds
Raindrop sound Raindrop sound
https://youtu.be/bPhu01wpf0k?t=34
https://youtu.be/Yt2-h13XK7Y?t=33
Deja vu sound Deja vu sound
https://youtu.be/XfEuxRDYiyc?t=17 ripple sound
Square sound
https://youtu.be/ngnlBZNuVb8?t=204
https://youtu.be/-al0i3zSyj0?t=68
https://youtu.be/d8My5jIaXoY?t=191
Neo flying, apparently
https://youtu.be/gZzTVJ-NLYQ?t=230
And some kind of ambient sound that they play over And some kind of ambient sound that they play over
Much later:
Deluxe compute variables
Flashing row effect
Neo flying
Staticky julia set looking silhouette
More patterns?
Symbol duplication is common
Also interesting:
The Matrix code for the Zion Control construct is sparser, slower, bluer, and annotated
https://www.youtube.com/watch?v=Jt5z3OEjDzU

View File

@@ -1,4 +1,10 @@
import { makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js"; import {
extractEntries,
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.
@@ -10,7 +16,14 @@ const levelStrengths = Array(pyramidHeight)
) )
.reverse(); .reverse();
export default (regl, { bloomSize }, input) => { export default (regl, config, input) => {
const uniforms = extractEntries(config, [
"bloomRadius",
"bloomSize",
"bloomStrength",
"highPassThreshold"
]);
const highPassPyramid = makePyramid(regl, pyramidHeight); const highPassPyramid = makePyramid(regl, pyramidHeight);
const hBlurPyramid = makePyramid(regl, pyramidHeight); const hBlurPyramid = makePyramid(regl, pyramidHeight);
const vBlurPyramid = makePyramid(regl, pyramidHeight); const vBlurPyramid = makePyramid(regl, pyramidHeight);
@@ -34,6 +47,7 @@ export default (regl, { bloomSize }, input) => {
} }
`, `,
uniforms: { uniforms: {
...uniforms,
tex: regl.prop("tex") tex: regl.prop("tex")
}, },
framebuffer: regl.prop("fbo") framebuffer: regl.prop("fbo")
@@ -45,10 +59,10 @@ export default (regl, { bloomSize }, input) => {
const blur = regl({ const blur = regl({
frag: ` frag: `
precision mediump float; precision mediump float;
varying vec2 vUV; uniform float width, height;
uniform sampler2D tex; uniform sampler2D tex;
uniform vec2 direction; uniform vec2 direction;
uniform float width, height; varying vec2 vUV;
void main() { void main() {
vec2 size = width > height ? vec2(width / height, 1.) : vec2(1., height / width); vec2 size = width > height ? vec2(width / height, 1.) : vec2(1., height / width);
gl_FragColor = gl_FragColor =
@@ -60,6 +74,7 @@ export default (regl, { bloomSize }, input) => {
} }
`, `,
uniforms: { uniforms: {
...uniforms,
tex: regl.prop("tex"), tex: regl.prop("tex"),
direction: regl.prop("direction"), direction: regl.prop("direction"),
height: regl.context("viewportWidth"), height: regl.context("viewportWidth"),
@@ -90,6 +105,7 @@ export default (regl, { bloomSize }, input) => {
} }
`, `,
uniforms: { uniforms: {
...uniforms,
tex: input, tex: input,
...Object.fromEntries( ...Object.fromEntries(
vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo]) vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo])
@@ -98,8 +114,6 @@ export default (regl, { bloomSize }, input) => {
framebuffer: output framebuffer: output
}); });
const scale = bloomSize;
return makePass( return makePass(
output, output,
() => { () => {
@@ -114,9 +128,9 @@ export default (regl, { bloomSize }, input) => {
}, },
(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, scale); resizePyramid(highPassPyramid, w, h, config.bloomSize);
resizePyramid(hBlurPyramid, w, h, scale); resizePyramid(hBlurPyramid, w, h, config.bloomSize);
resizePyramid(vBlurPyramid, w, h, scale); resizePyramid(vBlurPyramid, w, h, config.bloomSize);
output.resize(w, h); output.resize(w, h);
} }
); );

View File

@@ -17,8 +17,10 @@ const fonts = {
}; };
const defaults = { const defaults = {
animationSpeed: 1,
bloomRadius: 0.5, bloomRadius: 0.5,
bloomStrength: 1, bloomStrength: 1,
bloomSize: 0.5,
highPassThreshold: 0.3, highPassThreshold: 0.3,
cycleSpeed: 1, cycleSpeed: 1,
cycleStyleName: "cycleFasterWhenDimmed", cycleStyleName: "cycleFasterWhenDimmed",
@@ -107,7 +109,7 @@ const versions = {
{ rgb: [1.0, 1.0, 0.9], at: 1.0 } { rgb: [1.0, 1.0, 0.9], at: 1.0 }
], ],
raindropLength: 0.6, raindropLength: 0.6,
slant: 360 / 16 slant: (22.5 * Math.PI) / 180
}, },
paradise: { paradise: {
...defaults, ...defaults,
@@ -136,146 +138,56 @@ const versions = {
versions.throwback = versions.operator; versions.throwback = versions.operator;
versions["1999"] = versions.classic; versions["1999"] = versions.classic;
export default (searchString, make1DTexture) => { const range = (f, min = -Infinity, max = Infinity) =>
const urlParams = new URLSearchParams(searchString); Math.max(min, Math.min(max, f));
const getParam = (keyOrKeys, defaultValue) => { const nullNaN = f => (isNaN(f) ? null : f);
if (Array.isArray(keyOrKeys)) {
const keys = keyOrKeys;
const key = keys.find(key => urlParams.has(key));
return key != null ? urlParams.get(key) : defaultValue;
} else {
const key = keyOrKeys;
return urlParams.has(key) ? urlParams.get(key) : defaultValue;
}
};
const versionName = getParam("version", "classic"); const paramMapping = {
const version = version: { key: "version", parser: s => s },
versions[versionName] == null ? versions.classic : versions[versionName]; effect: { key: "effect", parser: s => s },
width: { key: "numColumns", parser: s => nullNaN(parseInt(s)) },
const config = { ...version }; animationSpeed: {
key: "animationSpeed",
config.animationSpeed = parseFloat(getParam("animationSpeed", 1)); parser: s => nullNaN(parseFloat(s))
config.fallSpeed *= parseFloat(getParam("fallSpeed", 1)); },
config.cycleSpeed *= parseFloat(getParam("cycleSpeed", 1)); cycleSpeed: { key: "cycleSpeed", parser: s => nullNaN(parseFloat(s)) },
config.numColumns = parseInt(getParam("width", config.numColumns)); fallSpeed: { key: "fallSpeed", parser: s => nullNaN(parseFloat(s)) },
config.raindropLength = parseFloat( raindropLength: {
getParam(["raindropLength", "dropLength"], config.raindropLength) key: "raindropLength",
); parser: s => nullNaN(parseFloat(s))
config.glyphSequenceLength = config.glyphSequenceLength; },
config.slant = slant: {
(parseFloat(getParam(["slant", "angle"], config.slant)) * Math.PI) / 180; key: "slant",
config.slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; parser: s => nullNaN((parseFloat(s) * Math.PI) / 180)
config.slantScale = },
1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); bloomSize: {
config.glyphEdgeCrop = parseFloat(getParam("encroach", config.glyphEdgeCrop)); key: "bloomSize",
config.glyphHeightToWidth = parseFloat( parser: s => nullNaN(range(parseFloat(s), 0.01, 1))
getParam("stretch", config.glyphHeightToWidth) },
); url: { key: "bgURL", parser: s => s },
config.cursorEffectThreshold = getParam( colors: { key: "stripeColors", parser: s => s }
"cursorEffectThreshold", };
config.cursorEffectThreshold paramMapping.dropLength = paramMapping.raindropLength;
); paramMapping.angle = paramMapping.slant;
config.bloomSize = Math.max(
0.01, export default (searchString, make1DTexture) => {
Math.min(1, parseFloat(getParam("bloomSize", 0.5))) const urlParams = Object.fromEntries(
); Array.from(new URLSearchParams(searchString).entries())
config.effect = getParam("effect", "plain"); .filter(([key]) => key in paramMapping)
config.bgURL = getParam( .map(([key, value]) => [
"url", paramMapping[key].key,
"https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg" paramMapping[key].parser(value)
); ])
config.customStripes = getParam( .filter(([_, value]) => value != null)
"colors", );
"0.4,0.15,0.1,0.4,0.15,0.1,0.8,0.8,0.6,0.8,0.8,0.6,1.0,0.7,0.8,1.0,0.7,0.8,"
) const version =
.split(",") urlParams.version in versions
.map(parseFloat); ? versions[urlParams.version]
config.showComputationTexture = config.effect === "none"; : versions.classic;
switch (config.cycleStyleName) { return {
case "cycleFasterWhenDimmed": ...version,
config.cycleStyle = 0; ...urlParams
break; };
case "cycleRandomly":
default:
config.cycleStyle = 1;
break;
}
switch (config.rippleTypeName) {
case "box":
config.rippleType = 0;
break;
case "circle":
config.rippleType = 1;
break;
default:
config.rippleType = -1;
}
const PALETTE_SIZE = 2048;
const paletteColors = Array(PALETTE_SIZE);
const sortedEntries = version.paletteEntries
.slice()
.sort((e1, e2) => e1.at - e2.at)
.map(entry => ({
rgb: entry.rgb,
arrayIndex: Math.floor(
Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)
)
}));
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
sortedEntries.push({
rgb: sortedEntries[sortedEntries.length - 1].rgb,
arrayIndex: PALETTE_SIZE - 1
});
sortedEntries.forEach((entry, index) => {
paletteColors[entry.arrayIndex] = entry.rgb.slice();
if (index + 1 < sortedEntries.length) {
const nextEntry = sortedEntries[index + 1];
const diff = nextEntry.arrayIndex - entry.arrayIndex;
for (let i = 0; i < diff; i++) {
const ratio = i / diff;
paletteColors[entry.arrayIndex + i] = [
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio
];
}
}
});
config.palette = make1DTexture(paletteColors.flat().map(i => i * 0xff));
let stripeColors = [0, 0, 0];
if (config.effect === "pride") {
config.effect = "stripes";
config.stripeColors = [
[1, 0, 0],
[1, 0.5, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[0.8, 0, 1]
].flat();
}
if (config.effect === "customStripes" || config.effect === "stripes") {
config.effect = "stripes";
const numStripeColors = Math.floor(config.stripeColors.length / 3);
stripeColors = config.stripeColors.slice(0, numStripeColors * 3);
}
config.stripes = make1DTexture(stripeColors.map(f => Math.floor(f * 0xff)));
const uniforms = Object.fromEntries(
Object.entries(config).filter(([key, value]) => {
const type = typeof (Array.isArray(value) ? value[0] : value);
return type !== "string" && type !== "object";
})
);
return [config, uniforms];
}; };

View File

@@ -1,7 +1,11 @@
import { loadImage, makePassFBO, makePass } from "./utils.js"; import { loadImage, makePassFBO, makePass } from "./utils.js";
export default (regl, { bgURL }, input) => { const defaultBGURL =
"https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
export default (regl, config, input) => {
const output = makePassFBO(regl); const output = makePassFBO(regl);
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const bgLoader = loadImage(regl, bgURL); const bgLoader = loadImage(regl, bgURL);
return makePass( return makePass(
output, output,

View File

@@ -1,4 +1,4 @@
import { makeFullScreenQuad, make1DTexture, makePipeline } from "./utils.js"; import { makeFullScreenQuad, makePipeline } from "./utils.js";
import makeConfig from "./config.js"; import makeConfig from "./config.js";
import makeMatrixRenderer from "./renderer.js"; import makeMatrixRenderer from "./renderer.js";
import makeBloomPass from "./bloomPass.js"; import makeBloomPass from "./bloomPass.js";
@@ -26,13 +26,13 @@ const regl = createREGL({
const effects = { const effects = {
none: null, none: null,
plain: makePalettePass, plain: makePalettePass,
customStripes: makeStripePass,
stripes: makeStripePass, stripes: makeStripePass,
pride: makeStripePass,
image: makeImagePass image: makeImagePass
}; };
const [config, uniforms] = makeConfig(window.location.search, data => const config = makeConfig(window.location.search);
make1DTexture(regl, data)
);
const effect = config.effect in effects ? config.effect : "plain"; const effect = config.effect in effects ? config.effect : "plain";
const resize = () => { const resize = () => {
@@ -44,7 +44,7 @@ resize();
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, uniforms); const fullScreenQuad = makeFullScreenQuad(regl);
const pipeline = makePipeline( const pipeline = makePipeline(
[ [
makeMatrixRenderer, makeMatrixRenderer,
@@ -61,7 +61,8 @@ document.body.onload = async () => {
} }
}); });
await Promise.all(pipeline.map(({ ready }) => ready)); await Promise.all(pipeline.map(({ ready }) => ready));
regl.frame(({ viewportWidth, viewportHeight }) => { const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
// tick.cancel();
pipeline.forEach(({ resize }) => resize(viewportWidth, viewportHeight)); pipeline.forEach(({ resize }) => resize(viewportWidth, viewportHeight));
fullScreenQuad(() => { fullScreenQuad(() => {
pipeline.forEach(({ render }) => render()); pipeline.forEach(({ render }) => render());

View File

@@ -1,4 +1,4 @@
import { makePassFBO, makePass } from "./utils.js"; import { make1DTexture, makePassFBO, makePass } from "./utils.js";
// The rendered texture's values are mapped to colors in a palette texture. // The rendered texture's values are mapped to colors in a palette texture.
// A little noise is introduced, to hide the banding that appears // A little noise is introduced, to hide the banding that appears
@@ -6,8 +6,43 @@ import { makePassFBO, makePass } from "./utils.js";
// won't persist across subsequent frames. This is a safe trick // won't persist across subsequent frames. This is a safe trick
// in screen space. // in screen space.
export default (regl, {}, input) => { export default (regl, config, input) => {
const output = makePassFBO(regl); const output = makePassFBO(regl);
const PALETTE_SIZE = 2048;
const paletteColors = Array(PALETTE_SIZE);
const sortedEntries = config.paletteEntries
.slice()
.sort((e1, e2) => e1.at - e2.at)
.map(entry => ({
rgb: entry.rgb,
arrayIndex: Math.floor(
Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)
)
}));
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
sortedEntries.push({
rgb: sortedEntries[sortedEntries.length - 1].rgb,
arrayIndex: PALETTE_SIZE - 1
});
sortedEntries.forEach((entry, index) => {
paletteColors[entry.arrayIndex] = entry.rgb.slice();
if (index + 1 < sortedEntries.length) {
const nextEntry = sortedEntries[index + 1];
const diff = nextEntry.arrayIndex - entry.arrayIndex;
for (let i = 0; i < diff; i++) {
const ratio = i / diff;
paletteColors[entry.arrayIndex + i] = [
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio
];
}
}
});
const palette = make1DTexture(regl, paletteColors.flat().map(i => i * 0xff));
return makePass( return makePass(
output, output,
regl({ regl({
@@ -35,6 +70,7 @@ export default (regl, {}, input) => {
uniforms: { uniforms: {
tex: input, tex: input,
palette,
ditherMagnitude: 0.05 ditherMagnitude: 0.05
}, },
framebuffer: output framebuffer: output

View File

@@ -1,4 +1,20 @@
import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js"; import {
extractEntries,
loadImage,
makePassFBO,
makeDoubleBuffer,
makePass
} from "./utils.js";
const rippleTypes = {
box: 0,
circle: 1
};
const cycleStyles = {
cycleFasterWhenDimmed: 0,
cycleRandomly: 1
};
export default (regl, config) => { export default (regl, config) => {
// These two framebuffers are used to compute the raining code. // These two framebuffers are used to compute the raining code.
@@ -16,6 +32,47 @@ export default (regl, config) => {
const output = makePassFBO(regl); const output = makePassFBO(regl);
const uniforms = extractEntries(config, [
// rain general
"glyphHeightToWidth",
"glyphTextureColumns",
"numColumns",
// rain update
"animationSpeed",
"brightnessMinimum",
"brightnessMix",
"brightnessMultiplier",
"brightnessOffset",
"cursorEffectThreshold",
"cycleSpeed",
"fallSpeed",
"glyphSequenceLength",
"hasSun",
"hasThunder",
"raindropLength",
"rippleScale",
"rippleSpeed",
"rippleThickness",
// rain render
"glyphEdgeCrop",
"isPolar"
]);
uniforms.rippleType =
config.rippleTypeName in rippleTypes
? rippleTypes[config.rippleTypeName]
: -1;
uniforms.cycleStyle =
config.cycleStyleName in cycleStyles
? cycleStyles[config.cycleStyleName]
: 0;
uniforms.slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
uniforms.slantScale =
1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
uniforms.showComputationTexture = config.effect === "none";
const msdfLoader = loadImage(regl, config.glyphTexURL);
// This shader is the star of the show. // This shader is the star of the show.
// In normal operation, each pixel represents a glyph's: // In normal operation, each pixel represents a glyph's:
// R: brightness // R: brightness
@@ -30,34 +87,19 @@ export default (regl, config) => {
#define SQRT_2 1.4142135623730951 #define SQRT_2 1.4142135623730951
#define SQRT_5 2.23606797749979 #define SQRT_5 2.23606797749979
uniform float time;
uniform float numColumns; uniform float numColumns;
uniform sampler2D lastState; uniform sampler2D lastState;
uniform bool hasSun; uniform bool hasSun;
uniform bool hasThunder; uniform bool hasThunder;
uniform bool showComputationTexture; uniform bool showComputationTexture;
uniform float brightnessMinimum, brightnessMultiplier, brightnessOffset, brightnessMix;
uniform float brightnessMinimum; uniform float animationSpeed, fallSpeed, cycleSpeed;
uniform float brightnessMultiplier;
uniform float brightnessOffset;
uniform float brightnessMix;
uniform float time;
uniform float animationSpeed;
uniform float cycleSpeed;
uniform float fallSpeed;
uniform float raindropLength; uniform float raindropLength;
uniform float glyphHeightToWidth, glyphSequenceLength, glyphTextureColumns;
uniform float glyphHeightToWidth;
uniform float glyphSequenceLength;
uniform float glyphTextureColumns;
uniform int cycleStyle; uniform int cycleStyle;
uniform float rippleScale, rippleSpeed, rippleThickness;
uniform float rippleScale;
uniform float rippleSpeed;
uniform float rippleThickness;
uniform int rippleType; uniform int rippleType;
uniform float cursorEffectThreshold; uniform float cursorEffectThreshold;
float max2(vec2 v) { float max2(vec2 v) {
@@ -88,10 +130,10 @@ export default (regl, config) => {
float getGlyphCycleSpeed(float rainTime, float brightness) { float getGlyphCycleSpeed(float rainTime, float brightness) {
float glyphCycleSpeed = 0.0; float glyphCycleSpeed = 0.0;
if (cycleStyle == 1) { if (cycleStyle == 0 && brightness > 0.0) {
glyphCycleSpeed = fract((rainTime + 0.7 * sin(SQRT_2 * rainTime) + 1.1 * sin(SQRT_5 * rainTime))) * 0.75;
} else if (cycleStyle == 0 && brightness > 0.0) {
glyphCycleSpeed = pow(1.0 - brightness, 4.0); glyphCycleSpeed = pow(1.0 - brightness, 4.0);
} else if (cycleStyle == 1) {
glyphCycleSpeed = fract((rainTime + 0.7 * sin(SQRT_2 * rainTime) + 1.1 * sin(SQRT_5 * rainTime))) * 0.75;
} }
return glyphCycleSpeed; return glyphCycleSpeed;
} }
@@ -207,20 +249,18 @@ export default (regl, config) => {
`, `,
uniforms: { uniforms: {
...uniforms,
lastState: doubleBuffer.back lastState: doubleBuffer.back
}, },
framebuffer: doubleBuffer.front framebuffer: doubleBuffer.front
}); });
const msdfLoader = loadImage(regl, config.glyphTexURL);
// 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 render = regl({ const render = regl({
vert: ` vert: `
attribute vec2 aPosition; attribute vec2 aPosition;
uniform float width; uniform float width, height;
uniform float height;
varying vec2 vUV; varying vec2 vUV;
void main() { void main() {
vUV = aPosition / 2.0 + 0.5; vUV = aPosition / 2.0 + 0.5;
@@ -237,15 +277,11 @@ export default (regl, config) => {
#endif #endif
precision lowp float; precision lowp float;
uniform sampler2D glyphTex;
uniform sampler2D lastState;
uniform float numColumns; uniform float numColumns;
uniform float glyphTextureColumns; uniform sampler2D glyphTex, lastState;
uniform float glyphHeightToWidth, glyphTextureColumns, glyphEdgeCrop;
uniform vec2 slantVec; uniform vec2 slantVec;
uniform float slantScale; uniform float slantScale;
uniform float glyphHeightToWidth;
uniform float glyphEdgeCrop;
uniform bool isPolar; uniform bool isPolar;
uniform bool showComputationTexture; uniform bool showComputationTexture;
@@ -309,6 +345,7 @@ export default (regl, config) => {
`, `,
uniforms: { uniforms: {
...uniforms,
glyphTex: msdfLoader.texture, glyphTex: msdfLoader.texture,
height: regl.context("viewportWidth"), height: regl.context("viewportWidth"),
width: regl.context("viewportHeight"), width: regl.context("viewportHeight"),

View File

@@ -1,7 +1,38 @@
import { makePassFBO, makePass } from "./utils.js"; import { make1DTexture, makePassFBO, makePass } from "./utils.js";
export default (regl, {}, input) => { const neapolitanStripeColors = [
[0.4, 0.15, 0.1],
[0.4, 0.15, 0.1],
[0.8, 0.8, 0.6],
[0.8, 0.8, 0.6],
[1.0, 0.7, 0.8],
[1.0, 0.7, 0.8]
].flat();
const prideStripeColors = [
[1, 0, 0],
[1, 0.5, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[0.8, 0, 1]
].flat();
export default (regl, config, input) => {
const output = makePassFBO(regl); const output = makePassFBO(regl);
const stripeColors =
"stripeColors" in config
? config.stripeColors.split(",").map(parseFloat)
: config.effect === "pride"
? prideStripeColors
: neapolitanStripeColors;
const numStripeColors = Math.floor(stripeColors.length / 3);
const stripes = make1DTexture(
regl,
stripeColors.slice(0, numStripeColors * 3).map(f => Math.floor(f * 0xff))
);
return makePass( return makePass(
output, output,
regl({ regl({
@@ -12,24 +43,26 @@ export default (regl, {}, input) => {
uniform sampler2D tex; uniform sampler2D tex;
uniform sampler2D stripes; uniform sampler2D stripes;
uniform float ditherMagnitude; uniform float ditherMagnitude;
uniform float time;
varying vec2 vUV; varying vec2 vUV;
highp float rand( const in vec2 uv ) { highp float rand( const in vec2 uv, const in float t ) {
const highp float a = 12.9898, b = 78.233, c = 43758.5453; 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 ); highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );
return fract(sin(sn) * c); return fract(sin(sn) * c + t);
} }
void main() { void main() {
vec3 color = texture2D(stripes, vUV).rgb - rand( gl_FragCoord.xy ) * ditherMagnitude; vec3 color = texture2D(stripes, vUV).rgb;
float brightness = texture2D(tex, vUV).r; float brightness = texture2D(tex, vUV).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
gl_FragColor = vec4(color * brightness, 1.0); gl_FragColor = vec4(color * brightness, 1.0);
} }
`, `,
uniforms: { uniforms: {
tex: input, tex: input,
ditherMagnitude: 0.1 stripes,
ditherMagnitude: 0.05
}, },
framebuffer: output framebuffer: output
}) })

View File

@@ -1,3 +1,8 @@
const extractEntries = (src, keys) =>
Object.fromEntries(
Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))
);
const makePassTexture = regl => const makePassTexture = regl =>
regl.texture({ regl.texture({
width: 1, width: 1,
@@ -167,6 +172,7 @@ const makePipeline = (steps, getInput, ...params) =>
); );
export { export {
extractEntries,
makePassTexture, makePassTexture,
makePassFBO, makePassFBO,
makeDoubleBuffer, makeDoubleBuffer,