Files
matrix/js/rainPass.js
2023-08-22 11:46:29 -07:00

147 lines
3.8 KiB
JavaScript

import { loadImage, loadText, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
const extractEntries = (src, keys) => Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
// These compute buffers are used to compute the properties of cells in the grid.
// They take turns being the source and destination of a "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 double buffers are smaller than the screen, because their pixels correspond
// with cells in the grid, and the cells' glyphs are much larger than a pixel.
const makeComputeDoubleBuffer = (regl, height, width) =>
makeDoubleBuffer(regl, {
width,
height,
wrapT: "clamp",
type: "half float",
});
const numVerticesPerQuad = 2 * 3;
const tlVert = [0, 0];
const trVert = [0, 1];
const blVert = [1, 0];
const brVert = [1, 1];
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
export default ({ regl, config }) => {
const [numRows, numColumns] = [config.numColumns, config.numColumns];
const commonUniforms = {
...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureGridSize"]),
numColumns,
numRows,
};
const computeDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
const rainPassCompute = loadText("shaders/glsl/rainPass.compute.frag.glsl");
const computeUniforms = {
...commonUniforms,
...extractEntries(config, ["fallSpeed", "raindropLength"]),
...extractEntries(config, ["cycleSpeed", "cycleFrameSkip"]),
};
const compute = regl({
frag: regl.prop("frag"),
uniforms: {
...computeUniforms,
previousComputeState: computeDoubleBuffer.back,
},
framebuffer: computeDoubleBuffer.front,
});
const quadPositions = Array(1)
.fill()
.map((_, y) =>
Array(1)
.fill()
.map((_, x) => Array(numVerticesPerQuad).fill([x, y]))
);
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
const glyphMSDF = loadImage(regl, config.glyphMSDFURL);
const rainPassVert = loadText("shaders/glsl/rainPass.vert.glsl");
const rainPassFrag = loadText("shaders/glsl/rainPass.frag.glsl");
const output = makePassFBO(regl, config.useHalfFloat);
const renderUniforms = {
...commonUniforms,
...extractEntries(config, [
// vertex
"forwardSpeed",
"glyphVerticalSpacing",
// fragment
"baseBrightness",
"baseContrast",
"glintBrightness",
"glintContrast",
"brightnessThreshold",
"brightnessOverride",
"isolateCursor",
"glyphEdgeCrop",
]),
};
const render = regl({
blend: {
enable: true,
func: {
src: "one",
dst: "one",
},
},
vert: regl.prop("vert"),
frag: regl.prop("frag"),
uniforms: {
...renderUniforms,
computeState: computeDoubleBuffer.front,
glyphMSDF: glyphMSDF.texture,
msdfPxRange: 4.0,
glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()],
screenSize: regl.prop("screenSize"),
},
attributes: {
aPosition: quadPositions,
aCorner: quadVertices,
},
count: numVerticesPerQuad,
framebuffer: output,
});
const screenSize = [1, 1];
return makePass(
{
primary: output,
},
Promise.all([
glyphMSDF.loaded,
rainPassCompute.loaded,
rainPassVert.loaded,
rainPassFrag.loaded,
]),
(w, h) => {
output.resize(w, h);
const aspectRatio = w / h;
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
},
(shouldRender) => {
compute({ frag: rainPassCompute.text() });
if (shouldRender) {
regl.clear({
depth: 1,
color: [0, 0, 0, 1],
framebuffer: output,
});
render({ screenSize, vert: rainPassVert.text(), frag: rainPassFrag.text() });
}
}
);
};