diff --git a/TODO.txt b/TODO.txt index 43969d3..341212e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,7 +1,9 @@ TODO: -Maybe apply a further additive blend on top of the color-mapped bloom - That could help add whatever's missing visually to the raindrops +Experiment with varying the colors in the palette pass + Maybe a separate palette for the non-bloom + Maybe dim and widen the bloom + Just make sure the changes to non-default configurations are acceptable Deja vu effect: flashing rows Make them flash all the time diff --git a/js/bloomPass.js b/js/bloomPass.js index c7372e4..0033c8b 100644 --- a/js/bloomPass.js +++ b/js/bloomPass.js @@ -16,10 +16,8 @@ const levelStrengths = Array(pyramidHeight) ) .reverse(); -export default (regl, config, input) => { +export default (regl, config, inputs) => { const uniforms = extractEntries(config, [ - "bloomRadius", - "bloomSize", "bloomStrength", "highPassThreshold" ]); @@ -84,14 +82,13 @@ export default (regl, config, input) => { }); // The pyramid of textures gets flattened onto the source texture. - const combineBloom = regl({ + const flattenPyramid = regl({ frag: ` precision mediump float; varying vec2 vUV; ${vBlurPyramid .map((_, index) => `uniform sampler2D pyr_${index};`) .join("\n")} - uniform sampler2D tex; uniform float bloomStrength; void main() { vec4 total = vec4(0.); @@ -101,12 +98,11 @@ export default (regl, config, input) => { `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};` ) .join("\n")} - gl_FragColor = total * bloomStrength + texture2D(tex, vUV); + gl_FragColor = total * bloomStrength; } `, uniforms: { ...uniforms, - tex: input, ...Object.fromEntries( vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo]) ) @@ -115,16 +111,19 @@ export default (regl, config, input) => { }); return makePass( - output, + { + primary: inputs.primary, + bloom: output + }, () => { - highPassPyramid.forEach(fbo => highPass({ fbo, tex: input })); + highPassPyramid.forEach(fbo => highPass({ fbo, tex: inputs.primary })); hBlurPyramid.forEach((fbo, index) => blur({ fbo, tex: highPassPyramid[index], direction: [1, 0] }) ); vBlurPyramid.forEach((fbo, index) => blur({ fbo, tex: hBlurPyramid[index], direction: [0, 1] }) ); - combineBloom(); + flattenPyramid(); }, (w, h) => { // The blur pyramids can be lower resolution than the screen. diff --git a/js/config.js b/js/config.js index e8bc743..6c4e9b6 100644 --- a/js/config.js +++ b/js/config.js @@ -40,10 +40,10 @@ const defaults = { rippleSpeed: 0.2, numColumns: 80, paletteEntries: [ - { rgb: [0.00, 0.00, 0.00], at: 0.00 }, + { rgb: [0.0, 0.0, 0.0], at: 0.0 }, { rgb: [0.09, 0.33, 0.04], at: 0.25 }, - { rgb: [0.39, 0.98, 0.38], at: 0.70 }, - { rgb: [0.57, 0.97, 0.61], at: 1.00 }, + { rgb: [0.39, 0.98, 0.38], at: 0.7 }, + { rgb: [0.57, 0.97, 0.61], at: 1.0 } ], raindropLength: 1, slant: 0 @@ -57,7 +57,6 @@ const versions = { operator: { ...defaults, ...fonts.matrixcode, - bloomRadius: 0.3, bloomStrength: 0.75, highPassThreshold: 0.0, cycleSpeed: 0.05, @@ -81,7 +80,6 @@ const versions = { nightmare: { ...defaults, ...fonts.gothic, - bloomRadius: 0.8, highPassThreshold: 0.7, brightnessMix: 0.75, fallSpeed: 2.0, @@ -100,7 +98,6 @@ const versions = { paradise: { ...defaults, ...fonts.coptic, - bloomRadius: 1.15, bloomStrength: 1.75, highPassThreshold: 0, cycleSpeed: 0.1, diff --git a/js/imagePass.js b/js/imagePass.js index 4375e8a..5e2142a 100644 --- a/js/imagePass.js +++ b/js/imagePass.js @@ -3,26 +3,33 @@ import { loadImage, makePassFBO, makePass } from "./utils.js"; const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg"; -export default (regl, config, input) => { +export default (regl, config, inputs) => { const output = makePassFBO(regl); const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; const bgLoader = loadImage(regl, bgURL); return makePass( - output, + { + primary: output + }, regl({ frag: ` precision mediump float; uniform sampler2D tex; + uniform sampler2D bloomTex; uniform sampler2D bgTex; varying vec2 vUV; void main() { vec3 bgColor = texture2D(bgTex, vUV).rgb; - float brightness = pow(texture2D(tex, vUV).r, 1.5); + float brightness = pow(min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r, 1.5); gl_FragColor = vec4(bgColor * brightness, 1.0); } `, - uniforms: { bgTex: bgLoader.texture, tex: input }, + uniforms: { + bgTex: bgLoader.texture, + tex: inputs.primary, + bloomTex: inputs.bloom + }, framebuffer: output }), null, diff --git a/js/main.js b/js/main.js index 03c030f..1a928b3 100644 --- a/js/main.js +++ b/js/main.js @@ -51,13 +51,13 @@ document.body.onload = async () => { effect === "none" ? null : makeBloomPass, effects[effect] ], - p => p.output, + p => p.outputs, regl, config ); const drawToScreen = regl({ uniforms: { - tex: pipeline[pipeline.length - 1].output + tex: pipeline[pipeline.length - 1].outputs.primary } }); await Promise.all(pipeline.map(({ ready }) => ready)); diff --git a/js/palettePass.js b/js/palettePass.js index 2c7fa63..40f79c6 100644 --- a/js/palettePass.js +++ b/js/palettePass.js @@ -6,7 +6,7 @@ import { make1DTexture, makePassFBO, makePass } from "./utils.js"; // won't persist across subsequent frames. This is a safe trick // in screen space. -export default (regl, config, input) => { +export default (regl, config, inputs) => { const output = makePassFBO(regl); const PALETTE_SIZE = 2048; @@ -41,16 +41,22 @@ export default (regl, config, input) => { } }); - const palette = make1DTexture(regl, paletteColors.flat().map(i => i * 0xff)); + const palette = make1DTexture( + regl, + paletteColors.flat().map(i => i * 0xff) + ); return makePass( - output, + { + primary: output + }, regl({ frag: ` precision mediump float; #define PI 3.14159265359 uniform sampler2D tex; + uniform sampler2D bloomTex; uniform sampler2D palette; uniform float ditherMagnitude; uniform float time; @@ -63,13 +69,15 @@ export default (regl, config, input) => { } void main() { - float at = texture2D( tex, vUV ).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude; + float brightness = texture2D( tex, vUV ).r + texture2D( bloomTex, vUV ).r; + float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude; gl_FragColor = texture2D( palette, vec2(at, 0.0)); } `, uniforms: { - tex: input, + tex: inputs.primary, + bloomTex: inputs.bloom, palette, ditherMagnitude: 0.05 }, diff --git a/js/renderer.js b/js/renderer.js index e358c1f..95f0bf7 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -356,7 +356,9 @@ export default (regl, config) => { }); return makePass( - output, + { + primary: output + }, resources => { update(); render(resources); diff --git a/js/stripePass.js b/js/stripePass.js index e232615..f4a0ed1 100644 --- a/js/stripePass.js +++ b/js/stripePass.js @@ -18,7 +18,7 @@ const prideStripeColors = [ [0.8, 0, 1] ].flat(); -export default (regl, config, input) => { +export default (regl, config, inputs) => { const output = makePassFBO(regl); const stripeColors = @@ -34,13 +34,16 @@ export default (regl, config, input) => { ); return makePass( - output, + { + primary: output + }, regl({ frag: ` precision mediump float; #define PI 3.14159265359 uniform sampler2D tex; + uniform sampler2D bloomTex; uniform sampler2D stripes; uniform float ditherMagnitude; uniform float time; @@ -54,13 +57,15 @@ export default (regl, config, input) => { void main() { vec3 color = texture2D(stripes, vUV).rgb; - float brightness = texture2D(tex, vUV).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude; - gl_FragColor = vec4(color * brightness, 1.0); + float brightness = min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r; + float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude; + gl_FragColor = vec4(color * at, 1.0); } `, uniforms: { - tex: input, + tex: inputs.primary, + bloomTex: inputs.bloom, stripes, ditherMagnitude: 0.05 }, diff --git a/js/utils.js b/js/utils.js index 53f811c..1d224b5 100644 --- a/js/utils.js +++ b/js/utils.js @@ -142,31 +142,32 @@ const make1DTexture = (regl, data) => min: "linear" }); -const makePass = (output, render, resize, ready) => { +const makePass = (outputs, render, resize, ready) => { if (render == null) { render = () => {}; } if (resize == null) { - resize = (w, h) => output.resize(w, h); + resize = (w, h) => + Object.values(outputs).forEach(output => output.resize(w, h)); } if (ready == null) { ready = Promise.resolve(); } return { - output, + outputs, render, resize, ready }; }; -const makePipeline = (steps, getInput, ...params) => +const makePipeline = (steps, getInputs, ...params) => steps .filter(f => f != null) .reduce( (pipeline, f, i) => [ ...pipeline, - f(...params, i == 0 ? null : getInput(pipeline[i - 1])) + f(...params, i == 0 ? null : getInputs(pipeline[i - 1])) ], [] );