diff --git a/index.html b/index.html index 1aa2134..da93878 100644 --- a/index.html +++ b/index.html @@ -70,17 +70,6 @@ makePalette(regl, data) ); - // All this takes place in a full screen quad. - const fullScreenQuad = makeFullScreenQuad(regl, uniforms); - const renderer = makeMatrixRenderer(regl, config); - const bloomPass = makeBloomPass(regl, config, renderer.fbo); - const colorPass = makeColorPass(regl, config, bloomPass.fbo); - const drawToScreen = regl({ - uniforms: { - tex: colorPass.fbo - } - }); - const resize = () => { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; @@ -88,27 +77,29 @@ window.onresize = resize; resize(); - fullScreenQuad(renderer.update); - document.body.onload = async () => { - const resources = await loadImages(regl, { + const images = await loadImages(regl, { msdfTex: config.glyphTexURL, - backgroundTex: - config.effect === "image" ? config.backgroundImage : null + bgTex: config.effect === "image" ? config.bgURL : null }); - const loop = regl.frame(({ viewportWidth, viewportHeight }) => { - // All the FBOs except the compute FBOs need to be sized to the window. - renderer.resize(viewportWidth, viewportHeight); - bloomPass.resize(viewportWidth, viewportHeight); - colorPass.resize(viewportWidth, viewportHeight); + // All this takes place in a full screen quad. + const fullScreenQuad = makeFullScreenQuad(regl, uniforms); + const renderer = makeMatrixRenderer(regl, config, images); + const bloomPass = makeBloomPass(regl, config, renderer.output); + const colorPass = makeColorPass(regl, config, images, bloomPass.output); + const drawToScreen = regl({ + uniforms: { + tex: colorPass.output + } + }); - // And here is the full draw sequence. + const passes = [renderer, bloomPass, colorPass]; + + const loop = regl.frame(({ viewportWidth, viewportHeight }) => { + passes.forEach(pass => pass.resize(viewportWidth, viewportHeight)); fullScreenQuad(() => { - renderer.update(); - renderer.render(resources); - bloomPass.render(); - colorPass.render(resources); + passes.forEach(pass => pass.render()); drawToScreen(); }); }); diff --git a/js/bloomPass.js b/js/bloomPass.js index d35b528..f88a799 100644 --- a/js/bloomPass.js +++ b/js/bloomPass.js @@ -13,7 +13,7 @@ const levelStrengths = Array(pyramidHeight) export default (regl, config, input) => { if (config.effect === "none") { return { - fbo: input, + output: input, resize: () => {}, render: () => {} }; @@ -22,7 +22,7 @@ export default (regl, config, input) => { const highPassPyramid = makePyramid(regl, pyramidHeight); const horizontalBlurPyramid = makePyramid(regl, pyramidHeight); const verticalBlurPyramid = makePyramid(regl, pyramidHeight); - const fbo = makePassFBO(regl); + const output = makePassFBO(regl); // The high pass restricts the blur to bright things in our input texture. const highPass = regl({ @@ -103,11 +103,11 @@ export default (regl, config, input) => { verticalBlurPyramid.map((fbo, index) => [`tex_${index}`, fbo]) ) ), - framebuffer: fbo + framebuffer: output }); return { - fbo, + output, resize: (viewportWidth, viewportHeight) => { // The blur pyramids can be lower resolution than the screen. resizePyramid( @@ -128,7 +128,7 @@ export default (regl, config, input) => { viewportHeight, config.bloomSize ); - fbo.resize(viewportWidth, viewportHeight); + output.resize(viewportWidth, viewportHeight); }, render: () => { highPassPyramid.forEach(fbo => highPass({ fbo, tex: input })); diff --git a/js/colorPass.js b/js/colorPass.js index 684b5a9..c82213e 100644 --- a/js/colorPass.js +++ b/js/colorPass.js @@ -63,20 +63,20 @@ const colorizeByStripes = regl => } }); -const colorizeByImage = regl => +const colorizeByImage = (regl, bgTex) => regl({ frag: ` precision mediump float; uniform sampler2D tex; - uniform sampler2D backgroundTex; + uniform sampler2D bgTex; varying vec2 vUV; void main() { - gl_FragColor = vec4(texture2D(backgroundTex, vUV).rgb * (pow(texture2D(tex, vUV).r, 1.5) * 0.995 + 0.005), 1.0); + gl_FragColor = vec4(texture2D(bgTex, vUV).rgb * (pow(texture2D(tex, vUV).r, 1.5) * 0.995 + 0.005), 1.0); } `, uniforms: { - backgroundTex: regl.prop("backgroundTex") + bgTex } }); @@ -87,35 +87,35 @@ const colorizersByEffect = { image: colorizeByImage }; -export default (regl, config, inputFBO) => { - const fbo = makePassFBO(regl); - +export default (regl, config, { bgTex }, input) => { if (config.effect === "none") { return { - fbo: inputFBO, + output: input, resize: () => {}, render: () => {} }; } + const output = makePassFBO(regl); + const colorize = regl({ uniforms: { tex: regl.prop("tex") }, - framebuffer: fbo + framebuffer: output }); const colorizer = (config.effect in colorizersByEffect ? colorizersByEffect[config.effect] - : colorizeByPalette)(regl); + : colorizeByPalette)(regl, bgTex); return { - fbo, - resize: fbo.resize, + output, + resize: output.resize, render: resources => { colorize( { - tex: inputFBO + tex: input }, () => colorizer(resources) ); diff --git a/js/config.js b/js/config.js index f7fac14..6078ca6 100644 --- a/js/config.js +++ b/js/config.js @@ -204,7 +204,7 @@ export default (searchString, makePaletteTexture) => { config.animationSpeed * config.fallSpeed == 0 ? 1 : Math.min(1, Math.abs(config.animationSpeed * config.fallSpeed)); - config.backgroundImage = getParam( + config.bgURL = getParam( "url", "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg" ); diff --git a/js/renderer.js b/js/renderer.js index 04bc871..476bdb9 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -1,27 +1,20 @@ -import { makePassFBO } from "./utils.js"; +import { makePassFBO, makeDoubleBuffer } from "./utils.js"; -export default (regl, config) => { +export default (regl, config, { msdfTex }) => { // 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 FBOs are smaller than the screen, because their pixels correspond + // 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 state = Array(2) - .fill() - .map(() => - regl.framebuffer({ - color: regl.texture({ - radius: config.numColumns, - wrapT: "clamp", - type: "half float" - }), - depthStencil: false - }) - ); + const doubleBuffer = makeDoubleBuffer(regl, { + radius: config.numColumns, + wrapT: "clamp", + type: "half float" + }); - const fbo = makePassFBO(regl); + const output = makePassFBO(regl); const update = regl({ frag: ` @@ -195,10 +188,10 @@ export default (regl, config) => { `, uniforms: { - lastState: ({ tick }) => state[tick % 2] + lastState: doubleBuffer.back }, - framebuffer: ({ tick }) => state[(tick + 1) % 2] // The crucial state FBO alternator + framebuffer: doubleBuffer.front }); // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen @@ -290,19 +283,21 @@ export default (regl, config) => { `, uniforms: { - msdfTex: regl.prop("msdfTex"), + msdfTex, height: regl.context("viewportWidth"), width: regl.context("viewportHeight"), - lastState: ({ tick }) => state[tick % 2] + lastState: doubleBuffer.front }, - framebuffer: fbo + framebuffer: output }); return { - resize: fbo.resize, - fbo, - update, - render + resize: output.resize, + output, + render: resources => { + update(); + render(resources); + } }; }; diff --git a/js/utils.js b/js/utils.js index 976cd7a..7b60316 100644 --- a/js/utils.js +++ b/js/utils.js @@ -17,6 +17,21 @@ const makePyramid = (regl, height) => .fill() .map(_ => makePassFBO(regl)); +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 resizePyramid = (pyramid, vw, vh, scale) => pyramid.forEach((fbo, index) => fbo.resize( @@ -48,7 +63,7 @@ const loadImage = async (regl, url) => { }); }; -const makeFullScreenQuad = (regl, uniforms) => +const makeFullScreenQuad = (regl, uniforms = {}, context = {}) => regl({ vert: ` precision mediump float; @@ -78,6 +93,8 @@ const makeFullScreenQuad = (regl, uniforms) => time: regl.context("time") }, + context, + depth: { enable: false }, count: 3 }); @@ -95,6 +112,7 @@ const makePalette = (regl, data) => export { makePassTexture, makePassFBO, + makeDoubleBuffer, makePyramid, resizePyramid, loadImage,