diff --git a/index.html b/index.html index da93878..c549116 100644 --- a/index.html +++ b/index.html @@ -42,7 +42,7 @@ import { loadImages, makeFullScreenQuad, - makePalette + make1DTexture } from "./js/utils.js"; import makeConfig from "./js/config.js"; import makeMatrixRenderer from "./js/renderer.js"; @@ -67,7 +67,7 @@ }); const [config, uniforms] = makeConfig(window.location.search, data => - makePalette(regl, data) + make1DTexture(regl, data) ); const resize = () => { diff --git a/js/bloomPass.js b/js/bloomPass.js index f88a799..0913a63 100644 --- a/js/bloomPass.js +++ b/js/bloomPass.js @@ -1,4 +1,4 @@ -import { makePassFBO, makePyramid, resizePyramid } from "./utils.js"; +import { makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js"; // The bloom pass is basically an added high-pass blur. @@ -11,32 +11,30 @@ const levelStrengths = Array(pyramidHeight) .reverse(); export default (regl, config, input) => { - if (config.effect === "none") { - return { - output: input, - resize: () => {}, - render: () => {} - }; + if (!config.performBloom) { + return makePass(input, null, null); } const highPassPyramid = makePyramid(regl, pyramidHeight); - const horizontalBlurPyramid = makePyramid(regl, pyramidHeight); - const verticalBlurPyramid = makePyramid(regl, pyramidHeight); + const hBlurPyramid = makePyramid(regl, pyramidHeight); + const vBlurPyramid = makePyramid(regl, pyramidHeight); const output = makePassFBO(regl); // The high pass restricts the blur to bright things in our input texture. const highPass = regl({ frag: ` + #define lumaMag vec3(0.2126, 0.7152, 0.0722) precision mediump float; varying vec2 vUV; uniform sampler2D tex; uniform float highPassThreshold; void main() { - float value = texture2D(tex, vUV).r; - if (value < highPassThreshold) { - value = 0.; + vec3 lumaColor = texture2D(tex, vUV).rgb * lumaMag; + float luma = lumaColor.r + lumaColor.g + lumaColor.b; + if (luma < highPassThreshold) { + luma = 0.; } - gl_FragColor = vec4(vec3(value), 1.0); + gl_FragColor = vec4(vec3(luma), 1.0); } `, uniforms: { @@ -79,70 +77,51 @@ export default (regl, config, input) => { frag: ` precision mediump float; varying vec2 vUV; - ${verticalBlurPyramid - .map((_, index) => `uniform sampler2D tex_${index};`) + ${vBlurPyramid + .map((_, index) => `uniform sampler2D pyr_${index};`) .join("\n")} uniform sampler2D tex; uniform float bloomStrength; void main() { vec4 total = vec4(0.); - ${verticalBlurPyramid + ${vBlurPyramid .map( (_, index) => - `total += texture2D(tex_${index}, vUV) * ${levelStrengths[index]};` + `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};` ) .join("\n")} gl_FragColor = total * bloomStrength + texture2D(tex, vUV); } `, - uniforms: Object.assign( - { - tex: input - }, - Object.fromEntries( - verticalBlurPyramid.map((fbo, index) => [`tex_${index}`, fbo]) + uniforms: { + tex: input, + ...Object.fromEntries( + vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo]) ) - ), + }, framebuffer: output }); - return { + const scale = config.bloomSize; + + return makePass( output, - resize: (viewportWidth, viewportHeight) => { - // The blur pyramids can be lower resolution than the screen. - resizePyramid( - highPassPyramid, - viewportWidth, - viewportHeight, - config.bloomSize - ); - resizePyramid( - horizontalBlurPyramid, - viewportWidth, - viewportHeight, - config.bloomSize - ); - resizePyramid( - verticalBlurPyramid, - viewportWidth, - viewportHeight, - config.bloomSize - ); - output.resize(viewportWidth, viewportHeight); - }, - render: () => { + () => { highPassPyramid.forEach(fbo => highPass({ fbo, tex: input })); - horizontalBlurPyramid.forEach((fbo, index) => + hBlurPyramid.forEach((fbo, index) => blur({ fbo, tex: highPassPyramid[index], direction: [1, 0] }) ); - verticalBlurPyramid.forEach((fbo, index) => - blur({ - fbo, - tex: horizontalBlurPyramid[index], - direction: [0, 1] - }) + vBlurPyramid.forEach((fbo, index) => + blur({ fbo, tex: hBlurPyramid[index], direction: [0, 1] }) ); combineBloom(); + }, + (w, h) => { + // The blur pyramids can be lower resolution than the screen. + resizePyramid(highPassPyramid, w, h, scale); + resizePyramid(hBlurPyramid, w, h, scale); + resizePyramid(vBlurPyramid, w, h, scale); + output.resize(w, h); } - }; + ); }; diff --git a/js/colorPass.js b/js/colorPass.js index c82213e..e121371 100644 --- a/js/colorPass.js +++ b/js/colorPass.js @@ -1,19 +1,18 @@ -import { makePassFBO } from "./utils.js"; +import { makePassFBO, makePass } from "./utils.js"; -// rendered texture's values are mapped to colors in a palette texture. -// A little noise is introduced, to hide the banding that appears -// in subtle gradients. The noise is also time-driven, so its grain -// won't persist across subsequent frames. This is a safe trick -// in screen space. - -const colorizeByPalette = regl => +const colorizeByPalette = (regl, uniforms, framebuffer) => + // The rendered texture's values are mapped to colors in a palette texture. + // A little noise is introduced, to hide the banding that appears + // in subtle gradients. The noise is also time-driven, so its grain + // won't persist across subsequent frames. This is a safe trick + // in screen space. regl({ frag: ` precision mediump float; #define PI 3.14159265359 uniform sampler2D tex; - uniform sampler2D paletteColorData; + uniform sampler2D palette; uniform float ditherMagnitude; uniform float time; varying vec2 vUV; @@ -25,23 +24,26 @@ const colorizeByPalette = regl => } void main() { - gl_FragColor = texture2D( paletteColorData, vec2( texture2D( tex, vUV ).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude, 0.0 ) ); + float at = texture2D( tex, vUV ).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude; + gl_FragColor = texture2D( palette, vec2(at, 0.0)); } `, uniforms: { + ...uniforms, ditherMagnitude: 0.05 - } + }, + framebuffer }); -const colorizeByStripes = regl => +const colorizeByStripes = (regl, uniforms, framebuffer) => regl({ frag: ` precision mediump float; #define PI 3.14159265359 uniform sampler2D tex; - uniform sampler2D stripeColorData; + uniform sampler2D stripes; uniform float ditherMagnitude; varying vec2 vUV; @@ -52,18 +54,20 @@ const colorizeByStripes = regl => } void main() { - float value = texture2D(tex, vUV).r; - vec3 value2 = texture2D(stripeColorData, vUV).rgb - rand( gl_FragCoord.xy ) * ditherMagnitude; - gl_FragColor = vec4(value2 * value, 1.0); + vec3 color = texture2D(stripes, vUV).rgb - rand( gl_FragCoord.xy ) * ditherMagnitude; + float brightness = texture2D(tex, vUV).r; + gl_FragColor = vec4(color * brightness, 1.0); } `, uniforms: { + ...uniforms, ditherMagnitude: 0.1 - } + }, + framebuffer }); -const colorizeByImage = (regl, bgTex) => +const colorizeByImage = (regl, uniforms, framebuffer) => regl({ frag: ` precision mediump float; @@ -72,12 +76,13 @@ const colorizeByImage = (regl, bgTex) => varying vec2 vUV; void main() { - gl_FragColor = vec4(texture2D(bgTex, vUV).rgb * (pow(texture2D(tex, vUV).r, 1.5) * 0.995 + 0.005), 1.0); + vec3 bgColor = texture2D(bgTex, vUV).rgb; + float brightness = pow(texture2D(tex, vUV).r, 1.5); + gl_FragColor = vec4(bgColor * brightness, 1.0); } `, - uniforms: { - bgTex - } + uniforms, + framebuffer }); const colorizersByEffect = { @@ -89,36 +94,19 @@ const colorizersByEffect = { export default (regl, config, { bgTex }, input) => { if (config.effect === "none") { - return { - output: input, - resize: () => {}, - render: () => {} - }; + return makePass(input, null, null); + } + + if (bgTex == null) { + bgTex = 0; } const output = makePassFBO(regl); - const colorize = regl({ - uniforms: { - tex: regl.prop("tex") - }, - framebuffer: output - }); - - const colorizer = (config.effect in colorizersByEffect - ? colorizersByEffect[config.effect] - : colorizeByPalette)(regl, bgTex); - - return { + return makePass( output, - resize: output.resize, - render: resources => { - colorize( - { - tex: input - }, - () => colorizer(resources) - ); - } - }; + (config.effect in colorizersByEffect + ? colorizersByEffect[config.effect] + : colorizeByPalette)(regl, { bgTex, tex: input }, output) + ); }; diff --git a/js/config.js b/js/config.js index 6078ca6..43cc399 100644 --- a/js/config.js +++ b/js/config.js @@ -38,7 +38,7 @@ const versions = { rippleScale: 30, rippleSpeed: 0.1, numColumns: 30, - palette: [ + paletteEntries: [ { rgb: [0.0, 0.0, 0.0], at: 0.0 }, { rgb: [0.52, 0.17, 0.05], at: 0.4 }, { rgb: [0.82, 0.37, 0.12], at: 0.7 }, @@ -69,7 +69,7 @@ const versions = { rippleScale: 30, rippleSpeed: 0.2, numColumns: 60, - palette: [ + paletteEntries: [ { rgb: [0.0, 0.0, 0.0], at: 0.0 }, { rgb: [0.32, 0.06, 0.0], at: 0.2 }, { rgb: [0.82, 0.06, 0.05], at: 0.4 }, @@ -100,7 +100,7 @@ const versions = { rippleScale: 30, rippleSpeed: 0.2, numColumns: 80, - palette: [ + paletteEntries: [ { rgb: [0 / 255, 0 / 255, 0 / 255], at: 0 / 16 }, { rgb: [6 / 255, 16 / 255, 8 / 255], at: 1 / 16 }, { rgb: [11 / 255, 28 / 255, 15 / 255], at: 2 / 16 }, @@ -143,7 +143,7 @@ const versions = { rippleScale: 30, rippleSpeed: 0.2, numColumns: 108, - palette: [ + paletteEntries: [ { rgb: [0.0, 0.0, 0.0], at: 0.0 }, { rgb: [0.18, 0.9, 0.35], at: 0.6 }, { rgb: [0.9, 1.0, 0.9], at: 1.0 } @@ -155,7 +155,7 @@ const versions = { versions.throwback = versions.operator; versions["1999"] = versions.classic; -export default (searchString, makePaletteTexture) => { +export default (searchString, make1DTexture) => { const urlParams = new URLSearchParams(searchString); const getParam = (keyOrKeys, defaultValue) => { if (Array.isArray(keyOrKeys)) { @@ -215,6 +215,11 @@ export default (searchString, makePaletteTexture) => { .split(",") .map(parseFloat); config.showComputationTexture = config.effect === "none"; + config.performBloom = + config.effect !== "none" && + config.bloomSize > 0 && + config.bloomStrength > 0; + console.log(config.effect, config.bloomSize, config.bloomStrength); switch (config.cycleStyleName) { case "cycleFasterWhenDimmed": @@ -239,7 +244,7 @@ export default (searchString, makePaletteTexture) => { const PALETTE_SIZE = 2048; const paletteColors = Array(PALETTE_SIZE); - const sortedEntries = version.palette + const sortedEntries = version.paletteEntries .slice() .sort((e1, e2) => e1.at - e2.at) .map(entry => ({ @@ -269,9 +274,7 @@ export default (searchString, makePaletteTexture) => { } }); - config.paletteColorData = makePaletteTexture( - paletteColors.flat().map(i => i * 0xff) - ); + config.palette = make1DTexture(paletteColors.flat().map(i => i * 0xff)); let stripeColors = [0, 0, 0]; @@ -292,9 +295,7 @@ export default (searchString, makePaletteTexture) => { stripeColors = config.customStripes.slice(0, numFlagColors * 3); } - config.stripeColorData = makePaletteTexture( - stripeColors.map(f => Math.floor(f * 0xff)) - ); + config.stripes = make1DTexture(stripeColors.map(f => Math.floor(f * 0xff))); const uniforms = Object.fromEntries( Object.entries(config).filter(([key, value]) => { diff --git a/js/renderer.js b/js/renderer.js index 476bdb9..b3e9ce4 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -1,4 +1,4 @@ -import { makePassFBO, makeDoubleBuffer } from "./utils.js"; +import { makePassFBO, makeDoubleBuffer, makePass } from "./utils.js"; export default (regl, config, { msdfTex }) => { // These two framebuffers are used to compute the raining code. @@ -292,12 +292,8 @@ export default (regl, config, { msdfTex }) => { framebuffer: output }); - return { - resize: output.resize, - output, - render: resources => { - update(); - render(resources); - } - }; + return makePass(output, resources => { + update(); + render(resources); + }); }; diff --git a/js/utils.js b/js/utils.js index 7b60316..5380184 100644 --- a/js/utils.js +++ b/js/utils.js @@ -99,7 +99,7 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) => count: 3 }); -const makePalette = (regl, data) => +const make1DTexture = (regl, data) => regl.texture({ data, width: data.length / 3, @@ -109,6 +109,24 @@ const makePalette = (regl, data) => min: "linear" }); +const makePass = (output, render, resize) => { + if (render == null) { + render = () => {}; + } + if (resize === undefined) { + // "default" resize function is on the FBO + resize = (w, h) => output.resize(w, h); + } + if (resize == null) { + resize = () => {}; + } + return { + output, + render, + resize + }; +}; + export { makePassTexture, makePassFBO, @@ -118,5 +136,6 @@ export { loadImage, loadImages, makeFullScreenQuad, - makePalette + make1DTexture, + makePass };