mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-17 22:09:28 -07:00
Ran all the JS through prettier.
This commit is contained in:
73
index.html
73
index.html
@@ -1,42 +1,39 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Matrix digital rain</title>
|
<title>Matrix digital rain</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
|
||||||
name="viewport"
|
<style>
|
||||||
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
|
body {
|
||||||
/>
|
background: black;
|
||||||
<style>
|
overflow: hidden;
|
||||||
body {
|
margin: 0;
|
||||||
background: black;
|
}
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<!--
|
||||||
This is an implementation of the green code seen in The Matrix film and video game franchise.
|
This is an implementation of the green code seen in The Matrix film and video game franchise.
|
||||||
This project demonstrates five concepts:
|
This project demonstrates five concepts:
|
||||||
1. Drawing to floating point frame buffer objects, or 'FBO's,
|
1. Drawing to floating point frame buffer objects, or 'FBO's,
|
||||||
for performing computation and post-processing
|
for performing computation and post-processing
|
||||||
2. GPU-side computation, with a fragment shader
|
2. GPU-side computation, with a fragment shader
|
||||||
updating two alternating FBOs
|
updating two alternating FBOs
|
||||||
3. Rendering crisp "vector" graphics, with a multiple-channel
|
3. Rendering crisp "vector" graphics, with a multiple-channel
|
||||||
signed distance field (or 'MSDF')
|
signed distance field (or 'MSDF')
|
||||||
4. Creating a blur/bloom effect from a texture pyramid
|
4. Creating a blur/bloom effect from a texture pyramid
|
||||||
5. Color mapping with noise, to hide banding
|
5. Color mapping with noise, to hide banding
|
||||||
|
|
||||||
For more information, please visit: https://github.com/Rezmason/matrix
|
For more information, please visit: https://github.com/Rezmason/matrix
|
||||||
-->
|
-->
|
||||||
<!-- <script src="lib/regl.min.js"></script> -->
|
<!-- <script src="lib/regl.min.js"></script> -->
|
||||||
<script src="lib/regl.js"></script>
|
<script src="lib/regl.js"></script>
|
||||||
<script src="lib/gl-matrix.js"></script>
|
<script src="lib/gl-matrix.js"></script>
|
||||||
<script type="module" src="js/main.js"></script>
|
<script type="module" src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
194
js/bloomPass.js
194
js/bloomPass.js
@@ -1,127 +1,103 @@
|
|||||||
import {
|
import { loadText, extractEntries, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js";
|
||||||
loadText,
|
|
||||||
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.
|
||||||
|
|
||||||
const pyramidHeight = 5;
|
const pyramidHeight = 5;
|
||||||
const levelStrengths = Array(pyramidHeight)
|
const levelStrengths = Array(pyramidHeight)
|
||||||
.fill()
|
.fill()
|
||||||
.map((_, index) =>
|
.map((_, index) => Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5))
|
||||||
Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5)
|
.reverse();
|
||||||
)
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
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) {
|
const uniforms = extractEntries(config, ["bloomStrength", "highPassThreshold"]);
|
||||||
return makePass(
|
|
||||||
{
|
|
||||||
primary: inputs.primary,
|
|
||||||
bloom: makePassFBO(regl)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniforms = extractEntries(config, [
|
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
"bloomStrength",
|
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
"highPassThreshold"
|
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||||
]);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
|
||||||
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
const highPassFrag = loadText("../shaders/highPass.frag");
|
||||||
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");
|
// 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.
|
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
|
||||||
const highPass = regl({
|
// The FBO pyramid's levels represent separate levels of detail;
|
||||||
frag: regl.prop("frag"),
|
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
|
||||||
uniforms: {
|
|
||||||
...uniforms,
|
|
||||||
tex: regl.prop("tex")
|
|
||||||
},
|
|
||||||
framebuffer: regl.prop("fbo")
|
|
||||||
});
|
|
||||||
|
|
||||||
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
|
const blurFrag = loadText("../shaders/blur.frag");
|
||||||
// The FBO pyramid's levels represent separate levels of detail;
|
const blur = regl({
|
||||||
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
|
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");
|
// The pyramid of textures gets flattened onto the source texture.
|
||||||
const blur = regl({
|
const flattenPyramid = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: `
|
||||||
uniforms: {
|
precision mediump float;
|
||||||
...uniforms,
|
varying vec2 vUV;
|
||||||
tex: regl.prop("tex"),
|
${vBlurPyramid.map((_, index) => `uniform sampler2D pyr_${index};`).join("\n")}
|
||||||
direction: regl.prop("direction"),
|
uniform float bloomStrength;
|
||||||
height: regl.context("viewportWidth"),
|
void main() {
|
||||||
width: regl.context("viewportHeight")
|
vec4 total = vec4(0.);
|
||||||
},
|
${vBlurPyramid.map((_, index) => `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};`).join("\n")}
|
||||||
framebuffer: regl.prop("fbo")
|
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.
|
return makePass(
|
||||||
const flattenPyramid = regl({
|
{
|
||||||
frag: `
|
primary: inputs.primary,
|
||||||
precision mediump float;
|
bloom: output,
|
||||||
varying vec2 vUV;
|
},
|
||||||
${vBlurPyramid
|
() => {
|
||||||
.map((_, index) => `uniform sampler2D pyr_${index};`)
|
for (let i = 0; i < pyramidHeight; i++) {
|
||||||
.join("\n")}
|
const highPassFBO = highPassPyramid[i];
|
||||||
uniform float bloomStrength;
|
const hBlurFBO = hBlurPyramid[i];
|
||||||
void main() {
|
const vBlurFBO = vBlurPyramid[i];
|
||||||
vec4 total = vec4(0.);
|
highPass({ fbo: highPassFBO, frag: highPassFrag.text(), tex: inputs.primary });
|
||||||
${vBlurPyramid
|
blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] });
|
||||||
.map(
|
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
|
||||||
(_, 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(
|
flattenPyramid();
|
||||||
{
|
},
|
||||||
primary: inputs.primary,
|
(w, h) => {
|
||||||
bloom: output
|
// The blur pyramids can be lower resolution than the screen.
|
||||||
},
|
resizePyramid(highPassPyramid, w, h, config.bloomSize);
|
||||||
() => {
|
resizePyramid(hBlurPyramid, w, h, config.bloomSize);
|
||||||
for (let i = 0; i < pyramidHeight; i++) {
|
resizePyramid(vBlurPyramid, w, h, config.bloomSize);
|
||||||
const highPassFBO = highPassPyramid[i];
|
output.resize(w, h);
|
||||||
const hBlurFBO = hBlurPyramid[i];
|
},
|
||||||
const vBlurFBO = vBlurPyramid[i];
|
[highPassFrag.laoded, blurFrag.loaded]
|
||||||
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]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
357
js/config.js
357
js/config.js
@@ -1,204 +1,197 @@
|
|||||||
const fonts = {
|
const fonts = {
|
||||||
coptic: {
|
coptic: {
|
||||||
glyphTexURL: "coptic_msdf.png",
|
glyphTexURL: "coptic_msdf.png",
|
||||||
glyphSequenceLength: 32,
|
glyphSequenceLength: 32,
|
||||||
glyphTextureColumns: 8
|
glyphTextureColumns: 8,
|
||||||
},
|
},
|
||||||
gothic: {
|
gothic: {
|
||||||
glyphTexURL: "gothic_msdf.png",
|
glyphTexURL: "gothic_msdf.png",
|
||||||
glyphSequenceLength: 27,
|
glyphSequenceLength: 27,
|
||||||
glyphTextureColumns: 8
|
glyphTextureColumns: 8,
|
||||||
},
|
},
|
||||||
matrixcode: {
|
matrixcode: {
|
||||||
glyphTexURL: "matrixcode_msdf.png",
|
glyphTexURL: "matrixcode_msdf.png",
|
||||||
glyphSequenceLength: 57,
|
glyphSequenceLength: 57,
|
||||||
glyphTextureColumns: 8
|
glyphTextureColumns: 8,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
backgroundColor: [0, 0, 0],
|
backgroundColor: [0, 0, 0],
|
||||||
volumetric: false,
|
volumetric: false,
|
||||||
resurrectingCodeRatio: 0,
|
resurrectingCodeRatio: 0,
|
||||||
animationSpeed: 1,
|
animationSpeed: 1,
|
||||||
forwardSpeed: 0.25,
|
forwardSpeed: 0.25,
|
||||||
bloomStrength: 1,
|
bloomStrength: 1,
|
||||||
bloomSize: 0.6,
|
bloomSize: 0.6,
|
||||||
highPassThreshold: 0.1,
|
highPassThreshold: 0.1,
|
||||||
cycleSpeed: 1,
|
cycleSpeed: 1,
|
||||||
cycleStyleName: "cycleFasterWhenDimmed",
|
cycleStyleName: "cycleFasterWhenDimmed",
|
||||||
cursorEffectThreshold: 1,
|
cursorEffectThreshold: 1,
|
||||||
brightnessOffset: 0.0,
|
brightnessOffset: 0.0,
|
||||||
brightnessMultiplier: 1.0,
|
brightnessMultiplier: 1.0,
|
||||||
brightnessMix: 1.0,
|
brightnessMix: 1.0,
|
||||||
brightnessMinimum: 0,
|
brightnessMinimum: 0,
|
||||||
fallSpeed: 1,
|
fallSpeed: 1,
|
||||||
glyphEdgeCrop: 0.0,
|
glyphEdgeCrop: 0.0,
|
||||||
glyphHeightToWidth: 1,
|
glyphHeightToWidth: 1,
|
||||||
hasSun: false,
|
hasSun: false,
|
||||||
hasThunder: false,
|
hasThunder: false,
|
||||||
isPolar: false,
|
isPolar: false,
|
||||||
rippleTypeName: null,
|
rippleTypeName: null,
|
||||||
rippleThickness: 0.2,
|
rippleThickness: 0.2,
|
||||||
rippleScale: 30,
|
rippleScale: 30,
|
||||||
rippleSpeed: 0.2,
|
rippleSpeed: 0.2,
|
||||||
numColumns: 80,
|
numColumns: 80,
|
||||||
density: 1,
|
density: 1,
|
||||||
paletteEntries: [
|
paletteEntries: [
|
||||||
{ hsl: [0.3, 0.9, 0.0], at: 0.0 },
|
{ 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.2], at: 0.2 },
|
||||||
{ hsl: [0.3, 0.9, 0.7], at: 0.7 },
|
{ hsl: [0.3, 0.9, 0.7], at: 0.7 },
|
||||||
{ hsl: [0.3, 0.9, 0.8], at: 0.8 }
|
{ hsl: [0.3, 0.9, 0.8], at: 0.8 },
|
||||||
],
|
],
|
||||||
raindropLength: 1,
|
raindropLength: 1,
|
||||||
slant: 0,
|
slant: 0,
|
||||||
resolution: 1,
|
resolution: 1,
|
||||||
useHalfFloat: false,
|
useHalfFloat: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const versions = {
|
const versions = {
|
||||||
classic: {
|
classic: {
|
||||||
...defaults,
|
...defaults,
|
||||||
...fonts.matrixcode
|
...fonts.matrixcode,
|
||||||
},
|
},
|
||||||
operator: {
|
operator: {
|
||||||
...defaults,
|
...defaults,
|
||||||
...fonts.matrixcode,
|
...fonts.matrixcode,
|
||||||
bloomStrength: 0.75,
|
bloomStrength: 0.75,
|
||||||
highPassThreshold: 0.0,
|
highPassThreshold: 0.0,
|
||||||
cycleSpeed: 0.05,
|
cycleSpeed: 0.05,
|
||||||
cycleStyleName: "cycleRandomly",
|
cycleStyleName: "cycleRandomly",
|
||||||
cursorEffectThreshold: 0.64,
|
cursorEffectThreshold: 0.64,
|
||||||
brightnessOffset: 0.25,
|
brightnessOffset: 0.25,
|
||||||
brightnessMultiplier: 0.0,
|
brightnessMultiplier: 0.0,
|
||||||
brightnessMinimum: -1.0,
|
brightnessMinimum: -1.0,
|
||||||
fallSpeed: 0.65,
|
fallSpeed: 0.65,
|
||||||
glyphEdgeCrop: 0.15,
|
glyphEdgeCrop: 0.15,
|
||||||
glyphHeightToWidth: 1.35,
|
glyphHeightToWidth: 1.35,
|
||||||
rippleTypeName: "box",
|
rippleTypeName: "box",
|
||||||
numColumns: 108,
|
numColumns: 108,
|
||||||
paletteEntries: [
|
paletteEntries: [
|
||||||
{ hsl: [0.4, 0.8, 0.0], at: 0.0 },
|
{ 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, 0.5], at: 0.5 },
|
||||||
{ hsl: [0.4, 0.8, 1.0], at: 1.0 }
|
{ hsl: [0.4, 0.8, 1.0], at: 1.0 },
|
||||||
],
|
],
|
||||||
raindropLength: 1.5
|
raindropLength: 1.5,
|
||||||
},
|
},
|
||||||
nightmare: {
|
nightmare: {
|
||||||
...defaults,
|
...defaults,
|
||||||
...fonts.gothic,
|
...fonts.gothic,
|
||||||
highPassThreshold: 0.7,
|
highPassThreshold: 0.7,
|
||||||
brightnessMix: 0.75,
|
brightnessMix: 0.75,
|
||||||
fallSpeed: 2.0,
|
fallSpeed: 2.0,
|
||||||
hasThunder: true,
|
hasThunder: true,
|
||||||
numColumns: 60,
|
numColumns: 60,
|
||||||
paletteEntries: [
|
paletteEntries: [
|
||||||
{ hsl: [0.0, 1.0, 0.0], at: 0.0 },
|
{ 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.2], at: 0.2 },
|
||||||
{ hsl: [0.0, 1.0, 0.4], at: 0.4 },
|
{ hsl: [0.0, 1.0, 0.4], at: 0.4 },
|
||||||
{ hsl: [0.1, 1.0, 0.7], at: 0.7 },
|
{ hsl: [0.1, 1.0, 0.7], at: 0.7 },
|
||||||
{ hsl: [0.2, 1.0, 1.0], at: 1.0 }
|
{ hsl: [0.2, 1.0, 1.0], at: 1.0 },
|
||||||
],
|
],
|
||||||
raindropLength: 0.6,
|
raindropLength: 0.6,
|
||||||
slant: (22.5 * Math.PI) / 180
|
slant: (22.5 * Math.PI) / 180,
|
||||||
},
|
},
|
||||||
paradise: {
|
paradise: {
|
||||||
...defaults,
|
...defaults,
|
||||||
...fonts.coptic,
|
...fonts.coptic,
|
||||||
bloomStrength: 1.75,
|
bloomStrength: 1.75,
|
||||||
highPassThreshold: 0,
|
highPassThreshold: 0,
|
||||||
cycleSpeed: 0.1,
|
cycleSpeed: 0.1,
|
||||||
brightnessMix: 0.05,
|
brightnessMix: 0.05,
|
||||||
fallSpeed: 0.08,
|
fallSpeed: 0.08,
|
||||||
hasSun: true,
|
hasSun: true,
|
||||||
isPolar: true,
|
isPolar: true,
|
||||||
rippleTypeName: "circle",
|
rippleTypeName: "circle",
|
||||||
rippleSpeed: 0.1,
|
rippleSpeed: 0.1,
|
||||||
numColumns: 30,
|
numColumns: 30,
|
||||||
paletteEntries: [
|
paletteEntries: [
|
||||||
{ hsl: [0.0, 0.0, 0.0], at: 0.0 },
|
{ hsl: [0.0, 0.0, 0.0], at: 0.0 },
|
||||||
{ hsl: [0.0, 0.8, 0.3], at: 0.3 },
|
{ hsl: [0.0, 0.8, 0.3], at: 0.3 },
|
||||||
{ hsl: [0.1, 0.8, 0.5], at: 0.5 },
|
{ 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.6], at: 0.6 },
|
||||||
{ hsl: [0.1, 1.0, 0.9], at: 0.9 }
|
{ hsl: [0.1, 1.0, 0.9], at: 0.9 },
|
||||||
],
|
],
|
||||||
raindropLength: 0.4
|
raindropLength: 0.4,
|
||||||
},
|
},
|
||||||
resurrections: {
|
resurrections: {
|
||||||
...defaults,
|
...defaults,
|
||||||
...fonts.matrixcode,
|
...fonts.matrixcode,
|
||||||
resurrectingCodeRatio: 0.25,
|
resurrectingCodeRatio: 0.25,
|
||||||
effect:"resurrections",
|
effect: "resurrections",
|
||||||
width:100,
|
width: 100,
|
||||||
volumetric:true,
|
volumetric: true,
|
||||||
density:1.5,
|
density: 1.5,
|
||||||
fallSpeed:1.2,
|
fallSpeed: 1.2,
|
||||||
raindropLength:1.25
|
raindropLength: 1.25,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
versions.throwback = versions.operator;
|
versions.throwback = versions.operator;
|
||||||
versions["1999"] = versions.classic;
|
versions["1999"] = versions.classic;
|
||||||
|
|
||||||
const range = (f, min = -Infinity, max = Infinity) =>
|
const range = (f, min = -Infinity, max = Infinity) => Math.max(min, Math.min(max, f));
|
||||||
Math.max(min, Math.min(max, f));
|
const nullNaN = (f) => (isNaN(f) ? null : f);
|
||||||
const nullNaN = f => (isNaN(f) ? null : f);
|
|
||||||
|
|
||||||
const paramMapping = {
|
const paramMapping = {
|
||||||
version: { key: "version", parser: s => s },
|
version: { key: "version", parser: (s) => s },
|
||||||
effect: { key: "effect", parser: s => s },
|
effect: { key: "effect", parser: (s) => s },
|
||||||
width: { key: "numColumns", parser: s => nullNaN(parseInt(s)) },
|
width: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) },
|
||||||
numColumns: { 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)) },
|
density: { key: "density", parser: (s) => nullNaN(range(parseFloat(s), 0)) },
|
||||||
resolution: { key: "resolution", parser: s => nullNaN(parseFloat(s)) },
|
resolution: { key: "resolution", parser: (s) => nullNaN(parseFloat(s)) },
|
||||||
animationSpeed: {
|
animationSpeed: {
|
||||||
key: "animationSpeed",
|
key: "animationSpeed",
|
||||||
parser: s => nullNaN(parseFloat(s))
|
parser: (s) => nullNaN(parseFloat(s)),
|
||||||
},
|
},
|
||||||
forwardSpeed: {
|
forwardSpeed: {
|
||||||
key: "forwardSpeed",
|
key: "forwardSpeed",
|
||||||
parser: s => nullNaN(parseFloat(s))
|
parser: (s) => nullNaN(parseFloat(s)),
|
||||||
},
|
},
|
||||||
cycleSpeed: { key: "cycleSpeed", parser: s => nullNaN(parseFloat(s)) },
|
cycleSpeed: { key: "cycleSpeed", parser: (s) => nullNaN(parseFloat(s)) },
|
||||||
fallSpeed: { key: "fallSpeed", parser: s => nullNaN(parseFloat(s)) },
|
fallSpeed: { key: "fallSpeed", parser: (s) => nullNaN(parseFloat(s)) },
|
||||||
raindropLength: {
|
raindropLength: {
|
||||||
key: "raindropLength",
|
key: "raindropLength",
|
||||||
parser: s => nullNaN(parseFloat(s))
|
parser: (s) => nullNaN(parseFloat(s)),
|
||||||
},
|
},
|
||||||
slant: {
|
slant: {
|
||||||
key: "slant",
|
key: "slant",
|
||||||
parser: s => nullNaN((parseFloat(s) * Math.PI) / 180)
|
parser: (s) => nullNaN((parseFloat(s) * Math.PI) / 180),
|
||||||
},
|
},
|
||||||
bloomSize: {
|
bloomSize: {
|
||||||
key: "bloomSize",
|
key: "bloomSize",
|
||||||
parser: s => nullNaN(range(parseFloat(s), 0, 1))
|
parser: (s) => nullNaN(range(parseFloat(s), 0, 1)),
|
||||||
},
|
},
|
||||||
url: { key: "bgURL", parser: s => s },
|
url: { key: "bgURL", parser: (s) => s },
|
||||||
stripeColors: { key: "stripeColors", parser: s => s },
|
stripeColors: { key: "stripeColors", parser: (s) => s },
|
||||||
backgroundColor: { key: "backgroundColor", parser: s => s.split(",").map(parseFloat) },
|
backgroundColor: { key: "backgroundColor", parser: (s) => s.split(",").map(parseFloat) },
|
||||||
volumetric: { key: "volumetric", parser: s => s.toLowerCase().includes("true") }
|
volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") },
|
||||||
};
|
};
|
||||||
paramMapping.dropLength = paramMapping.raindropLength;
|
paramMapping.dropLength = paramMapping.raindropLength;
|
||||||
paramMapping.angle = paramMapping.slant;
|
paramMapping.angle = paramMapping.slant;
|
||||||
paramMapping.colors = paramMapping.stripeColors;
|
paramMapping.colors = paramMapping.stripeColors;
|
||||||
|
|
||||||
export default (searchString, make1DTexture) => {
|
export default (searchString, make1DTexture) => {
|
||||||
const urlParams = Object.fromEntries(
|
const urlParams = Object.fromEntries(
|
||||||
Array.from(new URLSearchParams(searchString).entries())
|
Array.from(new URLSearchParams(searchString).entries())
|
||||||
.filter(([key]) => key in paramMapping)
|
.filter(([key]) => key in paramMapping)
|
||||||
.map(([key, value]) => [
|
.map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)])
|
||||||
paramMapping[key].key,
|
.filter(([_, value]) => value != null)
|
||||||
paramMapping[key].parser(value)
|
);
|
||||||
])
|
|
||||||
.filter(([_, value]) => value != null)
|
|
||||||
);
|
|
||||||
|
|
||||||
const version =
|
const version = urlParams.version in versions ? versions[urlParams.version] : versions.classic;
|
||||||
urlParams.version in versions
|
|
||||||
? versions[urlParams.version]
|
|
||||||
: versions.classic;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...version,
|
...version,
|
||||||
...urlParams
|
...urlParams,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
const defaultBGURL =
|
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
|
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
export default (regl, config, inputs) => {
|
||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
|
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
|
||||||
const background = loadImage(regl, bgURL);
|
const background = loadImage(regl, bgURL);
|
||||||
const imagePassFrag = loadText("../shaders/imagePass.frag");
|
const imagePassFrag = loadText("../shaders/imagePass.frag");
|
||||||
const render = regl({
|
const render = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
uniforms: {
|
uniforms: {
|
||||||
backgroundTex: background.texture,
|
backgroundTex: background.texture,
|
||||||
tex: inputs.primary,
|
tex: inputs.primary,
|
||||||
bloomTex: inputs.bloom
|
bloomTex: inputs.bloom,
|
||||||
},
|
},
|
||||||
framebuffer: output
|
framebuffer: output,
|
||||||
});
|
});
|
||||||
return makePass(
|
return makePass(
|
||||||
{
|
{
|
||||||
primary: output
|
primary: output,
|
||||||
},
|
},
|
||||||
() => render({frag: imagePassFrag.text()}),
|
() => render({ frag: imagePassFrag.text() }),
|
||||||
null,
|
null,
|
||||||
[background.loaded, imagePassFrag.loaded]
|
[background.loaded, imagePassFrag.loaded]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
98
js/main.js
98
js/main.js
@@ -9,30 +9,26 @@ import makeResurrectionPass from "./resurrectionPass.js";
|
|||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
document.body.appendChild(canvas);
|
document.body.appendChild(canvas);
|
||||||
document.addEventListener("touchmove", e => e.preventDefault(), {
|
document.addEventListener("touchmove", (e) => e.preventDefault(), {
|
||||||
passive: false
|
passive: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const regl = createREGL({
|
const regl = createREGL({
|
||||||
canvas,
|
canvas,
|
||||||
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
|
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
|
||||||
// These extensions are also needed, but Safari misreports that they are missing
|
// These extensions are also needed, but Safari misreports that they are missing
|
||||||
optionalExtensions: [
|
optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"],
|
||||||
"EXT_color_buffer_half_float",
|
|
||||||
"WEBGL_color_buffer_float",
|
|
||||||
"OES_standard_derivatives"
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const effects = {
|
const effects = {
|
||||||
none: null,
|
none: null,
|
||||||
plain: makePalettePass,
|
plain: makePalettePass,
|
||||||
customStripes: makeStripePass,
|
customStripes: makeStripePass,
|
||||||
stripes: makeStripePass,
|
stripes: makeStripePass,
|
||||||
pride: makeStripePass,
|
pride: makeStripePass,
|
||||||
image: makeImagePass,
|
image: makeImagePass,
|
||||||
resurrection: makeResurrectionPass,
|
resurrection: makeResurrectionPass,
|
||||||
resurrections: makeResurrectionPass
|
resurrections: makeResurrectionPass,
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = makeConfig(window.location.search);
|
const config = makeConfig(window.location.search);
|
||||||
@@ -40,8 +36,8 @@ const resolution = config.resolution;
|
|||||||
const effect = config.effect in effects ? config.effect : "plain";
|
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 * resolution);
|
||||||
canvas.height = Math.ceil(canvas.clientHeight * resolution);
|
canvas.height = Math.ceil(canvas.clientHeight * resolution);
|
||||||
};
|
};
|
||||||
window.onresize = resize;
|
window.onresize = resize;
|
||||||
resize();
|
resize();
|
||||||
@@ -49,41 +45,29 @@ resize();
|
|||||||
const dimensions = { width: 1, height: 1 };
|
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 pipeline = makePipeline(
|
const pipeline = makePipeline([makeMatrixRenderer, effect === "none" ? null : makeBloomPass, effects[effect]], (p) => p.outputs, regl, config);
|
||||||
[
|
const drawToScreen = regl({
|
||||||
makeMatrixRenderer,
|
uniforms: {
|
||||||
effect === "none" ? null : makeBloomPass,
|
tex: pipeline[pipeline.length - 1].outputs.primary,
|
||||||
effects[effect]
|
},
|
||||||
],
|
});
|
||||||
p => p.outputs,
|
await Promise.all(pipeline.map(({ ready }) => ready));
|
||||||
regl,
|
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||||
config
|
// tick.cancel();
|
||||||
);
|
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
|
||||||
const drawToScreen = regl({
|
dimensions.width = viewportWidth;
|
||||||
uniforms: {
|
dimensions.height = viewportHeight;
|
||||||
tex: pipeline[pipeline.length - 1].outputs.primary
|
for (const step of pipeline) {
|
||||||
}
|
step.resize(viewportWidth, viewportHeight);
|
||||||
});
|
}
|
||||||
await Promise.all(pipeline.map(({ ready }) => ready));
|
}
|
||||||
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
fullScreenQuad(() => {
|
||||||
// tick.cancel();
|
for (const step of pipeline) {
|
||||||
if (
|
step.render();
|
||||||
dimensions.width !== viewportWidth ||
|
}
|
||||||
dimensions.height !== viewportHeight
|
drawToScreen();
|
||||||
) {
|
});
|
||||||
dimensions.width = viewportWidth;
|
});
|
||||||
dimensions.height = viewportHeight;
|
|
||||||
for (const step of pipeline) {
|
|
||||||
step.resize(viewportWidth, viewportHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fullScreenQuad(() => {
|
|
||||||
for (const step of pipeline) {
|
|
||||||
step.render();
|
|
||||||
}
|
|
||||||
drawToScreen();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,51 +1,49 @@
|
|||||||
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
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) => {
|
||||||
const k = (n + hue * 12) % 12;
|
const k = (n + hue * 12) % 12;
|
||||||
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
||||||
};
|
};
|
||||||
return [f(0), f(8), f(4)];
|
return [f(0), f(8), f(4)];
|
||||||
};
|
};
|
||||||
|
|
||||||
const makePalette = (regl, entries) => {
|
const makePalette = (regl, entries) => {
|
||||||
const PALETTE_SIZE = 2048;
|
const PALETTE_SIZE = 2048;
|
||||||
const paletteColors = Array(PALETTE_SIZE);
|
const paletteColors = Array(PALETTE_SIZE);
|
||||||
const sortedEntries = entries
|
const sortedEntries = entries
|
||||||
.slice()
|
.slice()
|
||||||
.sort((e1, e2) => e1.at - e2.at)
|
.sort((e1, e2) => e1.at - e2.at)
|
||||||
.map(entry => ({
|
.map((entry) => ({
|
||||||
rgb: colorToRGB(entry.hsl),
|
rgb: colorToRGB(entry.hsl),
|
||||||
arrayIndex: Math.floor(
|
arrayIndex: Math.floor(Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)),
|
||||||
Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)
|
}));
|
||||||
)
|
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
|
||||||
}));
|
sortedEntries.push({
|
||||||
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
|
rgb: sortedEntries[sortedEntries.length - 1].rgb,
|
||||||
sortedEntries.push({
|
arrayIndex: PALETTE_SIZE - 1,
|
||||||
rgb: sortedEntries[sortedEntries.length - 1].rgb,
|
});
|
||||||
arrayIndex: PALETTE_SIZE - 1
|
sortedEntries.forEach((entry, index) => {
|
||||||
});
|
paletteColors[entry.arrayIndex] = entry.rgb.slice();
|
||||||
sortedEntries.forEach((entry, index) => {
|
if (index + 1 < sortedEntries.length) {
|
||||||
paletteColors[entry.arrayIndex] = entry.rgb.slice();
|
const nextEntry = sortedEntries[index + 1];
|
||||||
if (index + 1 < sortedEntries.length) {
|
const diff = nextEntry.arrayIndex - entry.arrayIndex;
|
||||||
const nextEntry = sortedEntries[index + 1];
|
for (let i = 0; i < diff; i++) {
|
||||||
const diff = nextEntry.arrayIndex - entry.arrayIndex;
|
const ratio = i / diff;
|
||||||
for (let i = 0; i < diff; i++) {
|
paletteColors[entry.arrayIndex + i] = [
|
||||||
const ratio = i / diff;
|
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
|
||||||
paletteColors[entry.arrayIndex + i] = [
|
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
|
||||||
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
|
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio,
|
||||||
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
|
];
|
||||||
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio
|
}
|
||||||
];
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return make1DTexture(
|
return make1DTexture(
|
||||||
regl,
|
regl,
|
||||||
paletteColors.flat().map(i => i * 0xff)
|
paletteColors.flat().map((i) => i * 0xff)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -55,32 +53,30 @@ const makePalette = (regl, entries) => {
|
|||||||
// in screen space.
|
// in screen space.
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
export default (regl, config, inputs) => {
|
||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
const palette = makePalette(regl, config.paletteEntries);
|
const palette = makePalette(regl, config.paletteEntries);
|
||||||
|
|
||||||
const palettePassFrag = loadText("../shaders/palettePass.frag");
|
const palettePassFrag = loadText("../shaders/palettePass.frag");
|
||||||
|
|
||||||
const render = regl({
|
const render = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...extractEntries(config, [
|
...extractEntries(config, ["backgroundColor"]),
|
||||||
"backgroundColor",
|
tex: inputs.primary,
|
||||||
]),
|
bloomTex: inputs.bloom,
|
||||||
tex: inputs.primary,
|
palette,
|
||||||
bloomTex: inputs.bloom,
|
ditherMagnitude: 0.05,
|
||||||
palette,
|
},
|
||||||
ditherMagnitude: 0.05
|
framebuffer: output,
|
||||||
},
|
});
|
||||||
framebuffer: output
|
|
||||||
});
|
|
||||||
|
|
||||||
return makePass(
|
return makePass(
|
||||||
{
|
{
|
||||||
primary: output
|
primary: output,
|
||||||
},
|
},
|
||||||
() => render({ frag: palettePassFrag.text() }),
|
() => render({ frag: palettePassFrag.text() }),
|
||||||
null,
|
null,
|
||||||
palettePassFrag.loaded
|
palettePassFrag.loaded
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
307
js/renderer.js
307
js/renderer.js
@@ -1,187 +1,180 @@
|
|||||||
import {
|
import { extractEntries, loadImage, loadText, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
|
||||||
extractEntries,
|
|
||||||
loadImage,
|
|
||||||
loadText,
|
|
||||||
makePassFBO,
|
|
||||||
makeDoubleBuffer,
|
|
||||||
makePass
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
const rippleTypes = {
|
const rippleTypes = {
|
||||||
box: 0,
|
box: 0,
|
||||||
circle: 1
|
circle: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cycleStyles = {
|
const cycleStyles = {
|
||||||
cycleFasterWhenDimmed: 0,
|
cycleFasterWhenDimmed: 0,
|
||||||
cycleRandomly: 1
|
cycleRandomly: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const numVerticesPerQuad = 2 * 3;
|
const numVerticesPerQuad = 2 * 3;
|
||||||
|
|
||||||
export default (regl, config) => {
|
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;
|
// These two framebuffers are used to compute the raining code.
|
||||||
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
// they take turns being the source and destination of the "compute" shader.
|
||||||
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
|
// The half float data type is crucial! It lets us store almost any real number,
|
||||||
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
|
// whereas the default type limits us to integers between 0 and 255.
|
||||||
const numQuads = numQuadRows * numQuadColumns;
|
|
||||||
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
|
||||||
|
|
||||||
// These two framebuffers are used to compute the raining code.
|
// This double buffer is smaller than the screen, because its pixels correspond
|
||||||
// they take turns being the source and destination of the "compute" shader.
|
// with glyphs in the final image, and the glyphs are much larger than a pixel.
|
||||||
// The half float data type is crucial! It lets us store almost any real number,
|
const doubleBuffer = makeDoubleBuffer(regl, {
|
||||||
// whereas the default type limits us to integers between 0 and 255.
|
width: numColumns,
|
||||||
|
height: numRows,
|
||||||
|
wrapT: "clamp",
|
||||||
|
type: "half float",
|
||||||
|
});
|
||||||
|
|
||||||
// This double buffer is smaller than the screen, because its pixels correspond
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
// 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 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 = {
|
const msdf = loadImage(regl, config.glyphTexURL);
|
||||||
...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
|
|
||||||
};
|
|
||||||
|
|
||||||
uniforms.rippleType =
|
const updateFrag = loadText("../shaders/update.frag");
|
||||||
config.rippleTypeName in rippleTypes
|
const update = regl({
|
||||||
? rippleTypes[config.rippleTypeName]
|
frag: regl.prop("frag"),
|
||||||
: -1;
|
uniforms: {
|
||||||
uniforms.cycleStyle =
|
...uniforms,
|
||||||
config.cycleStyleName in cycleStyles
|
lastState: doubleBuffer.back,
|
||||||
? 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 msdf = loadImage(regl, config.glyphTexURL);
|
framebuffer: doubleBuffer.front,
|
||||||
|
});
|
||||||
|
|
||||||
const updateFrag = loadText("../shaders/update.frag");
|
const quadPositions = Array(numQuadRows)
|
||||||
const update = regl({
|
.fill()
|
||||||
frag: regl.prop("frag"),
|
.map((_, y) =>
|
||||||
uniforms: {
|
Array(numQuadColumns)
|
||||||
...uniforms,
|
.fill()
|
||||||
lastState: doubleBuffer.back
|
.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) =>
|
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||||
Array(numQuadColumns).fill().map((_, x) =>
|
const renderVert = loadText("../shaders/render.vert");
|
||||||
Array(numVerticesPerQuad).fill([x, y])
|
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
|
lastState: doubleBuffer.front,
|
||||||
const renderVert = loadText("../shaders/render.vert");
|
glyphTex: msdf.texture,
|
||||||
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"),
|
|
||||||
|
|
||||||
uniforms: {
|
camera: regl.prop("camera"),
|
||||||
...uniforms,
|
transform: regl.prop("transform"),
|
||||||
|
screenSize: regl.prop("screenSize"),
|
||||||
|
},
|
||||||
|
|
||||||
lastState: doubleBuffer.front,
|
attributes: {
|
||||||
glyphTex: msdf.texture,
|
aPosition: quadPositions,
|
||||||
|
aCorner: quadCorners,
|
||||||
|
},
|
||||||
|
count: numQuads * numVerticesPerQuad,
|
||||||
|
|
||||||
camera: regl.prop("camera"),
|
framebuffer: output,
|
||||||
transform: regl.prop("transform"),
|
});
|
||||||
screenSize: regl.prop("screenSize")
|
|
||||||
},
|
|
||||||
|
|
||||||
attributes: {
|
const screenSize = [1, 1];
|
||||||
aPosition: quadPositions,
|
const { mat4, vec3 } = glMatrix;
|
||||||
aCorner: quadCorners
|
const camera = mat4.create();
|
||||||
},
|
const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1);
|
||||||
count: numQuads * numVerticesPerQuad,
|
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];
|
update({ frag: updateFrag.text() });
|
||||||
const {mat4, vec3} = glMatrix;
|
regl.clear({
|
||||||
const camera = mat4.create();
|
depth: 1,
|
||||||
const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1);
|
color: [0, 0, 0, 1],
|
||||||
const scale = vec3.set(vec3.create(), 1, 1, 1);
|
framebuffer: output,
|
||||||
const transform = mat4.create();
|
});
|
||||||
mat4.translate(transform, transform, translation);
|
render({ camera, transform, screenSize, vert: renderVert.text(), frag: renderFrag.text() });
|
||||||
mat4.scale(transform, transform, scale);
|
},
|
||||||
|
(w, h) => {
|
||||||
return makePass(
|
output.resize(w, h);
|
||||||
{
|
const aspectRatio = w / h;
|
||||||
primary: output
|
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||||
},
|
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||||
() => {
|
},
|
||||||
const time = Date.now();
|
[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]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
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) => {
|
||||||
const k = (n + hue * 12) % 12;
|
const k = (n + hue * 12) % 12;
|
||||||
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
||||||
};
|
};
|
||||||
return [f(0), f(8), f(4)];
|
return [f(0), f(8), f(4)];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
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({
|
const render = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...extractEntries(config, [
|
...extractEntries(config, ["backgroundColor"]),
|
||||||
"backgroundColor",
|
tex: inputs.primary,
|
||||||
]),
|
bloomTex: inputs.bloom,
|
||||||
tex: inputs.primary,
|
ditherMagnitude: 0.05,
|
||||||
bloomTex: inputs.bloom,
|
},
|
||||||
ditherMagnitude: 0.05
|
framebuffer: output,
|
||||||
},
|
});
|
||||||
framebuffer: output
|
|
||||||
});
|
|
||||||
|
|
||||||
return makePass(
|
return makePass(
|
||||||
{
|
{
|
||||||
primary: output
|
primary: output,
|
||||||
},
|
},
|
||||||
() => render({frag: resurrectionPassFrag.text() })
|
() => render({ frag: resurrectionPassFrag.text() })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,61 +1,55 @@
|
|||||||
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
const neapolitanStripeColors = [
|
const neapolitanStripeColors = [
|
||||||
[0.4, 0.15, 0.1],
|
[0.4, 0.15, 0.1],
|
||||||
[0.4, 0.15, 0.1],
|
[0.4, 0.15, 0.1],
|
||||||
[0.8, 0.8, 0.6],
|
[0.8, 0.8, 0.6],
|
||||||
[0.8, 0.8, 0.6],
|
[0.8, 0.8, 0.6],
|
||||||
[1.0, 0.7, 0.8],
|
[1.0, 0.7, 0.8],
|
||||||
[1.0, 0.7, 0.8]
|
[1.0, 0.7, 0.8],
|
||||||
].flat();
|
].flat();
|
||||||
|
|
||||||
const prideStripeColors = [
|
const prideStripeColors = [
|
||||||
[1, 0, 0],
|
[1, 0, 0],
|
||||||
[1, 0.5, 0],
|
[1, 0.5, 0],
|
||||||
[1, 1, 0],
|
[1, 1, 0],
|
||||||
[0, 1, 0],
|
[0, 1, 0],
|
||||||
[0, 0, 1],
|
[0, 0, 1],
|
||||||
[0.8, 0, 1]
|
[0.8, 0, 1],
|
||||||
].flat();
|
].flat();
|
||||||
|
|
||||||
export default (regl, config, inputs) => {
|
export default (regl, config, inputs) => {
|
||||||
const output = makePassFBO(regl, config.useHalfFloat);
|
const output = makePassFBO(regl, config.useHalfFloat);
|
||||||
|
|
||||||
const stripeColors =
|
const stripeColors =
|
||||||
"stripeColors" in config
|
"stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : neapolitanStripeColors;
|
||||||
? config.stripeColors.split(",").map(parseFloat)
|
const numStripeColors = Math.floor(stripeColors.length / 3);
|
||||||
: config.effect === "pride"
|
const stripes = make1DTexture(
|
||||||
? prideStripeColors
|
regl,
|
||||||
: neapolitanStripeColors;
|
stripeColors.slice(0, numStripeColors * 3).map((f) => Math.floor(f * 0xff))
|
||||||
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({
|
const render = regl({
|
||||||
frag: regl.prop("frag"),
|
frag: regl.prop("frag"),
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...extractEntries(config, [
|
...extractEntries(config, ["backgroundColor"]),
|
||||||
"backgroundColor",
|
tex: inputs.primary,
|
||||||
]),
|
bloomTex: inputs.bloom,
|
||||||
tex: inputs.primary,
|
stripes,
|
||||||
bloomTex: inputs.bloom,
|
ditherMagnitude: 0.05,
|
||||||
stripes,
|
},
|
||||||
ditherMagnitude: 0.05
|
framebuffer: output,
|
||||||
},
|
});
|
||||||
framebuffer: output
|
|
||||||
});
|
|
||||||
|
|
||||||
return makePass(
|
return makePass(
|
||||||
{
|
{
|
||||||
primary: output
|
primary: output,
|
||||||
},
|
},
|
||||||
() => render({frag: stripePassFrag.text()}),
|
() => render({ frag: stripePassFrag.text() }),
|
||||||
null,
|
null,
|
||||||
stripePassFrag.loaded
|
stripePassFrag.loaded
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
304
js/utils.js
304
js/utils.js
@@ -1,128 +1,120 @@
|
|||||||
const extractEntries = (src, keys) =>
|
const extractEntries = (src, keys) => Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
|
||||||
Object.fromEntries(
|
|
||||||
Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))
|
|
||||||
);
|
|
||||||
|
|
||||||
const makePassTexture = (regl, halfFloat) =>
|
const makePassTexture = (regl, halfFloat) =>
|
||||||
regl.texture({
|
regl.texture({
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
type: halfFloat ? "half float" : "uint8",
|
type: halfFloat ? "half float" : "uint8",
|
||||||
wrap: "clamp",
|
wrap: "clamp",
|
||||||
min: "linear",
|
min: "linear",
|
||||||
mag: "linear"
|
mag: "linear",
|
||||||
});
|
});
|
||||||
|
|
||||||
const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
|
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
|
// A pyramid is just an array of FBOs, where each FBO is half the width
|
||||||
// and half the height of the FBO below it.
|
// and half the height of the FBO below it.
|
||||||
const makePyramid = (regl, height, halfFloat) =>
|
const makePyramid = (regl, height, halfFloat) =>
|
||||||
Array(height)
|
Array(height)
|
||||||
.fill()
|
.fill()
|
||||||
.map(_ => makePassFBO(regl, halfFloat));
|
.map((_) => makePassFBO(regl, halfFloat));
|
||||||
|
|
||||||
const makeDoubleBuffer = (regl, props) => {
|
const makeDoubleBuffer = (regl, props) => {
|
||||||
const state = Array(2)
|
const state = Array(2)
|
||||||
.fill()
|
.fill()
|
||||||
.map(() =>
|
.map(() =>
|
||||||
regl.framebuffer({
|
regl.framebuffer({
|
||||||
color: regl.texture(props),
|
color: regl.texture(props),
|
||||||
depthStencil: false
|
depthStencil: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
front: ({ tick }) => state[tick % 2],
|
front: ({ tick }) => state[tick % 2],
|
||||||
back: ({ tick }) => state[(tick + 1) % 2]
|
back: ({ tick }) => state[(tick + 1) % 2],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizePyramid = (pyramid, vw, vh, scale) =>
|
const resizePyramid = (pyramid, vw, vh, scale) =>
|
||||||
pyramid.forEach((fbo, index) =>
|
pyramid.forEach((fbo, index) => fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)));
|
||||||
fbo.resize(
|
|
||||||
Math.floor((vw * scale) / 2 ** index),
|
|
||||||
Math.floor((vh * scale) / 2 ** index)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadImage = (regl, url) => {
|
const loadImage = (regl, url) => {
|
||||||
let texture = regl.texture([[0]]);
|
let texture = regl.texture([[0]]);
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
return {
|
return {
|
||||||
texture: () => {
|
texture: () => {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
console.warn(`texture still loading: ${url}`);
|
console.warn(`texture still loading: ${url}`);
|
||||||
}
|
}
|
||||||
return texture;
|
return texture;
|
||||||
},
|
},
|
||||||
loaded: (async () => {
|
loaded: (async () => {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
const data = new Image();
|
const data = new Image();
|
||||||
data.crossOrigin = "anonymous";
|
data.crossOrigin = "anonymous";
|
||||||
data.src = url;
|
data.src = url;
|
||||||
await data.decode();
|
await data.decode();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
texture = regl.texture({
|
texture = regl.texture({
|
||||||
data,
|
data,
|
||||||
mag: "linear",
|
mag: "linear",
|
||||||
min: "linear",
|
min: "linear",
|
||||||
flipY: true
|
flipY: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})()
|
})(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadShader = (regl, url) => {
|
const loadShader = (regl, url) => {
|
||||||
let texture = regl.texture([[0]]);
|
let texture = regl.texture([[0]]);
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
return {
|
return {
|
||||||
texture: () => {
|
texture: () => {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
console.warn(`texture still loading: ${url}`);
|
console.warn(`texture still loading: ${url}`);
|
||||||
}
|
}
|
||||||
return texture;
|
return texture;
|
||||||
},
|
},
|
||||||
loaded: (async () => {
|
loaded: (async () => {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
const data = new Image();
|
const data = new Image();
|
||||||
data.crossOrigin = "anonymous";
|
data.crossOrigin = "anonymous";
|
||||||
data.src = url;
|
data.src = url;
|
||||||
await data.decode();
|
await data.decode();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
texture = regl.texture({
|
texture = regl.texture({
|
||||||
data,
|
data,
|
||||||
mag: "linear",
|
mag: "linear",
|
||||||
min: "linear",
|
min: "linear",
|
||||||
flipY: true
|
flipY: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})()
|
})(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadText = (url) => {
|
const loadText = (url) => {
|
||||||
let text = "";
|
let text = "";
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
return {
|
return {
|
||||||
text: () => {
|
text: () => {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
console.warn(`text still loading: ${url}`);
|
console.warn(`text still loading: ${url}`);
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
},
|
},
|
||||||
loaded: (async () => {
|
loaded: (async () => {
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
text = await (await fetch(url)).text();
|
text = await (await fetch(url)).text();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
})()
|
})(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
|
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
|
||||||
regl({
|
regl({
|
||||||
vert: `
|
vert: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
attribute vec2 aPosition;
|
attribute vec2 aPosition;
|
||||||
varying vec2 vUV;
|
varying vec2 vUV;
|
||||||
@@ -132,7 +124,7 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
frag: `
|
frag: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec2 vUV;
|
varying vec2 vUV;
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
@@ -141,75 +133,65 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
aPosition: [-4, -4, 4, -4, 0, 4]
|
aPosition: [-4, -4, 4, -4, 0, 4],
|
||||||
},
|
},
|
||||||
count: 3,
|
count: 3,
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
...uniforms,
|
...uniforms,
|
||||||
time: regl.context("time")
|
time: regl.context("time"),
|
||||||
},
|
},
|
||||||
|
|
||||||
context,
|
context,
|
||||||
|
|
||||||
depth: { enable: false },
|
depth: { enable: false },
|
||||||
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const make1DTexture = (regl, data) =>
|
const make1DTexture = (regl, data) =>
|
||||||
regl.texture({
|
regl.texture({
|
||||||
data,
|
data,
|
||||||
width: data.length / 3,
|
width: data.length / 3,
|
||||||
height: 1,
|
height: 1,
|
||||||
format: "rgb",
|
format: "rgb",
|
||||||
mag: "linear",
|
mag: "linear",
|
||||||
min: "linear"
|
min: "linear",
|
||||||
});
|
});
|
||||||
|
|
||||||
const makePass = (outputs, render, resize, ready) => {
|
const makePass = (outputs, render, resize, ready) => {
|
||||||
if (render == null) {
|
if (render == null) {
|
||||||
render = () => {};
|
render = () => {};
|
||||||
}
|
}
|
||||||
if (resize == null) {
|
if (resize == null) {
|
||||||
resize = (w, h) =>
|
resize = (w, h) => Object.values(outputs).forEach((output) => output.resize(w, h));
|
||||||
Object.values(outputs).forEach(output => output.resize(w, h));
|
}
|
||||||
}
|
if (ready == null) {
|
||||||
if (ready == null) {
|
ready = Promise.resolve();
|
||||||
ready = Promise.resolve();
|
} else if (ready instanceof Array) {
|
||||||
} else if (ready instanceof Array) {
|
ready = Promise.all(ready);
|
||||||
ready = Promise.all(ready);
|
}
|
||||||
}
|
return {
|
||||||
return {
|
outputs,
|
||||||
outputs,
|
render,
|
||||||
render,
|
resize,
|
||||||
resize,
|
ready,
|
||||||
ready
|
};
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const makePipeline = (steps, getInputs, ...params) =>
|
const makePipeline = (steps, getInputs, ...params) =>
|
||||||
steps
|
steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(...params, i == 0 ? null : getInputs(pipeline[i - 1]))], []);
|
||||||
.filter(f => f != null)
|
|
||||||
.reduce(
|
|
||||||
(pipeline, f, i) => [
|
|
||||||
...pipeline,
|
|
||||||
f(...params, i == 0 ? null : getInputs(pipeline[i - 1]))
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
extractEntries,
|
extractEntries,
|
||||||
makePassTexture,
|
makePassTexture,
|
||||||
makePassFBO,
|
makePassFBO,
|
||||||
makeDoubleBuffer,
|
makeDoubleBuffer,
|
||||||
makePyramid,
|
makePyramid,
|
||||||
resizePyramid,
|
resizePyramid,
|
||||||
loadImage,
|
loadImage,
|
||||||
loadText,
|
loadText,
|
||||||
makeFullScreenQuad,
|
makeFullScreenQuad,
|
||||||
make1DTexture,
|
make1DTexture,
|
||||||
makePass,
|
makePass,
|
||||||
makePipeline
|
makePipeline,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user