Ran all the JS through prettier.

This commit is contained in:
Rezmason
2021-10-20 03:25:04 -07:00
parent d8a1409907
commit 91deea34d6
10 changed files with 776 additions and 864 deletions

View File

@@ -1,127 +1,103 @@
import {
loadText,
extractEntries,
makePassFBO,
makePyramid,
resizePyramid,
makePass
} from "./utils.js";
import { loadText, extractEntries, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js";
// The bloom pass is basically an added high-pass blur.
const pyramidHeight = 5;
const levelStrengths = Array(pyramidHeight)
.fill()
.map((_, index) =>
Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5)
)
.reverse();
.fill()
.map((_, index) => Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5))
.reverse();
export default (regl, config, inputs) => {
const enabled = config.bloomSize > 0 && config.bloomStrength > 0;
const enabled = config.bloomSize > 0 && config.bloomStrength > 0;
if (!enabled) {
return makePass({
primary: inputs.primary,
bloom: makePassFBO(regl),
});
}
if (!enabled) {
return makePass(
{
primary: inputs.primary,
bloom: makePassFBO(regl)
}
);
}
const uniforms = extractEntries(config, ["bloomStrength", "highPassThreshold"]);
const uniforms = extractEntries(config, [
"bloomStrength",
"highPassThreshold"
]);
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const output = makePassFBO(regl, config.useHalfFloat);
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
const output = makePassFBO(regl, config.useHalfFloat);
const highPassFrag = loadText("../shaders/highPass.frag");
const highPassFrag = loadText("../shaders/highPass.frag");
// The high pass restricts the blur to bright things in our input texture.
const highPass = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
tex: regl.prop("tex"),
},
framebuffer: regl.prop("fbo"),
});
// The high pass restricts the blur to bright things in our input texture.
const highPass = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
tex: regl.prop("tex")
},
framebuffer: regl.prop("fbo")
});
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
// The FBO pyramid's levels represent separate levels of detail;
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
// The FBO pyramid's levels represent separate levels of detail;
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
const blurFrag = loadText("../shaders/blur.frag");
const blur = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
tex: regl.prop("tex"),
direction: regl.prop("direction"),
height: regl.context("viewportWidth"),
width: regl.context("viewportHeight"),
},
framebuffer: regl.prop("fbo"),
});
const blurFrag = loadText("../shaders/blur.frag");
const blur = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
tex: regl.prop("tex"),
direction: regl.prop("direction"),
height: regl.context("viewportWidth"),
width: regl.context("viewportHeight")
},
framebuffer: regl.prop("fbo")
});
// The pyramid of textures gets flattened onto the source texture.
const flattenPyramid = regl({
frag: `
precision mediump float;
varying vec2 vUV;
${vBlurPyramid.map((_, index) => `uniform sampler2D pyr_${index};`).join("\n")}
uniform float bloomStrength;
void main() {
vec4 total = vec4(0.);
${vBlurPyramid.map((_, index) => `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};`).join("\n")}
gl_FragColor = total * bloomStrength;
}
`,
uniforms: {
...uniforms,
...Object.fromEntries(vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo])),
},
framebuffer: output,
});
// The pyramid of textures gets flattened onto the source texture.
const flattenPyramid = regl({
frag: `
precision mediump float;
varying vec2 vUV;
${vBlurPyramid
.map((_, index) => `uniform sampler2D pyr_${index};`)
.join("\n")}
uniform float bloomStrength;
void main() {
vec4 total = vec4(0.);
${vBlurPyramid
.map(
(_, index) =>
`total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};`
)
.join("\n")}
gl_FragColor = total * bloomStrength;
}
`,
uniforms: {
...uniforms,
...Object.fromEntries(
vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo])
)
},
framebuffer: output
});
return makePass(
{
primary: inputs.primary,
bloom: output,
},
() => {
for (let i = 0; i < pyramidHeight; i++) {
const highPassFBO = highPassPyramid[i];
const hBlurFBO = hBlurPyramid[i];
const vBlurFBO = vBlurPyramid[i];
highPass({ fbo: highPassFBO, frag: highPassFrag.text(), tex: inputs.primary });
blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] });
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
}
return makePass(
{
primary: inputs.primary,
bloom: output
},
() => {
for (let i = 0; i < pyramidHeight; i++) {
const highPassFBO = highPassPyramid[i];
const hBlurFBO = hBlurPyramid[i];
const vBlurFBO = vBlurPyramid[i];
highPass({ fbo: highPassFBO, frag: highPassFrag.text(), tex: inputs.primary });
blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] });
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
}
flattenPyramid();
},
(w, h) => {
// The blur pyramids can be lower resolution than the screen.
resizePyramid(highPassPyramid, w, h, config.bloomSize);
resizePyramid(hBlurPyramid, w, h, config.bloomSize);
resizePyramid(vBlurPyramid, w, h, config.bloomSize);
output.resize(w, h);
},
[highPassFrag.laoded, blurFrag.loaded]
);
flattenPyramid();
},
(w, h) => {
// The blur pyramids can be lower resolution than the screen.
resizePyramid(highPassPyramid, w, h, config.bloomSize);
resizePyramid(hBlurPyramid, w, h, config.bloomSize);
resizePyramid(vBlurPyramid, w, h, config.bloomSize);
output.resize(w, h);
},
[highPassFrag.laoded, blurFrag.loaded]
);
};

View File

@@ -1,204 +1,197 @@
const fonts = {
coptic: {
glyphTexURL: "coptic_msdf.png",
glyphSequenceLength: 32,
glyphTextureColumns: 8
},
gothic: {
glyphTexURL: "gothic_msdf.png",
glyphSequenceLength: 27,
glyphTextureColumns: 8
},
matrixcode: {
glyphTexURL: "matrixcode_msdf.png",
glyphSequenceLength: 57,
glyphTextureColumns: 8
}
coptic: {
glyphTexURL: "coptic_msdf.png",
glyphSequenceLength: 32,
glyphTextureColumns: 8,
},
gothic: {
glyphTexURL: "gothic_msdf.png",
glyphSequenceLength: 27,
glyphTextureColumns: 8,
},
matrixcode: {
glyphTexURL: "matrixcode_msdf.png",
glyphSequenceLength: 57,
glyphTextureColumns: 8,
},
};
const defaults = {
backgroundColor: [0, 0, 0],
volumetric: false,
resurrectingCodeRatio: 0,
animationSpeed: 1,
forwardSpeed: 0.25,
bloomStrength: 1,
bloomSize: 0.6,
highPassThreshold: 0.1,
cycleSpeed: 1,
cycleStyleName: "cycleFasterWhenDimmed",
cursorEffectThreshold: 1,
brightnessOffset: 0.0,
brightnessMultiplier: 1.0,
brightnessMix: 1.0,
brightnessMinimum: 0,
fallSpeed: 1,
glyphEdgeCrop: 0.0,
glyphHeightToWidth: 1,
hasSun: false,
hasThunder: false,
isPolar: false,
rippleTypeName: null,
rippleThickness: 0.2,
rippleScale: 30,
rippleSpeed: 0.2,
numColumns: 80,
density: 1,
paletteEntries: [
{ hsl: [0.3, 0.9, 0.0], at: 0.0 },
{ hsl: [0.3, 0.9, 0.2], at: 0.2 },
{ hsl: [0.3, 0.9, 0.7], at: 0.7 },
{ hsl: [0.3, 0.9, 0.8], at: 0.8 }
],
raindropLength: 1,
slant: 0,
resolution: 1,
useHalfFloat: false,
backgroundColor: [0, 0, 0],
volumetric: false,
resurrectingCodeRatio: 0,
animationSpeed: 1,
forwardSpeed: 0.25,
bloomStrength: 1,
bloomSize: 0.6,
highPassThreshold: 0.1,
cycleSpeed: 1,
cycleStyleName: "cycleFasterWhenDimmed",
cursorEffectThreshold: 1,
brightnessOffset: 0.0,
brightnessMultiplier: 1.0,
brightnessMix: 1.0,
brightnessMinimum: 0,
fallSpeed: 1,
glyphEdgeCrop: 0.0,
glyphHeightToWidth: 1,
hasSun: false,
hasThunder: false,
isPolar: false,
rippleTypeName: null,
rippleThickness: 0.2,
rippleScale: 30,
rippleSpeed: 0.2,
numColumns: 80,
density: 1,
paletteEntries: [
{ hsl: [0.3, 0.9, 0.0], at: 0.0 },
{ hsl: [0.3, 0.9, 0.2], at: 0.2 },
{ hsl: [0.3, 0.9, 0.7], at: 0.7 },
{ hsl: [0.3, 0.9, 0.8], at: 0.8 },
],
raindropLength: 1,
slant: 0,
resolution: 1,
useHalfFloat: false,
};
const versions = {
classic: {
...defaults,
...fonts.matrixcode
},
operator: {
...defaults,
...fonts.matrixcode,
bloomStrength: 0.75,
highPassThreshold: 0.0,
cycleSpeed: 0.05,
cycleStyleName: "cycleRandomly",
cursorEffectThreshold: 0.64,
brightnessOffset: 0.25,
brightnessMultiplier: 0.0,
brightnessMinimum: -1.0,
fallSpeed: 0.65,
glyphEdgeCrop: 0.15,
glyphHeightToWidth: 1.35,
rippleTypeName: "box",
numColumns: 108,
paletteEntries: [
{ hsl: [0.4, 0.8, 0.0], at: 0.0 },
{ hsl: [0.4, 0.8, 0.5], at: 0.5 },
{ hsl: [0.4, 0.8, 1.0], at: 1.0 }
],
raindropLength: 1.5
},
nightmare: {
...defaults,
...fonts.gothic,
highPassThreshold: 0.7,
brightnessMix: 0.75,
fallSpeed: 2.0,
hasThunder: true,
numColumns: 60,
paletteEntries: [
{ hsl: [0.0, 1.0, 0.0], at: 0.0 },
{ hsl: [0.0, 1.0, 0.2], at: 0.2 },
{ hsl: [0.0, 1.0, 0.4], at: 0.4 },
{ hsl: [0.1, 1.0, 0.7], at: 0.7 },
{ hsl: [0.2, 1.0, 1.0], at: 1.0 }
],
raindropLength: 0.6,
slant: (22.5 * Math.PI) / 180
},
paradise: {
...defaults,
...fonts.coptic,
bloomStrength: 1.75,
highPassThreshold: 0,
cycleSpeed: 0.1,
brightnessMix: 0.05,
fallSpeed: 0.08,
hasSun: true,
isPolar: true,
rippleTypeName: "circle",
rippleSpeed: 0.1,
numColumns: 30,
paletteEntries: [
{ hsl: [0.0, 0.0, 0.0], at: 0.0 },
{ hsl: [0.0, 0.8, 0.3], at: 0.3 },
{ hsl: [0.1, 0.8, 0.5], at: 0.5 },
{ hsl: [0.1, 1.0, 0.6], at: 0.6 },
{ hsl: [0.1, 1.0, 0.9], at: 0.9 }
],
raindropLength: 0.4
},
resurrections: {
...defaults,
...fonts.matrixcode,
resurrectingCodeRatio: 0.25,
effect:"resurrections",
width:100,
volumetric:true,
density:1.5,
fallSpeed:1.2,
raindropLength:1.25
}
classic: {
...defaults,
...fonts.matrixcode,
},
operator: {
...defaults,
...fonts.matrixcode,
bloomStrength: 0.75,
highPassThreshold: 0.0,
cycleSpeed: 0.05,
cycleStyleName: "cycleRandomly",
cursorEffectThreshold: 0.64,
brightnessOffset: 0.25,
brightnessMultiplier: 0.0,
brightnessMinimum: -1.0,
fallSpeed: 0.65,
glyphEdgeCrop: 0.15,
glyphHeightToWidth: 1.35,
rippleTypeName: "box",
numColumns: 108,
paletteEntries: [
{ hsl: [0.4, 0.8, 0.0], at: 0.0 },
{ hsl: [0.4, 0.8, 0.5], at: 0.5 },
{ hsl: [0.4, 0.8, 1.0], at: 1.0 },
],
raindropLength: 1.5,
},
nightmare: {
...defaults,
...fonts.gothic,
highPassThreshold: 0.7,
brightnessMix: 0.75,
fallSpeed: 2.0,
hasThunder: true,
numColumns: 60,
paletteEntries: [
{ hsl: [0.0, 1.0, 0.0], at: 0.0 },
{ hsl: [0.0, 1.0, 0.2], at: 0.2 },
{ hsl: [0.0, 1.0, 0.4], at: 0.4 },
{ hsl: [0.1, 1.0, 0.7], at: 0.7 },
{ hsl: [0.2, 1.0, 1.0], at: 1.0 },
],
raindropLength: 0.6,
slant: (22.5 * Math.PI) / 180,
},
paradise: {
...defaults,
...fonts.coptic,
bloomStrength: 1.75,
highPassThreshold: 0,
cycleSpeed: 0.1,
brightnessMix: 0.05,
fallSpeed: 0.08,
hasSun: true,
isPolar: true,
rippleTypeName: "circle",
rippleSpeed: 0.1,
numColumns: 30,
paletteEntries: [
{ hsl: [0.0, 0.0, 0.0], at: 0.0 },
{ hsl: [0.0, 0.8, 0.3], at: 0.3 },
{ hsl: [0.1, 0.8, 0.5], at: 0.5 },
{ hsl: [0.1, 1.0, 0.6], at: 0.6 },
{ hsl: [0.1, 1.0, 0.9], at: 0.9 },
],
raindropLength: 0.4,
},
resurrections: {
...defaults,
...fonts.matrixcode,
resurrectingCodeRatio: 0.25,
effect: "resurrections",
width: 100,
volumetric: true,
density: 1.5,
fallSpeed: 1.2,
raindropLength: 1.25,
},
};
versions.throwback = versions.operator;
versions["1999"] = versions.classic;
const range = (f, min = -Infinity, max = Infinity) =>
Math.max(min, Math.min(max, f));
const nullNaN = f => (isNaN(f) ? null : f);
const range = (f, min = -Infinity, max = Infinity) => Math.max(min, Math.min(max, f));
const nullNaN = (f) => (isNaN(f) ? null : f);
const paramMapping = {
version: { key: "version", parser: s => s },
effect: { key: "effect", parser: s => s },
width: { key: "numColumns", parser: s => nullNaN(parseInt(s)) },
numColumns: { key: "numColumns", parser: s => nullNaN(parseInt(s)) },
density: { key: "density", parser: s => nullNaN(range(parseFloat(s), 0)) },
resolution: { key: "resolution", parser: s => nullNaN(parseFloat(s)) },
animationSpeed: {
key: "animationSpeed",
parser: s => nullNaN(parseFloat(s))
},
forwardSpeed: {
key: "forwardSpeed",
parser: s => nullNaN(parseFloat(s))
},
cycleSpeed: { key: "cycleSpeed", parser: s => nullNaN(parseFloat(s)) },
fallSpeed: { key: "fallSpeed", parser: s => nullNaN(parseFloat(s)) },
raindropLength: {
key: "raindropLength",
parser: s => nullNaN(parseFloat(s))
},
slant: {
key: "slant",
parser: s => nullNaN((parseFloat(s) * Math.PI) / 180)
},
bloomSize: {
key: "bloomSize",
parser: s => nullNaN(range(parseFloat(s), 0, 1))
},
url: { key: "bgURL", parser: s => s },
stripeColors: { key: "stripeColors", parser: s => s },
backgroundColor: { key: "backgroundColor", parser: s => s.split(",").map(parseFloat) },
volumetric: { key: "volumetric", parser: s => s.toLowerCase().includes("true") }
version: { key: "version", parser: (s) => s },
effect: { key: "effect", parser: (s) => s },
width: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) },
numColumns: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) },
density: { key: "density", parser: (s) => nullNaN(range(parseFloat(s), 0)) },
resolution: { key: "resolution", parser: (s) => nullNaN(parseFloat(s)) },
animationSpeed: {
key: "animationSpeed",
parser: (s) => nullNaN(parseFloat(s)),
},
forwardSpeed: {
key: "forwardSpeed",
parser: (s) => nullNaN(parseFloat(s)),
},
cycleSpeed: { key: "cycleSpeed", parser: (s) => nullNaN(parseFloat(s)) },
fallSpeed: { key: "fallSpeed", parser: (s) => nullNaN(parseFloat(s)) },
raindropLength: {
key: "raindropLength",
parser: (s) => nullNaN(parseFloat(s)),
},
slant: {
key: "slant",
parser: (s) => nullNaN((parseFloat(s) * Math.PI) / 180),
},
bloomSize: {
key: "bloomSize",
parser: (s) => nullNaN(range(parseFloat(s), 0, 1)),
},
url: { key: "bgURL", parser: (s) => s },
stripeColors: { key: "stripeColors", parser: (s) => s },
backgroundColor: { key: "backgroundColor", parser: (s) => s.split(",").map(parseFloat) },
volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") },
};
paramMapping.dropLength = paramMapping.raindropLength;
paramMapping.angle = paramMapping.slant;
paramMapping.colors = paramMapping.stripeColors;
export default (searchString, make1DTexture) => {
const urlParams = Object.fromEntries(
Array.from(new URLSearchParams(searchString).entries())
.filter(([key]) => key in paramMapping)
.map(([key, value]) => [
paramMapping[key].key,
paramMapping[key].parser(value)
])
.filter(([_, value]) => value != null)
);
const urlParams = Object.fromEntries(
Array.from(new URLSearchParams(searchString).entries())
.filter(([key]) => key in paramMapping)
.map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)])
.filter(([_, value]) => value != null)
);
const version =
urlParams.version in versions
? versions[urlParams.version]
: versions.classic;
const version = urlParams.version in versions ? versions[urlParams.version] : versions.classic;
return {
...version,
...urlParams
};
return {
...version,
...urlParams,
};
};

View File

@@ -1,28 +1,27 @@
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
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) => {
const output = makePassFBO(regl, config.useHalfFloat);
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const background = loadImage(regl, bgURL);
const imagePassFrag = loadText("../shaders/imagePass.frag");
const render = regl({
frag: regl.prop("frag"),
uniforms: {
backgroundTex: background.texture,
tex: inputs.primary,
bloomTex: inputs.bloom
},
framebuffer: output
});
return makePass(
{
primary: output
},
() => render({frag: imagePassFrag.text()}),
null,
[background.loaded, imagePassFrag.loaded]
);
const output = makePassFBO(regl, config.useHalfFloat);
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
const background = loadImage(regl, bgURL);
const imagePassFrag = loadText("../shaders/imagePass.frag");
const render = regl({
frag: regl.prop("frag"),
uniforms: {
backgroundTex: background.texture,
tex: inputs.primary,
bloomTex: inputs.bloom,
},
framebuffer: output,
});
return makePass(
{
primary: output,
},
() => render({ frag: imagePassFrag.text() }),
null,
[background.loaded, imagePassFrag.loaded]
);
};

View File

@@ -9,30 +9,26 @@ import makeResurrectionPass from "./resurrectionPass.js";
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
document.addEventListener("touchmove", e => e.preventDefault(), {
passive: false
document.addEventListener("touchmove", (e) => e.preventDefault(), {
passive: false,
});
const regl = createREGL({
canvas,
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
// These extensions are also needed, but Safari misreports that they are missing
optionalExtensions: [
"EXT_color_buffer_half_float",
"WEBGL_color_buffer_float",
"OES_standard_derivatives"
]
canvas,
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
// These extensions are also needed, but Safari misreports that they are missing
optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"],
});
const effects = {
none: null,
plain: makePalettePass,
customStripes: makeStripePass,
stripes: makeStripePass,
pride: makeStripePass,
image: makeImagePass,
resurrection: makeResurrectionPass,
resurrections: makeResurrectionPass
none: null,
plain: makePalettePass,
customStripes: makeStripePass,
stripes: makeStripePass,
pride: makeStripePass,
image: makeImagePass,
resurrection: makeResurrectionPass,
resurrections: makeResurrectionPass,
};
const config = makeConfig(window.location.search);
@@ -40,8 +36,8 @@ const resolution = config.resolution;
const effect = config.effect in effects ? config.effect : "plain";
const resize = () => {
canvas.width = Math.ceil(canvas.clientWidth * resolution);
canvas.height = Math.ceil(canvas.clientHeight * resolution);
canvas.width = Math.ceil(canvas.clientWidth * resolution);
canvas.height = Math.ceil(canvas.clientHeight * resolution);
};
window.onresize = resize;
resize();
@@ -49,41 +45,29 @@ resize();
const dimensions = { width: 1, height: 1 };
document.body.onload = async () => {
// All this takes place in a full screen quad.
const fullScreenQuad = makeFullScreenQuad(regl);
const pipeline = makePipeline(
[
makeMatrixRenderer,
effect === "none" ? null : makeBloomPass,
effects[effect]
],
p => p.outputs,
regl,
config
);
const drawToScreen = regl({
uniforms: {
tex: pipeline[pipeline.length - 1].outputs.primary
}
});
await Promise.all(pipeline.map(({ ready }) => ready));
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
// tick.cancel();
if (
dimensions.width !== viewportWidth ||
dimensions.height !== viewportHeight
) {
dimensions.width = viewportWidth;
dimensions.height = viewportHeight;
for (const step of pipeline) {
step.resize(viewportWidth, viewportHeight);
}
}
fullScreenQuad(() => {
for (const step of pipeline) {
step.render();
}
drawToScreen();
});
});
// All this takes place in a full screen quad.
const fullScreenQuad = makeFullScreenQuad(regl);
const pipeline = makePipeline([makeMatrixRenderer, effect === "none" ? null : makeBloomPass, effects[effect]], (p) => p.outputs, regl, config);
const drawToScreen = regl({
uniforms: {
tex: pipeline[pipeline.length - 1].outputs.primary,
},
});
await Promise.all(pipeline.map(({ ready }) => ready));
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
// tick.cancel();
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
dimensions.width = viewportWidth;
dimensions.height = viewportHeight;
for (const step of pipeline) {
step.resize(viewportWidth, viewportHeight);
}
}
fullScreenQuad(() => {
for (const step of pipeline) {
step.render();
}
drawToScreen();
});
});
};

View File

@@ -1,51 +1,49 @@
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
const colorToRGB = ([hue, saturation, lightness]) => {
const a = saturation * Math.min(lightness, 1 - lightness);
const f = (n) => {
const k = (n + hue * 12) % 12;
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
};
return [f(0), f(8), f(4)];
const a = saturation * Math.min(lightness, 1 - lightness);
const f = (n) => {
const k = (n + hue * 12) % 12;
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
};
return [f(0), f(8), f(4)];
};
const makePalette = (regl, entries) => {
const PALETTE_SIZE = 2048;
const paletteColors = Array(PALETTE_SIZE);
const sortedEntries = entries
.slice()
.sort((e1, e2) => e1.at - e2.at)
.map(entry => ({
rgb: colorToRGB(entry.hsl),
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_SIZE = 2048;
const paletteColors = Array(PALETTE_SIZE);
const sortedEntries = entries
.slice()
.sort((e1, e2) => e1.at - e2.at)
.map((entry) => ({
rgb: colorToRGB(entry.hsl),
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,
];
}
}
});
return make1DTexture(
regl,
paletteColors.flat().map(i => i * 0xff)
);
return make1DTexture(
regl,
paletteColors.flat().map((i) => i * 0xff)
);
};
// The rendered texture's values are mapped to colors in a palette texture.
@@ -55,32 +53,30 @@ const makePalette = (regl, entries) => {
// in screen space.
export default (regl, config, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const palette = makePalette(regl, config.paletteEntries);
const output = makePassFBO(regl, config.useHalfFloat);
const palette = makePalette(regl, config.paletteEntries);
const palettePassFrag = loadText("../shaders/palettePass.frag");
const palettePassFrag = loadText("../shaders/palettePass.frag");
const render = regl({
frag: regl.prop("frag"),
const render = regl({
frag: regl.prop("frag"),
uniforms: {
...extractEntries(config, [
"backgroundColor",
]),
tex: inputs.primary,
bloomTex: inputs.bloom,
palette,
ditherMagnitude: 0.05
},
framebuffer: output
});
uniforms: {
...extractEntries(config, ["backgroundColor"]),
tex: inputs.primary,
bloomTex: inputs.bloom,
palette,
ditherMagnitude: 0.05,
},
framebuffer: output,
});
return makePass(
{
primary: output
},
() => render({ frag: palettePassFrag.text() }),
null,
palettePassFrag.loaded
);
return makePass(
{
primary: output,
},
() => render({ frag: palettePassFrag.text() }),
null,
palettePassFrag.loaded
);
};

View File

@@ -1,187 +1,180 @@
import {
extractEntries,
loadImage,
loadText,
makePassFBO,
makeDoubleBuffer,
makePass
} from "./utils.js";
import { extractEntries, loadImage, loadText, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
const rippleTypes = {
box: 0,
circle: 1
box: 0,
circle: 1,
};
const cycleStyles = {
cycleFasterWhenDimmed: 0,
cycleRandomly: 1
cycleFasterWhenDimmed: 0,
cycleRandomly: 1,
};
const numVerticesPerQuad = 2 * 3;
export default (regl, config) => {
const volumetric = config.volumetric;
const density = volumetric && config.effect !== "none" ? config.density : 1;
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
const numQuads = numQuadRows * numQuadColumns;
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
const volumetric = config.volumetric;
const density = volumetric && config.effect !== "none" ? config.density : 1;
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
const numQuads = numQuadRows * numQuadColumns;
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
// These two framebuffers are used to compute the raining code.
// 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,
// whereas the default type limits us to integers between 0 and 255.
// These two framebuffers are used to compute the raining code.
// 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,
// whereas the default type limits us to integers between 0 and 255.
// This double buffer is smaller than the screen, because its pixels correspond
// with glyphs in the final image, and the glyphs are much larger than a pixel.
const doubleBuffer = makeDoubleBuffer(regl, {
width: numColumns,
height: numRows,
wrapT: "clamp",
type: "half float",
});
// This double buffer is smaller than the screen, because its pixels correspond
// with glyphs in the final image, and the glyphs are much larger than a pixel.
const doubleBuffer = makeDoubleBuffer(regl, {
width: numColumns,
height: numRows,
wrapT: "clamp",
type: "half float"
});
const output = makePassFBO(regl, config.useHalfFloat);
const uniforms = {
...extractEntries(config, [
// rain general
"glyphHeightToWidth",
"glyphTextureColumns",
// rain update
"animationSpeed",
"brightnessMinimum",
"brightnessMix",
"brightnessMultiplier",
"brightnessOffset",
"cursorEffectThreshold",
"cycleSpeed",
"fallSpeed",
"glyphSequenceLength",
"hasSun",
"hasThunder",
"raindropLength",
"rippleScale",
"rippleSpeed",
"rippleThickness",
"resurrectingCodeRatio",
// rain vertex
"forwardSpeed",
// rain render
"glyphEdgeCrop",
"isPolar",
]),
density,
numRows,
numColumns,
numQuadRows,
numQuadColumns,
quadSize,
volumetric,
};
const output = makePassFBO(regl, config.useHalfFloat);
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 uniforms = {
...extractEntries(config, [
// rain general
"glyphHeightToWidth",
"glyphTextureColumns",
// rain update
"animationSpeed",
"brightnessMinimum",
"brightnessMix",
"brightnessMultiplier",
"brightnessOffset",
"cursorEffectThreshold",
"cycleSpeed",
"fallSpeed",
"glyphSequenceLength",
"hasSun",
"hasThunder",
"raindropLength",
"rippleScale",
"rippleSpeed",
"rippleThickness",
"resurrectingCodeRatio",
// rain vertex
"forwardSpeed",
// rain render
"glyphEdgeCrop",
"isPolar",
]),
density,
numRows,
numColumns,
numQuadRows,
numQuadColumns,
quadSize,
volumetric
};
const msdf = loadImage(regl, config.glyphTexURL);
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 updateFrag = loadText("../shaders/update.frag");
const update = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
lastState: doubleBuffer.back,
},
const msdf = loadImage(regl, config.glyphTexURL);
framebuffer: doubleBuffer.front,
});
const updateFrag = loadText("../shaders/update.frag");
const update = regl({
frag: regl.prop("frag"),
uniforms: {
...uniforms,
lastState: doubleBuffer.back
},
const quadPositions = Array(numQuadRows)
.fill()
.map((_, y) =>
Array(numQuadColumns)
.fill()
.map((_, x) => Array(numVerticesPerQuad).fill([x, y]))
);
framebuffer: doubleBuffer.front
});
const quadCorners = Array(numQuads).fill([
[0, 0],
[0, 1],
[1, 1],
[0, 0],
[1, 1],
[1, 0],
]);
const quadPositions = Array(numQuadRows).fill().map((_, y) =>
Array(numQuadColumns).fill().map((_, x) =>
Array(numVerticesPerQuad).fill([x, y])
)
);
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
const renderVert = loadText("../shaders/render.vert");
const renderFrag = loadText("../shaders/render.frag");
const render = regl({
blend: {
enable: true,
func: {
srcRGB: "src alpha",
srcAlpha: 1,
dstRGB: "dst alpha",
dstAlpha: 1,
},
},
vert: regl.prop("vert"),
frag: regl.prop("frag"),
const quadCorners = Array(numQuads).fill([[0, 0], [0, 1], [1, 1], [0, 0], [1, 1], [1, 0]]);
uniforms: {
...uniforms,
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
const renderVert = loadText("../shaders/render.vert");
const renderFrag = loadText("../shaders/render.frag");
const render = regl({
blend: {
enable: true,
func: {
srcRGB: "src alpha",
srcAlpha: 1,
dstRGB: "dst alpha",
dstAlpha: 1
}
},
vert: regl.prop("vert"),
frag: regl.prop("frag"),
lastState: doubleBuffer.front,
glyphTex: msdf.texture,
uniforms: {
...uniforms,
camera: regl.prop("camera"),
transform: regl.prop("transform"),
screenSize: regl.prop("screenSize"),
},
lastState: doubleBuffer.front,
glyphTex: msdf.texture,
attributes: {
aPosition: quadPositions,
aCorner: quadCorners,
},
count: numQuads * numVerticesPerQuad,
camera: regl.prop("camera"),
transform: regl.prop("transform"),
screenSize: regl.prop("screenSize")
},
framebuffer: output,
});
attributes: {
aPosition: quadPositions,
aCorner: quadCorners
},
count: numQuads * numVerticesPerQuad,
const screenSize = [1, 1];
const { mat4, vec3 } = glMatrix;
const camera = mat4.create();
const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1);
const scale = vec3.set(vec3.create(), 1, 1, 1);
const transform = mat4.create();
mat4.translate(transform, transform, translation);
mat4.scale(transform, transform, scale);
framebuffer: output
});
return makePass(
{
primary: output,
},
() => {
const time = Date.now();
const screenSize = [1, 1];
const {mat4, vec3} = glMatrix;
const camera = mat4.create();
const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1);
const scale = vec3.set(vec3.create(), 1, 1, 1);
const transform = mat4.create();
mat4.translate(transform, transform, translation);
mat4.scale(transform, transform, scale);
return makePass(
{
primary: output
},
() => {
const time = Date.now();
update({frag: updateFrag.text()});
regl.clear({
depth: 1,
color: [0, 0, 0, 1],
framebuffer: output
});
render({camera, transform, screenSize, vert: renderVert.text(), frag: renderFrag.text()});
},
(w, h) => {
output.resize(w, h);
const aspectRatio = w / h;
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
},
[msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded]
);
update({ frag: updateFrag.text() });
regl.clear({
depth: 1,
color: [0, 0, 0, 1],
framebuffer: output,
});
render({ camera, transform, screenSize, vert: renderVert.text(), frag: renderFrag.text() });
},
(w, h) => {
output.resize(w, h);
const aspectRatio = w / h;
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
},
[msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded]
);
};

View File

@@ -1,37 +1,35 @@
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
const colorToRGB = ([hue, saturation, lightness]) => {
const a = saturation * Math.min(lightness, 1 - lightness);
const f = (n) => {
const k = (n + hue * 12) % 12;
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
};
return [f(0), f(8), f(4)];
const a = saturation * Math.min(lightness, 1 - lightness);
const f = (n) => {
const k = (n + hue * 12) % 12;
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
};
return [f(0), f(8), f(4)];
};
export default (regl, config, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const output = makePassFBO(regl, config.useHalfFloat);
const resurrectionPassFrag = loadText("../shaders/resurrectionPass.frag");
const resurrectionPassFrag = loadText("../shaders/resurrectionPass.frag");
const render = regl({
frag: regl.prop("frag"),
const render = regl({
frag: regl.prop("frag"),
uniforms: {
...extractEntries(config, [
"backgroundColor",
]),
tex: inputs.primary,
bloomTex: inputs.bloom,
ditherMagnitude: 0.05
},
framebuffer: output
});
uniforms: {
...extractEntries(config, ["backgroundColor"]),
tex: inputs.primary,
bloomTex: inputs.bloom,
ditherMagnitude: 0.05,
},
framebuffer: output,
});
return makePass(
{
primary: output
},
() => render({frag: resurrectionPassFrag.text() })
);
return makePass(
{
primary: output,
},
() => render({ frag: resurrectionPassFrag.text() })
);
};

View File

@@ -1,61 +1,55 @@
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
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]
[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]
[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, inputs) => {
const output = makePassFBO(regl, config.useHalfFloat);
const output = makePassFBO(regl, config.useHalfFloat);
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))
);
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))
);
const stripePassFrag = loadText("../shaders/stripePass.frag");
const stripePassFrag = loadText("../shaders/stripePass.frag");
const render = regl({
frag: regl.prop("frag"),
const render = regl({
frag: regl.prop("frag"),
uniforms: {
...extractEntries(config, [
"backgroundColor",
]),
tex: inputs.primary,
bloomTex: inputs.bloom,
stripes,
ditherMagnitude: 0.05
},
framebuffer: output
});
uniforms: {
...extractEntries(config, ["backgroundColor"]),
tex: inputs.primary,
bloomTex: inputs.bloom,
stripes,
ditherMagnitude: 0.05,
},
framebuffer: output,
});
return makePass(
{
primary: output
},
() => render({frag: stripePassFrag.text()}),
null,
stripePassFrag.loaded
);
return makePass(
{
primary: output,
},
() => render({ frag: stripePassFrag.text() }),
null,
stripePassFrag.loaded
);
};

View File

@@ -1,128 +1,120 @@
const extractEntries = (src, keys) =>
Object.fromEntries(
Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))
);
const extractEntries = (src, keys) => Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
const makePassTexture = (regl, halfFloat) =>
regl.texture({
width: 1,
height: 1,
type: halfFloat ? "half float" : "uint8",
wrap: "clamp",
min: "linear",
mag: "linear"
});
regl.texture({
width: 1,
height: 1,
type: halfFloat ? "half float" : "uint8",
wrap: "clamp",
min: "linear",
mag: "linear",
});
const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
// A pyramid is just an array of FBOs, where each FBO is half the width
// and half the height of the FBO below it.
const makePyramid = (regl, height, halfFloat) =>
Array(height)
.fill()
.map(_ => makePassFBO(regl, halfFloat));
Array(height)
.fill()
.map((_) => makePassFBO(regl, halfFloat));
const makeDoubleBuffer = (regl, props) => {
const state = Array(2)
.fill()
.map(() =>
regl.framebuffer({
color: regl.texture(props),
depthStencil: false
})
);
return {
front: ({ tick }) => state[tick % 2],
back: ({ tick }) => state[(tick + 1) % 2]
};
const state = Array(2)
.fill()
.map(() =>
regl.framebuffer({
color: regl.texture(props),
depthStencil: false,
})
);
return {
front: ({ tick }) => state[tick % 2],
back: ({ tick }) => state[(tick + 1) % 2],
};
};
const resizePyramid = (pyramid, vw, vh, scale) =>
pyramid.forEach((fbo, index) =>
fbo.resize(
Math.floor((vw * scale) / 2 ** index),
Math.floor((vh * scale) / 2 ** index)
)
);
pyramid.forEach((fbo, index) => fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)));
const loadImage = (regl, url) => {
let texture = regl.texture([[0]]);
let loaded = false;
return {
texture: () => {
if (!loaded) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
loaded: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
data.src = url;
await data.decode();
loaded = true;
texture = regl.texture({
data,
mag: "linear",
min: "linear",
flipY: true
});
}
})()
};
let texture = regl.texture([[0]]);
let loaded = false;
return {
texture: () => {
if (!loaded) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
loaded: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
data.src = url;
await data.decode();
loaded = true;
texture = regl.texture({
data,
mag: "linear",
min: "linear",
flipY: true,
});
}
})(),
};
};
const loadShader = (regl, url) => {
let texture = regl.texture([[0]]);
let loaded = false;
return {
texture: () => {
if (!loaded) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
loaded: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
data.src = url;
await data.decode();
loaded = true;
texture = regl.texture({
data,
mag: "linear",
min: "linear",
flipY: true
});
}
})()
};
let texture = regl.texture([[0]]);
let loaded = false;
return {
texture: () => {
if (!loaded) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
loaded: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
data.src = url;
await data.decode();
loaded = true;
texture = regl.texture({
data,
mag: "linear",
min: "linear",
flipY: true,
});
}
})(),
};
};
const loadText = (url) => {
let text = "";
let loaded = false;
return {
text: () => {
if (!loaded) {
console.warn(`text still loading: ${url}`);
}
return text;
},
loaded: (async () => {
if (url != null) {
text = await (await fetch(url)).text();
loaded = true;
}
})()
};
let text = "";
let loaded = false;
return {
text: () => {
if (!loaded) {
console.warn(`text still loading: ${url}`);
}
return text;
},
loaded: (async () => {
if (url != null) {
text = await (await fetch(url)).text();
loaded = true;
}
})(),
};
};
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
regl({
vert: `
regl({
vert: `
precision mediump float;
attribute vec2 aPosition;
varying vec2 vUV;
@@ -132,7 +124,7 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
}
`,
frag: `
frag: `
precision mediump float;
varying vec2 vUV;
uniform sampler2D tex;
@@ -141,75 +133,65 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
}
`,
attributes: {
aPosition: [-4, -4, 4, -4, 0, 4]
},
count: 3,
attributes: {
aPosition: [-4, -4, 4, -4, 0, 4],
},
count: 3,
uniforms: {
...uniforms,
time: regl.context("time")
},
uniforms: {
...uniforms,
time: regl.context("time"),
},
context,
context,
depth: { enable: false },
});
depth: { enable: false },
});
const make1DTexture = (regl, data) =>
regl.texture({
data,
width: data.length / 3,
height: 1,
format: "rgb",
mag: "linear",
min: "linear"
});
regl.texture({
data,
width: data.length / 3,
height: 1,
format: "rgb",
mag: "linear",
min: "linear",
});
const makePass = (outputs, render, resize, ready) => {
if (render == null) {
render = () => {};
}
if (resize == null) {
resize = (w, h) =>
Object.values(outputs).forEach(output => output.resize(w, h));
}
if (ready == null) {
ready = Promise.resolve();
} else if (ready instanceof Array) {
ready = Promise.all(ready);
}
return {
outputs,
render,
resize,
ready
};
if (render == null) {
render = () => {};
}
if (resize == null) {
resize = (w, h) => Object.values(outputs).forEach((output) => output.resize(w, h));
}
if (ready == null) {
ready = Promise.resolve();
} else if (ready instanceof Array) {
ready = Promise.all(ready);
}
return {
outputs,
render,
resize,
ready,
};
};
const makePipeline = (steps, getInputs, ...params) =>
steps
.filter(f => f != null)
.reduce(
(pipeline, f, i) => [
...pipeline,
f(...params, i == 0 ? null : getInputs(pipeline[i - 1]))
],
[]
);
steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(...params, i == 0 ? null : getInputs(pipeline[i - 1]))], []);
export {
extractEntries,
makePassTexture,
makePassFBO,
makeDoubleBuffer,
makePyramid,
resizePyramid,
loadImage,
loadText,
makeFullScreenQuad,
make1DTexture,
makePass,
makePipeline
extractEntries,
makePassTexture,
makePassFBO,
makeDoubleBuffer,
makePyramid,
resizePyramid,
loadImage,
loadText,
makeFullScreenQuad,
make1DTexture,
makePass,
makePipeline,
};