diff --git a/js/config.js b/js/config.js index 9d9e96d..b7e9eb3 100644 --- a/js/config.js +++ b/js/config.js @@ -186,7 +186,7 @@ const versions = { bloomStrength: 0.3, numColumns: 50, raindropLength: 0.9, - fallSpeed: 0.15, + fallSpeed: 0.1, cycleStyle: "cycleRandomly", highPassThreshold: 0.0, hasSun: true, @@ -206,6 +206,7 @@ const versions = { cycleStyle: "cycleRandomly", cycleSpeed: 0.8, glyphEdgeCrop: 0.1, + ditherMagnitude: 0, paletteEntries: [ { hsl: [0.39, 0.9, 0.0], at: 0.0 }, { hsl: [0.39, 1.0, 0.6], at: 0.5 }, @@ -216,7 +217,7 @@ const versions = { cursorEffectThreshold: 0.8, renderer: "regl", - bloomSize: 0, + bloomStrength: 0, volumetric: true, forwardSpeed: 0, density: 3, @@ -292,10 +293,16 @@ export default (urlParams) => { const fontName = [validParams.font, version.font, defaults.font].find((name) => name in fonts); const font = fonts[fontName]; - return { + const config = { ...defaults, ...version, ...font, ...validParams, }; + + if (config.bloomSize <= 0) { + config.bloomStrength = 0; + } + + return config; }; diff --git a/js/regl/imagePass.js b/js/regl/imagePass.js index 7428026..783101d 100644 --- a/js/regl/imagePass.js +++ b/js/regl/imagePass.js @@ -7,11 +7,13 @@ const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/ export default ({ regl, config }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; + const bloomStrength = config.bloomStrength; const background = loadImage(regl, bgURL); const imagePassFrag = loadText("shaders/glsl/imagePass.frag.glsl"); const render = regl({ frag: regl.prop("frag"), uniforms: { + bloomStrength, backgroundTex: background.texture, tex: inputs.primary, bloomTex: inputs.bloom, diff --git a/js/regl/palettePass.js b/js/regl/palettePass.js index d42d3f2..591089b 100644 --- a/js/regl/palettePass.js +++ b/js/regl/palettePass.js @@ -65,7 +65,7 @@ const makePalette = (regl, entries) => { export default ({ regl, config }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); const palette = makePalette(regl, config.paletteEntries); - const { backgroundColor, ditherMagnitude } = config; + const { backgroundColor, ditherMagnitude, bloomStrength } = config; const palettePassFrag = loadText("shaders/glsl/palettePass.frag.glsl"); @@ -75,6 +75,7 @@ export default ({ regl, config }, inputs) => { uniforms: { backgroundColor, ditherMagnitude, + bloomStrength, tex: inputs.primary, bloomTex: inputs.bloom, palette, diff --git a/js/regl/resurrectionPass.js b/js/regl/resurrectionPass.js index 7403a98..e43285b 100644 --- a/js/regl/resurrectionPass.js +++ b/js/regl/resurrectionPass.js @@ -10,7 +10,7 @@ import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js"; export default ({ regl, config }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); - const { backgroundColor, ditherMagnitude } = config; + const { backgroundColor, ditherMagnitude, bloomStrength } = config; const resurrectionPassFrag = loadText("shaders/glsl/resurrectionPass.frag.glsl"); const render = regl({ @@ -19,6 +19,7 @@ export default ({ regl, config }, inputs) => { uniforms: { backgroundColor, ditherMagnitude, + bloomStrength, tex: inputs.primary, bloomTex: inputs.bloom, }, diff --git a/js/regl/stripePass.js b/js/regl/stripePass.js index d7e85eb..496519b 100644 --- a/js/regl/stripePass.js +++ b/js/regl/stripePass.js @@ -31,7 +31,7 @@ const prideStripeColors = [ export default ({ regl, config }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); - const { backgroundColor, ditherMagnitude } = config; + const { backgroundColor, ditherMagnitude, bloomStrength } = config; // Expand and convert stripe colors into 1D texture data const stripeColors = @@ -50,6 +50,7 @@ export default ({ regl, config }, inputs) => { uniforms: { backgroundColor, ditherMagnitude, + bloomStrength, tex: inputs.primary, bloomTex: inputs.bloom, stripes, diff --git a/js/webgpu/bloomPass.js b/js/webgpu/bloomPass.js index 0b3a86b..779f6cf 100644 --- a/js/webgpu/bloomPass.js +++ b/js/webgpu/bloomPass.js @@ -15,16 +15,20 @@ import { makeComputeTarget, loadShader, makeUniformBuffer, makeBindGroup, makePa // const makePyramidViews = (pyramid) => [pyramid.createView()]; const makePyramid = (device, size, pyramidHeight) => - Array(pyramidHeight).fill().map((_, index) => makeComputeTarget( - device, - size.map(x => Math.floor(x * 2 ** -(index + 1))) - )); + Array(pyramidHeight) + .fill() + .map((_, index) => + makeComputeTarget( + device, + size.map((x) => Math.floor(x * 2 ** -(index + 1))) + ) + ); -const destroyPyramid = (pyramid) => pyramid?.forEach(texture => texture.destroy()); +const destroyPyramid = (pyramid) => pyramid?.forEach((texture) => texture.destroy()); const makePyramidLevelView = (pyramid, level) => pyramid[level].createView(); -const makePyramidViews = (pyramid) => pyramid.map(tex => tex.createView()); +const makePyramidViews = (pyramid) => pyramid.map((tex) => tex.createView()); // The bloom pass is basically an added blur of the rain pass's high-pass output. // The blur approximation is the sum of a pyramid of downscaled, blurred textures. @@ -89,7 +93,7 @@ export default ({ config, device }) => { vBlurBuffer = makeUniformBuffer(device, blurUniforms, { bloomRadius, direction: [0, 1] }); const combineUniforms = structs.from(combineShader.code).Config; - combineBuffer = makeUniformBuffer(device, combineUniforms, { bloomStrength, pyramidHeight }); + combineBuffer = makeUniformBuffer(device, combineUniforms, { pyramidHeight }); })(); const build = (screenSize, inputs) => { diff --git a/js/webgpu/imagePass.js b/js/webgpu/imagePass.js index 4e2ec1e..9b1f523 100644 --- a/js/webgpu/imagePass.js +++ b/js/webgpu/imagePass.js @@ -1,4 +1,5 @@ -import { makeComputeTarget, loadTexture, loadShader, makeBindGroup, makePass } from "./utils.js"; +import { structs } from "../../lib/gpu-buffer.js"; +import { makeComputeTarget, makeUniformBuffer, loadTexture, loadShader, makeBindGroup, makePass } from "./utils.js"; // Multiplies the rendered rain and bloom by a loaded in image @@ -14,6 +15,7 @@ export default ({ config, device }) => { }); let computePipeline; + let configBuffer; let output; let screenSize; let backgroundTex; @@ -30,6 +32,9 @@ export default ({ config, device }) => { entryPoint: "computeMain", }, }); + + const configUniforms = structs.from(imageShader.code).Config; + configBuffer = makeUniformBuffer(device, configUniforms, { bloomStrength: config.bloomStrength }); })(); const build = (size, inputs) => { @@ -37,6 +42,7 @@ export default ({ config, device }) => { output = makeComputeTarget(device, size); screenSize = size; computeBindGroup = makeBindGroup(device, computePipeline, 0, [ + configBuffer, linearSampler, inputs.primary.createView(), inputs.bloom.createView(), diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js index cf69c68..28fe090 100644 --- a/js/webgpu/palettePass.js +++ b/js/webgpu/palettePass.js @@ -102,7 +102,11 @@ export default ({ config, device, timeBuffer }) => { const paletteShaderUniforms = structs.from(paletteShader.code); const configUniforms = paletteShaderUniforms.Config; - configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: config.ditherMagnitude, backgroundColor: config.backgroundColor }); + configBuffer = makeUniformBuffer(device, configUniforms, { + bloomStrength: config.bloomStrength, + ditherMagnitude: config.ditherMagnitude, + backgroundColor: config.backgroundColor, + }); const paletteUniforms = paletteShaderUniforms.Palette; paletteBuffer = makePalette(device, paletteUniforms, config.paletteEntries); diff --git a/js/webgpu/resurrectionPass.js b/js/webgpu/resurrectionPass.js index 7fb4d5f..56102a4 100644 --- a/js/webgpu/resurrectionPass.js +++ b/js/webgpu/resurrectionPass.js @@ -36,7 +36,11 @@ export default ({ config, device, timeBuffer }) => { }); const configUniforms = structs.from(resurrectionShader.code).Config; - configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: config.ditherMagnitude, backgroundColor: config.backgroundColor }); + configBuffer = makeUniformBuffer(device, configUniforms, { + bloomStrength: config.bloomStrength, + ditherMagnitude: config.ditherMagnitude, + backgroundColor: config.backgroundColor, + }); })(); const build = (size, inputs) => { diff --git a/js/webgpu/stripePass.js b/js/webgpu/stripePass.js index 772fbcf..5b50185 100644 --- a/js/webgpu/stripePass.js +++ b/js/webgpu/stripePass.js @@ -73,7 +73,11 @@ export default ({ config, device, timeBuffer }) => { }); const configUniforms = structs.from(stripeShader.code).Config; - configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude: config.ditherMagnitude, backgroundColor: config.backgroundColor }); + configBuffer = makeUniformBuffer(device, configUniforms, { + bloomStrength: config.bloomStrength, + ditherMagnitude: config.ditherMagnitude, + backgroundColor: config.backgroundColor, + }); })(); const build = (size, inputs) => { diff --git a/js/webgpu/utils.js b/js/webgpu/utils.js index 7fab8c9..8649951 100644 --- a/js/webgpu/utils.js +++ b/js/webgpu/utils.js @@ -24,6 +24,7 @@ const loadTexture = async (device, url) => { const loadTexture = async (device, url) => { const image = new Image(); + image.crossOrigin = "Anonymous"; image.src = url; await image.decode(); const { width, height } = image; diff --git a/shaders/glsl/imagePass.frag.glsl b/shaders/glsl/imagePass.frag.glsl index e2d1fbf..e7b5986 100644 --- a/shaders/glsl/imagePass.frag.glsl +++ b/shaders/glsl/imagePass.frag.glsl @@ -2,13 +2,20 @@ precision mediump float; uniform sampler2D tex; uniform sampler2D bloomTex; uniform sampler2D backgroundTex; +uniform float bloomStrength; varying vec2 vUV; +vec4 getBrightness(vec2 uv) { + vec4 primary = texture2D(tex, uv); + vec4 bloom = texture2D(bloomTex, uv) * bloomStrength; + return min((primary + bloom) * (2.0 - bloomStrength), 1.0); +} + void main() { vec3 bgColor = texture2D(backgroundTex, vUV).rgb; // Combine the texture and bloom, then blow it out to reveal more of the image - float brightness = pow(min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r, 1.5); + float brightness = pow(getBrightness(vUV).r, 1.5); gl_FragColor = vec4(bgColor * brightness, 1.0); } diff --git a/shaders/glsl/palettePass.frag.glsl b/shaders/glsl/palettePass.frag.glsl index d611952..8e11512 100644 --- a/shaders/glsl/palettePass.frag.glsl +++ b/shaders/glsl/palettePass.frag.glsl @@ -4,6 +4,7 @@ precision mediump float; uniform sampler2D tex; uniform sampler2D bloomTex; uniform sampler2D palette; +uniform float bloomStrength; uniform float ditherMagnitude; uniform float time; uniform vec3 backgroundColor; @@ -15,8 +16,14 @@ highp float rand( const in vec2 uv, const in float t ) { return fract(sin(sn) * c + t); } +vec4 getBrightness(vec2 uv) { + vec4 primary = texture2D(tex, uv); + vec4 bloom = texture2D(bloomTex, uv) * bloomStrength; + return min((primary + bloom) * (2.0 - bloomStrength), 1.0); +} + void main() { - vec4 brightnessRGB = texture2D( tex, vUV ) + texture2D( bloomTex, vUV ); + vec4 brightnessRGB = getBrightness(vUV); // Combine the texture and bloom float brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b; diff --git a/shaders/glsl/resurrectionPass.frag.glsl b/shaders/glsl/resurrectionPass.frag.glsl index 0d73a83..da51a27 100644 --- a/shaders/glsl/resurrectionPass.frag.glsl +++ b/shaders/glsl/resurrectionPass.frag.glsl @@ -3,6 +3,7 @@ precision mediump float; uniform sampler2D tex; uniform sampler2D bloomTex; +uniform float bloomStrength; uniform float ditherMagnitude; uniform float time; uniform vec3 backgroundColor; @@ -38,7 +39,7 @@ void main() { // Mix the texture and bloom based on distance from center, // to approximate a lens blur vec3 brightness = mix( - texture2D( bloomTex, vUV ).rgb, + texture2D( bloomTex, vUV ).rgb * bloomStrength, texture2D( tex, vUV ).rgb, (0.7 - length(vUV - 0.5)) ) * 1.25; diff --git a/shaders/glsl/stripePass.frag.glsl b/shaders/glsl/stripePass.frag.glsl index 6eebd0c..9f97b31 100644 --- a/shaders/glsl/stripePass.frag.glsl +++ b/shaders/glsl/stripePass.frag.glsl @@ -3,6 +3,7 @@ precision mediump float; uniform sampler2D tex; uniform sampler2D bloomTex; +uniform float bloomStrength; uniform sampler2D stripes; uniform float ditherMagnitude; uniform float time; @@ -15,10 +16,16 @@ highp float rand( const in vec2 uv, const in float t ) { return fract(sin(sn) * c + t); } +vec4 getBrightness(vec2 uv) { + vec4 primary = texture2D(tex, uv); + vec4 bloom = texture2D(bloomTex, uv) * bloomStrength; + return min((primary + bloom) * (2.0 - bloomStrength), 1.0); +} + void main() { vec3 color = texture2D(stripes, vUV).rgb; // Combine the texture and bloom - float brightness = min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r; + float brightness = getBrightness(vUV).r; // Dither: subtract a random value from the brightness brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude; diff --git a/shaders/wgsl/bloomCombine.wgsl b/shaders/wgsl/bloomCombine.wgsl index 3be338e..0cdefda 100644 --- a/shaders/wgsl/bloomCombine.wgsl +++ b/shaders/wgsl/bloomCombine.wgsl @@ -1,5 +1,4 @@ struct Config { - bloomStrength : f32; pyramidHeight : f32; }; @@ -63,5 +62,5 @@ struct ComputeInput { sum = sum + textureSampleLevel( tex4, linearSampler, uv, i + 1.0 ) * weight; } - textureStore(outputTex, coord, sum * config.bloomStrength); + textureStore(outputTex, coord, sum); } diff --git a/shaders/wgsl/imagePass.wgsl b/shaders/wgsl/imagePass.wgsl index 7ab4e99..3d242e6 100644 --- a/shaders/wgsl/imagePass.wgsl +++ b/shaders/wgsl/imagePass.wgsl @@ -1,13 +1,24 @@ -[[group(0), binding(0)]] var linearSampler : sampler; -[[group(0), binding(1)]] var tex : texture_2d; -[[group(0), binding(2)]] var bloomTex : texture_2d; -[[group(0), binding(3)]] var backgroundTex : texture_2d; -[[group(0), binding(4)]] var outputTex : texture_storage_2d; +struct Config { + bloomStrength : f32; +}; + +[[group(0), binding(0)]] var config : Config; +[[group(0), binding(1)]] var linearSampler : sampler; +[[group(0), binding(2)]] var tex : texture_2d; +[[group(0), binding(3)]] var bloomTex : texture_2d; +[[group(0), binding(4)]] var backgroundTex : texture_2d; +[[group(0), binding(5)]] var outputTex : texture_storage_2d; struct ComputeInput { [[builtin(global_invocation_id)]] id : vec3; }; +fn getBrightness(uv : vec2) -> vec4 { + var primary = textureSampleLevel(tex, linearSampler, uv, 0.0); + var bloom = textureSampleLevel(bloomTex, linearSampler, uv, 0.0) * config.bloomStrength; + return min((primary + bloom) * (2.0 - config.bloomStrength), vec4(1.0)); +} + [[stage(compute), workgroup_size(32, 1, 1)]] fn computeMain(input : ComputeInput) { // Resolve the invocation ID to a texel coordinate @@ -23,8 +34,7 @@ struct ComputeInput { var bgColor = textureSampleLevel( backgroundTex, linearSampler, uv, 0.0 ).rgb; // Combine the texture and bloom, then blow it out to reveal more of the image - var brightness = min(1.0, textureSampleLevel( tex, linearSampler, uv, 0.0 ).r * 2.0); - brightness = brightness + textureSampleLevel( bloomTex, linearSampler, uv, 0.0 ).r; + var brightness = getBrightness(uv).r; brightness = pow(brightness, 1.5); textureStore(outputTex, coord, vec4(bgColor * brightness, 1.0)); diff --git a/shaders/wgsl/palettePass.wgsl b/shaders/wgsl/palettePass.wgsl index 60b3fe2..e490f53 100644 --- a/shaders/wgsl/palettePass.wgsl +++ b/shaders/wgsl/palettePass.wgsl @@ -1,4 +1,5 @@ struct Config { + bloomStrength : f32; ditherMagnitude : f32; backgroundColor : vec3; }; @@ -35,6 +36,11 @@ fn randomFloat( uv : vec2 ) -> f32 { return fract(sin(sn) * c); } +fn getBrightness(uv : vec2) -> vec4 { + var primary = textureSampleLevel(tex, linearSampler, uv, 0.0); + var bloom = textureSampleLevel(bloomTex, linearSampler, uv, 0.0) * config.bloomStrength; + return min((primary + bloom) * (2.0 - config.bloomStrength), vec4(1.0)); +} [[stage(compute), workgroup_size(32, 1, 1)]] fn computeMain(input : ComputeInput) { @@ -48,7 +54,7 @@ fn randomFloat( uv : vec2 ) -> f32 { var uv = vec2(coord) / vec2(screenSize); - var brightnessRGB = textureSampleLevel( tex, linearSampler, uv, 0.0 ) + textureSampleLevel( bloomTex, linearSampler, uv, 0.0 ); + var brightnessRGB = getBrightness(uv); // Combine the texture and bloom var brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b; diff --git a/shaders/wgsl/resurrectionPass.wgsl b/shaders/wgsl/resurrectionPass.wgsl index 2732476..91fe0b7 100644 --- a/shaders/wgsl/resurrectionPass.wgsl +++ b/shaders/wgsl/resurrectionPass.wgsl @@ -1,4 +1,5 @@ struct Config { + bloomStrength : f32; ditherMagnitude : f32; backgroundColor : vec3; }; @@ -71,7 +72,7 @@ fn hslToRgb(h : f32, s : f32, l : f32) -> vec3 { // to approximate a lens blur var brightness = mix( textureSampleLevel( tex, linearSampler, uv, 0.0 ).rgb, - textureSampleLevel( bloomTex, linearSampler, uv, 0.0 ).rgb, + textureSampleLevel( bloomTex, linearSampler, uv, 0.0 ).rgb * config.bloomStrength, (0.7 - length(uv - 0.5)) ) * 1.25; diff --git a/shaders/wgsl/stripePass.wgsl b/shaders/wgsl/stripePass.wgsl index 20f94e7..2bce982 100644 --- a/shaders/wgsl/stripePass.wgsl +++ b/shaders/wgsl/stripePass.wgsl @@ -1,4 +1,5 @@ struct Config { + bloomStrength : f32; ditherMagnitude : f32; backgroundColor : vec3; }; @@ -31,6 +32,12 @@ fn randomFloat( uv : vec2 ) -> f32 { return fract(sin(sn) * c); } +fn getBrightness(uv : vec2) -> vec4 { + var primary = textureSampleLevel(tex, linearSampler, uv, 0.0); + var bloom = textureSampleLevel(bloomTex, linearSampler, uv, 0.0) * config.bloomStrength; + return min((primary + bloom) * (2.0 - config.bloomStrength), vec4(1.0)); +} + [[stage(compute), workgroup_size(32, 1, 1)]] fn computeMain(input : ComputeInput) { // Resolve the invocation ID to a texel coordinate @@ -45,9 +52,7 @@ fn randomFloat( uv : vec2 ) -> f32 { var color = textureSampleLevel( stripeTexture, linearSampler, uv, 0.0 ).rgb; - // Combine the texture and bloom - var brightness = min(1.0, textureSampleLevel( tex, linearSampler, uv, 0.0 ).r * 2.0); - brightness = brightness + textureSampleLevel( bloomTex, linearSampler, uv, 0.0 ).r; + var brightness = getBrightness(uv).r; // Dither: subtract a random value from the brightness brightness = brightness - randomFloat( uv + vec2(time.seconds) ) * config.ditherMagnitude;