From ddf5ef77facacd18d239c4b34eb871545013ddcc Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Thu, 22 May 2025 16:55:57 +0100 Subject: [PATCH] Break compose shader to chunks for easier customization + convert to WGSL --- .../render-passes/render-pass-compose.js | 13 +- .../glsl/chunks/render-pass/frag/compose.js | 264 - .../render-pass/frag/compose/compose-bloom.js | 14 + .../render-pass/frag/compose/compose-cas.js | 40 + .../render-pass/frag/compose/compose-dof.js | 52 + .../frag/compose/compose-fringing.js | 15 + .../frag/compose/compose-grading.js | 30 + .../render-pass/frag/compose/compose-ssao.js | 26 + .../frag/compose/compose-vignette.js | 29 + .../render-pass/frag/compose/compose.js | 90 + .../glsl/collections/compose-chunks-glsl.js | 19 + .../frag/tonemapping/tonemappingAces2.js | 4 +- .../render-pass/frag/compose/compose-bloom.js | 15 + .../render-pass/frag/compose/compose-cas.js | 40 + .../render-pass/frag/compose/compose-dof.js | 54 + .../frag/compose/compose-fringing.js | 16 + .../frag/compose/compose-grading.js | 30 + .../render-pass/frag/compose/compose-ssao.js | 27 + .../frag/compose/compose-vignette.js | 29 + .../render-pass/frag/compose/compose.js | 92 + .../wgsl/collections/compose-chunks-wgsl.js | 19 + treemap.html | 4949 +++++++++++++++++ 22 files changed, 5597 insertions(+), 270 deletions(-) delete mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-bloom.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-cas.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-dof.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-fringing.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-grading.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-ssao.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-vignette.js create mode 100644 src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose.js create mode 100644 src/scene/shader-lib/glsl/collections/compose-chunks-glsl.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-bloom.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-cas.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-dof.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-fringing.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-grading.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-ssao.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-vignette.js create mode 100644 src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose.js create mode 100644 src/scene/shader-lib/wgsl/collections/compose-chunks-wgsl.js create mode 100644 treemap.html diff --git a/src/extras/render-passes/render-pass-compose.js b/src/extras/render-passes/render-pass-compose.js index bafed72a04e..6ba03370be4 100644 --- a/src/extras/render-passes/render-pass-compose.js +++ b/src/extras/render-passes/render-pass-compose.js @@ -3,9 +3,10 @@ import { Color } from '../../core/math/color.js'; import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js'; import { GAMMA_NONE, GAMMA_SRGB, gammaNames, TONEMAP_LINEAR, tonemapNames } from '../../scene/constants.js'; import { ShaderChunks } from '../../scene/shader-lib/shader-chunks.js'; -import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL } from '../../platform/graphics/constants.js'; +import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; import { ShaderUtils } from '../../scene/shader-lib/shader-utils.js'; -import glslComposePS from '../../scene/shader-lib/glsl/chunks/render-pass/frag/compose.js'; +import { composeChunksGLSL } from '../../scene/shader-lib/glsl/collections/compose-chunks-glsl.js'; +import { composeChunksWGSL } from '../../scene/shader-lib/wgsl/collections/compose-chunks-wgsl.js'; /** * Render pass implementation of the final post-processing composition. @@ -69,6 +70,10 @@ class RenderPassCompose extends RenderPassShaderQuad { constructor(graphicsDevice) { super(graphicsDevice); + // register compose shader chunks + ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL).add(composeChunksGLSL); + ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_WGSL).add(composeChunksWGSL); + const { scope } = graphicsDevice; this.sceneTextureId = scope.resolve('sceneTexture'); this.bloomTextureId = scope.resolve('bloomTexture'); @@ -254,13 +259,13 @@ class RenderPassCompose extends RenderPassShaderQuad { if (this.isSharpnessEnabled) defines.set('CAS', true); if (this._debug) defines.set('DEBUG_COMPOSE', this._debug); - const includes = new Map(ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL)); + const includes = new Map(ShaderChunks.get(this.device, this.device.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL)); this.shader = ShaderUtils.createShader(this.device, { uniqueName: `ComposeShader-${key}`, attributes: { aPosition: SEMANTIC_POSITION }, vertexChunk: 'quadVS', - fragmentGLSL: glslComposePS, + fragmentChunk: 'composePS', fragmentDefines: defines, fragmentIncludes: includes }); diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose.js deleted file mode 100644 index 094b38f6e59..00000000000 --- a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose.js +++ /dev/null @@ -1,264 +0,0 @@ -// Contrast Adaptive Sharpening (CAS) is used to apply the sharpening. It's based on AMD's -// FidelityFX CAS, WebGL implementation: https://www.shadertoy.com/view/wtlSWB. It's best to run it -// on a tone-mapped color buffer after post-processing, but before the UI, and so this is the -// obvious place to put it to avoid a separate render pass, even though we need to handle running it -// before the tone-mapping. -export default /* glsl */` - #include "tonemappingPS" - #include "gammaPS" - - varying vec2 uv0; - uniform sampler2D sceneTexture; - uniform vec2 sceneTextureInvRes; - - #ifdef BLOOM - uniform sampler2D bloomTexture; - uniform float bloomIntensity; - #endif - - #ifdef DOF - uniform sampler2D cocTexture; - uniform sampler2D blurTexture; - - // Samples the DOF blur and CoC textures. When the blur texture was generated at lower resolution, - // upscale it to the full resolution using bilinear interpolation to hide the blockiness along COC edges. - vec3 dofBlur(vec2 uv, out vec2 coc) { - coc = texture2DLod(cocTexture, uv, 0.0).rg; - - #if DOF_UPSCALE - vec2 blurTexelSize = 1.0 / vec2(textureSize(blurTexture, 0)); - vec3 bilinearBlur = vec3(0.0); - float totalWeight = 0.0; - - // 3x3 grid of neighboring texels - for (int i = -1; i <= 1; i++) { - for (int j = -1; j <= 1; j++) { - vec2 offset = vec2(i, j) * blurTexelSize; - vec2 cocSample = texture2DLod(cocTexture, uv + offset, 0.0).rg; - vec3 blurSample = texture2DLod(blurTexture, uv + offset, 0.0).rgb; - - // Accumulate the weighted blur sample - float cocWeight = clamp(cocSample.r + cocSample.g, 0.0, 1.0); - bilinearBlur += blurSample * cocWeight; - totalWeight += cocWeight; - } - } - - // normalize the accumulated color - if (totalWeight > 0.0) { - bilinearBlur /= totalWeight; - } - - return bilinearBlur; - #else - // when blurTexture is full resolution, just sample it, no upsampling - return texture2DLod(blurTexture, uv, 0.0).rgb; - #endif - } - - #endif - - #ifdef SSAO - #define SSAO_TEXTURE - #endif - - #if DEBUG_COMPOSE == ssao - #define SSAO_TEXTURE - #endif - - #ifdef SSAO_TEXTURE - uniform sampler2D ssaoTexture; - #endif - - #ifdef GRADING - uniform vec3 brightnessContrastSaturation; - uniform vec3 tint; - - // for all parameters, 1.0 is the no-change value - vec3 colorGradingHDR(vec3 color, float brt, float sat, float con) - { - // tint - color *= tint; - - // brightness - color = color * brt; - - // saturation - float grey = dot(color, vec3(0.3, 0.59, 0.11)); - grey = grey / max(1.0, max(color.r, max(color.g, color.b))); // Normalize luminance in HDR to preserve intensity (optional) - color = mix(vec3(grey), color, sat); - - // contrast - return mix(vec3(0.5), color, con); - } - - #endif - - #ifdef VIGNETTE - - uniform vec4 vignetterParams; - - float vignette(vec2 uv) { - - float inner = vignetterParams.x; - float outer = vignetterParams.y; - float curvature = vignetterParams.z; - float intensity = vignetterParams.w; - - // edge curvature - vec2 curve = pow(abs(uv * 2.0 -1.0), vec2(1.0 / curvature)); - - // distance to edge - float edge = pow(length(curve), curvature); - - // gradient and intensity - return 1.0 - intensity * smoothstep(inner, outer, edge); - } - - #endif - - #ifdef FRINGING - - uniform float fringingIntensity; - - vec3 fringing(vec2 uv, vec3 color) { - - // offset depends on the direction from the center, raised to power to make it stronger away from the center - vec2 centerDistance = uv - 0.5; - vec2 offset = fringingIntensity * pow(centerDistance, vec2(2.0, 2.0)); - - color.r = texture2D(sceneTexture, uv - offset).r; - color.b = texture2D(sceneTexture, uv + offset).b; - return color; - } - - #endif - - #ifdef CAS - - uniform float sharpness; - - // reversible LDR <-> HDR tone mapping, as CAS needs LDR input - // based on: https://gpuopen.com/learn/optimized-reversible-tonemapper-for-resolve/ - float maxComponent(float x, float y, float z) { return max(x, max(y, z)); } - vec3 toSDR(vec3 c) { return c / (1.0 + maxComponent(c.r, c.g, c.b)); } - vec3 toHDR(vec3 c) { return c / (1.0 - maxComponent(c.r, c.g, c.b)); } - - vec3 cas(vec3 color, vec2 uv, float sharpness) { - - float x = sceneTextureInvRes.x; - float y = sceneTextureInvRes.y; - - // sample 4 neighbors around the already sampled pixel, and convert it to SDR - vec3 a = toSDR(texture2DLod(sceneTexture, uv + vec2(0.0, -y), 0.0).rgb); - vec3 b = toSDR(texture2DLod(sceneTexture, uv + vec2(-x, 0.0), 0.0).rgb); - vec3 c = toSDR(color.rgb); - vec3 d = toSDR(texture2DLod(sceneTexture, uv + vec2(x, 0.0), 0.0).rgb); - vec3 e = toSDR(texture2DLod(sceneTexture, uv + vec2(0.0, y), 0.0).rgb); - - // apply the sharpening - float min_g = min(a.g, min(b.g, min(c.g, min(d.g, e.g)))); - float max_g = max(a.g, max(b.g, max(c.g, max(d.g, e.g)))); - float sharpening_amount = sqrt(min(1.0 - max_g, min_g) / max_g); - float w = sharpening_amount * sharpness; - vec3 res = (w * (a + b + d + e) + c) / (4.0 * w + 1.0); - - // remove negative colors - res = max(res, 0.0); - - // convert back to HDR - return toHDR(res); - } - - #endif - - void main() { - - vec2 uv = uv0; - - // TAA pass renders upside-down on WebGPU, flip it here - #ifdef TAA - #ifdef WEBGPU - uv.y = 1.0 - uv.y; - #endif - #endif - - vec4 scene = texture2DLod(sceneTexture, uv, 0.0); - vec3 result = scene.rgb; - - #ifdef CAS - result = cas(result, uv, sharpness); - #endif - - #ifdef DOF - vec2 coc; - vec3 blur = dofBlur(uv0, coc); - result = mix(result, blur, coc.r + coc.g); - #endif - - #ifdef SSAO_TEXTURE - mediump float ssao = texture2DLod(ssaoTexture, uv0, 0.0).r; - #endif - - #ifdef SSAO - result *= ssao; - #endif - - #ifdef FRINGING - result = fringing(uv, result); - #endif - - #ifdef BLOOM - vec3 bloom = texture2DLod(bloomTexture, uv0, 0.0).rgb; - result += bloom * bloomIntensity; - #endif - - #ifdef GRADING - // color grading takes place in HDR space before tone mapping - result = colorGradingHDR(result, brightnessContrastSaturation.x, brightnessContrastSaturation.z, brightnessContrastSaturation.y); - #endif - - result = toneMap(result); - - #ifdef VIGNETTE - mediump float vig = vignette(uv); - result *= vig; - #endif - - // debug output - #ifdef DEBUG_COMPOSE - - #ifdef BLOOM - #if DEBUG_COMPOSE == bloom - result = bloom * bloomIntensity; - #endif - #endif - - #ifdef DOF - #ifdef DEBUG_COMPOSE == dofcoc - result = vec3(coc, 0.0); - #endif - #ifdef DEBUG_COMPOSE == dofblur - result = blur; - #endif - #endif - - #if DEBUG_COMPOSE == ssao - result = vec3(ssao); - #endif - - #if DEBUG_COMPOSE == vignette - result = vec3(vig); - #endif - - #if DEBUG_COMPOSE == scene - result = scene.rgb; - #endif - - #endif - - result = gammaCorrectOutput(result); - - gl_FragColor = vec4(result, scene.a); - } -`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-bloom.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-bloom.js new file mode 100644 index 00000000000..d92d0a25313 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-bloom.js @@ -0,0 +1,14 @@ +export default /* glsl */` + #ifdef BLOOM + uniform sampler2D bloomTexture; + uniform float bloomIntensity; + + // Global variable for debug + vec3 dBloom; + + vec3 applyBloom(vec3 color, vec2 uv) { + dBloom = texture2DLod(bloomTexture, uv, 0.0).rgb; + return color + dBloom * bloomIntensity; + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-cas.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-cas.js new file mode 100644 index 00000000000..322e5a0e75d --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-cas.js @@ -0,0 +1,40 @@ +// Contrast Adaptive Sharpening (CAS) is used to apply the sharpening. It's based on AMD's +// FidelityFX CAS, WebGL implementation: https://www.shadertoy.com/view/wtlSWB. It's best to run it +// on a tone-mapped color buffer after post-processing, but before the UI, and so this is the +// obvious place to put it to avoid a separate render pass, even though we need to handle running it +// before the tone-mapping. +export default /* glsl */` + #ifdef CAS + uniform float sharpness; + + // reversible LDR <-> HDR tone mapping, as CAS needs LDR input + float maxComponent(float x, float y, float z) { return max(x, max(y, z)); } + vec3 toSDR(vec3 c) { return c / (1.0 + maxComponent(c.r, c.g, c.b)); } + vec3 toHDR(vec3 c) { return c / (1.0 - maxComponent(c.r, c.g, c.b)); } + + vec3 applyCas(vec3 color, vec2 uv, float sharpness) { + float x = sceneTextureInvRes.x; + float y = sceneTextureInvRes.y; + + // sample 4 neighbors around the already sampled pixel, and convert it to SDR + vec3 a = toSDR(texture2DLod(sceneTexture, uv + vec2(0.0, -y), 0.0).rgb); + vec3 b = toSDR(texture2DLod(sceneTexture, uv + vec2(-x, 0.0), 0.0).rgb); + vec3 c = toSDR(color.rgb); + vec3 d = toSDR(texture2DLod(sceneTexture, uv + vec2(x, 0.0), 0.0).rgb); + vec3 e = toSDR(texture2DLod(sceneTexture, uv + vec2(0.0, y), 0.0).rgb); + + // apply the sharpening + float min_g = min(a.g, min(b.g, min(c.g, min(d.g, e.g)))); + float max_g = max(a.g, max(b.g, max(c.g, max(d.g, e.g)))); + float sharpening_amount = sqrt(min(1.0 - max_g, min_g) / max_g); + float w = sharpening_amount * sharpness; + vec3 res = (w * (a + b + d + e) + c) / (4.0 * w + 1.0); + + // remove negative colors + res = max(res, 0.0); + + // convert back to HDR + return toHDR(res); + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-dof.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-dof.js new file mode 100644 index 00000000000..cc4537f4829 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-dof.js @@ -0,0 +1,52 @@ +export default /* glsl */` + #ifdef DOF + uniform sampler2D cocTexture; + uniform sampler2D blurTexture; + + // Global variables for debug + vec2 dCoc; + vec3 dBlur; + + // Samples the DOF blur and CoC textures + vec3 getDofBlur(vec2 uv) { + dCoc = texture2DLod(cocTexture, uv, 0.0).rg; + + #if DOF_UPSCALE + vec2 blurTexelSize = 1.0 / vec2(textureSize(blurTexture, 0)); + vec3 bilinearBlur = vec3(0.0); + float totalWeight = 0.0; + + // 3x3 grid of neighboring texels + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + vec2 offset = vec2(i, j) * blurTexelSize; + vec2 cocSample = texture2DLod(cocTexture, uv + offset, 0.0).rg; + vec3 blurSample = texture2DLod(blurTexture, uv + offset, 0.0).rgb; + + // Accumulate the weighted blur sample + float cocWeight = clamp(cocSample.r + cocSample.g, 0.0, 1.0); + bilinearBlur += blurSample * cocWeight; + totalWeight += cocWeight; + } + } + + // normalize the accumulated color + if (totalWeight > 0.0) { + bilinearBlur /= totalWeight; + } + + dBlur = bilinearBlur; + return bilinearBlur; + #else + // when blurTexture is full resolution, just sample it, no upsampling + dBlur = texture2DLod(blurTexture, uv, 0.0).rgb; + return dBlur; + #endif + } + + vec3 applyDof(vec3 color, vec2 uv) { + vec3 blur = getDofBlur(uv); + return mix(color, blur, dCoc.r + dCoc.g); + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-fringing.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-fringing.js new file mode 100644 index 00000000000..53eb1897ef6 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-fringing.js @@ -0,0 +1,15 @@ +export default /* glsl */` + #ifdef FRINGING + uniform float fringingIntensity; + + vec3 applyFringing(vec3 color, vec2 uv) { + // offset depends on the direction from the center + vec2 centerDistance = uv - 0.5; + vec2 offset = fringingIntensity * pow(centerDistance, vec2(2.0, 2.0)); + + color.r = texture2D(sceneTexture, uv - offset).r; + color.b = texture2D(sceneTexture, uv + offset).b; + return color; + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-grading.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-grading.js new file mode 100644 index 00000000000..723ca4debf0 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-grading.js @@ -0,0 +1,30 @@ +export default /* glsl */` + #ifdef GRADING + uniform vec3 brightnessContrastSaturation; + uniform vec3 tint; + + // for all parameters, 1.0 is the no-change value + vec3 colorGradingHDR(vec3 color, float brt, float sat, float con) { + // tint + color *= tint; + + // brightness + color = color * brt; + + // saturation + float grey = dot(color, vec3(0.3, 0.59, 0.11)); + grey = grey / max(1.0, max(color.r, max(color.g, color.b))); // Normalize luminance in HDR to preserve intensity + color = mix(vec3(grey), color, sat); + + // contrast + return mix(vec3(0.5), color, con); + } + + vec3 applyGrading(vec3 color) { + return colorGradingHDR(color, + brightnessContrastSaturation.x, + brightnessContrastSaturation.z, + brightnessContrastSaturation.y); + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-ssao.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-ssao.js new file mode 100644 index 00000000000..a02b3d92104 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-ssao.js @@ -0,0 +1,26 @@ +export default /* glsl */` + #ifdef SSAO + #define SSAO_TEXTURE + #endif + + #if DEBUG_COMPOSE == ssao + #define SSAO_TEXTURE + #endif + + #ifdef SSAO_TEXTURE + uniform sampler2D ssaoTexture; + + // Global variable for debug + float dSsao; + + vec3 applySsao(vec3 color, vec2 uv) { + dSsao = texture2DLod(ssaoTexture, uv, 0.0).r; + + #ifdef SSAO + return color * dSsao; + #else + return color; + #endif + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-vignette.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-vignette.js new file mode 100644 index 00000000000..576088cab04 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose-vignette.js @@ -0,0 +1,29 @@ +export default /* glsl */` + #ifdef VIGNETTE + uniform vec4 vignetterParams; + + // Global variable for debug + float dVignette; + + float calcVignette(vec2 uv) { + float inner = vignetterParams.x; + float outer = vignetterParams.y; + float curvature = vignetterParams.z; + float intensity = vignetterParams.w; + + // edge curvature + vec2 curve = pow(abs(uv * 2.0 -1.0), vec2(1.0 / curvature)); + + // distance to edge + float edge = pow(length(curve), curvature); + + // gradient and intensity + dVignette = 1.0 - intensity * smoothstep(inner, outer, edge); + return dVignette; + } + + vec3 applyVignette(vec3 color, vec2 uv) { + return color * calcVignette(uv); + } + #endif +`; diff --git a/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose.js b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose.js new file mode 100644 index 00000000000..aaf18824115 --- /dev/null +++ b/src/scene/shader-lib/glsl/chunks/render-pass/frag/compose/compose.js @@ -0,0 +1,90 @@ +export default /* glsl */` + #include "tonemappingPS" + #include "gammaPS" + + varying vec2 uv0; + uniform sampler2D sceneTexture; + uniform vec2 sceneTextureInvRes; + + #include "composeBloomPS" + #include "composeDofPS" + #include "composeSsaoPS" + #include "composeGradingPS" + #include "composeVignettePS" + #include "composeFringingPS" + #include "composeCasPS" + + void main() { + vec2 uv = uv0; + + // TAA pass renders upside-down on WebGPU, flip it here + #ifdef TAA + #ifdef WEBGPU + uv.y = 1.0 - uv.y; + #endif + #endif + + vec4 scene = texture2DLod(sceneTexture, uv, 0.0); + vec3 result = scene.rgb; + + // Apply CAS + #ifdef CAS + result = applyCas(result, uv, sharpness); + #endif + + // Apply DOF + #ifdef DOF + result = applyDof(result, uv0); + #endif + + // Apply SSAO + #ifdef SSAO_TEXTURE + result = applySsao(result, uv0); + #endif + + // Apply Fringing + #ifdef FRINGING + result = applyFringing(result, uv); + #endif + + // Apply Bloom + #ifdef BLOOM + result = applyBloom(result, uv0); + #endif + + // Apply Color Grading + #ifdef GRADING + result = applyGrading(result); + #endif + + // Apply Tone Mapping + result = toneMap(result); + + // Apply Vignette + #ifdef VIGNETTE + result = applyVignette(result, uv); + #endif + + // Debug output handling in one centralized location + #ifdef DEBUG_COMPOSE + #if DEBUG_COMPOSE == scene + result = scene.rgb; + #elif defined(BLOOM) && DEBUG_COMPOSE == bloom + result = dBloom * bloomIntensity; + #elif defined(DOF) && DEBUG_COMPOSE == dofcoc + result = vec3(dCoc, 0.0); + #elif defined(DOF) && DEBUG_COMPOSE == dofblur + result = dBlur; + #elif defined(SSAO_TEXTURE) && DEBUG_COMPOSE == ssao + result = vec3(dSsao); + #elif defined(VIGNETTE) && DEBUG_COMPOSE == vignette + result = vec3(dVignette); + #endif + #endif + + // Apply gamma correction + result = gammaCorrectOutput(result); + + gl_FragColor = vec4(result, scene.a); + } +`; diff --git a/src/scene/shader-lib/glsl/collections/compose-chunks-glsl.js b/src/scene/shader-lib/glsl/collections/compose-chunks-glsl.js new file mode 100644 index 00000000000..f1ba473b422 --- /dev/null +++ b/src/scene/shader-lib/glsl/collections/compose-chunks-glsl.js @@ -0,0 +1,19 @@ +import composePS from '../chunks/render-pass/frag/compose/compose.js'; +import composeBloomPS from '../chunks/render-pass/frag/compose/compose-bloom.js'; +import composeDofPS from '../chunks/render-pass/frag/compose/compose-dof.js'; +import composeSsaoPS from '../chunks/render-pass/frag/compose/compose-ssao.js'; +import composeGradingPS from '../chunks/render-pass/frag/compose/compose-grading.js'; +import composeVignettePS from '../chunks/render-pass/frag/compose/compose-vignette.js'; +import composeFringingPS from '../chunks/render-pass/frag/compose/compose-fringing.js'; +import composeCasPS from '../chunks/render-pass/frag/compose/compose-cas.js'; + +export const composeChunksGLSL = { + composePS, + composeBloomPS, + composeDofPS, + composeSsaoPS, + composeGradingPS, + composeVignettePS, + composeFringingPS, + composeCasPS +}; diff --git a/src/scene/shader-lib/wgsl/chunks/common/frag/tonemapping/tonemappingAces2.js b/src/scene/shader-lib/wgsl/chunks/common/frag/tonemapping/tonemappingAces2.js index ae7fd3c8118..b9929369221 100644 --- a/src/scene/shader-lib/wgsl/chunks/common/frag/tonemapping/tonemappingAces2.js +++ b/src/scene/shader-lib/wgsl/chunks/common/frag/tonemapping/tonemappingAces2.js @@ -25,11 +25,11 @@ fn RRTAndODTFit(v: vec3f) -> vec3f { fn toneMap(color: vec3f) -> vec3f { var c: vec3f = color * (uniform.exposure / 0.6); - c = ACESInputMat * c; + c = c * ACESInputMat; // Apply RRT and ODT c = RRTAndODTFit(c); - c = ACESOutputMat * c; + c = c * ACESOutputMat; // Clamp to [0, 1] return clamp(c, vec3f(0.0), vec3f(1.0)); diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-bloom.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-bloom.js new file mode 100644 index 00000000000..8d5a5a45e02 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-bloom.js @@ -0,0 +1,15 @@ +export default /* wgsl */` + #ifdef BLOOM + var bloomTexture: texture_2d; + var bloomTextureSampler: sampler; + uniform bloomIntensity: f32; + + // Global variable for debug + var dBloom: vec3f; + + fn applyBloom(color: vec3f, uv: vec2f) -> vec3f { + dBloom = textureSampleLevel(bloomTexture, bloomTextureSampler, uv, 0.0).rgb; + return color + dBloom * uniform.bloomIntensity; + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-cas.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-cas.js new file mode 100644 index 00000000000..b7827e7a076 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-cas.js @@ -0,0 +1,40 @@ +// Contrast Adaptive Sharpening (CAS) is used to apply the sharpening. It's based on AMD's +// FidelityFX CAS, WebGL implementation: https://www.shadertoy.com/view/wtlSWB. It's best to run it +// on a tone-mapped color buffer after post-processing, but before the UI, and so this is the +// obvious place to put it to avoid a separate render pass, even though we need to handle running it +// before the tone-mapping. +export default /* wgsl */` + #ifdef CAS + uniform sharpness: f32; + + // reversible LDR <-> HDR tone mapping, as CAS needs LDR input + fn maxComponent(x: f32, y: f32, z: f32) -> f32 { return max(x, max(y, z)); } + fn toSDR(c: vec3f) -> vec3f { return c / (1.0 + maxComponent(c.r, c.g, c.b)); } + fn toHDR(c: vec3f) -> vec3f { return c / (1.0 - maxComponent(c.r, c.g, c.b)); } + + fn applyCas(color: vec3f, uv: vec2f, sharpness: f32) -> vec3f { + let x = uniform.sceneTextureInvRes.x; + let y = uniform.sceneTextureInvRes.y; + + // sample 4 neighbors around the already sampled pixel, and convert it to SDR + let a = toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(0.0, -y), 0.0).rgb); + let b = toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(-x, 0.0), 0.0).rgb); + let c = toSDR(color.rgb); + let d = toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(x, 0.0), 0.0).rgb); + let e = toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(0.0, y), 0.0).rgb); + + // apply the sharpening + let min_g = min(a.g, min(b.g, min(c.g, min(d.g, e.g)))); + let max_g = max(a.g, max(b.g, max(c.g, max(d.g, e.g)))); + let sharpening_amount = sqrt(min(1.0 - max_g, min_g) / max_g); + let w = sharpening_amount * uniform.sharpness; + var res = (w * (a + b + d + e) + c) / (4.0 * w + 1.0); + + // remove negative colors + res = max(res, vec3f(0.0)); + + // convert back to HDR + return toHDR(res); + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-dof.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-dof.js new file mode 100644 index 00000000000..a582e912086 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-dof.js @@ -0,0 +1,54 @@ +export default /* wgsl */` + #ifdef DOF + var cocTexture: texture_2d; + var cocTextureSampler: sampler; + var blurTexture: texture_2d; + var blurTextureSampler: sampler; + + // Global variables for debug + var dCoc: vec2f; + var dBlur: vec3f; + + // Samples the DOF blur and CoC textures + fn getDofBlur(uv: vec2f) -> vec3f { + dCoc = textureSampleLevel(cocTexture, cocTextureSampler, uv, 0.0).rg; + + #if DOF_UPSCALE + let blurTexelSize = 1.0 / vec2f(textureDimensions(blurTexture, 0)); + var bilinearBlur = vec3f(0.0); + var totalWeight = 0.0; + + // 3x3 grid of neighboring texels + for (var i = -1; i <= 1; i++) { + for (var j = -1; j <= 1; j++) { + let offset = vec2f(f32(i), f32(j)) * blurTexelSize; + let cocSample = textureSampleLevel(cocTexture, cocTextureSampler, uv + offset, 0.0).rg; + let blurSample = textureSampleLevel(blurTexture, blurTextureSampler, uv + offset, 0.0).rgb; + + // Accumulate the weighted blur sample + let cocWeight = clamp(cocSample.r + cocSample.g, 0.0, 1.0); + bilinearBlur += blurSample * cocWeight; + totalWeight += cocWeight; + } + } + + // normalize the accumulated color + if (totalWeight > 0.0) { + bilinearBlur /= totalWeight; + } + + dBlur = bilinearBlur; + return bilinearBlur; + #else + // when blurTexture is full resolution, just sample it, no upsampling + dBlur = textureSampleLevel(blurTexture, blurTextureSampler, uv, 0.0).rgb; + return dBlur; + #endif + } + + fn applyDof(color: vec3f, uv: vec2f) -> vec3f { + let blur = getDofBlur(uv); + return mix(color, blur, dCoc.r + dCoc.g); + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-fringing.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-fringing.js new file mode 100644 index 00000000000..2d7d1bd05a0 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-fringing.js @@ -0,0 +1,16 @@ +export default /* wgsl */` + #ifdef FRINGING + uniform fringingIntensity: f32; + + fn applyFringing(color: vec3f, uv: vec2f) -> vec3f { + // offset depends on the direction from the center + let centerDistance = uv - 0.5; + let offset = uniform.fringingIntensity * pow(centerDistance, vec2f(2.0)); + + var colorOut = color; + colorOut.r = textureSample(sceneTexture, sceneTextureSampler, uv - offset).r; + colorOut.b = textureSample(sceneTexture, sceneTextureSampler, uv + offset).b; + return colorOut; + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-grading.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-grading.js new file mode 100644 index 00000000000..0cc402a85c0 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-grading.js @@ -0,0 +1,30 @@ +export default /* wgsl */` + #ifdef GRADING + uniform brightnessContrastSaturation: vec3f; + uniform tint: vec3f; + + // for all parameters, 1.0 is the no-change value + fn colorGradingHDR(color: vec3f, brt: f32, sat: f32, con: f32) -> vec3f { + // tint + var colorOut = color * uniform.tint; + + // brightness + colorOut = colorOut * brt; + + // saturation + let grey = dot(colorOut, vec3f(0.3, 0.59, 0.11)); + let normalizedGrey = grey / max(1.0, max(colorOut.r, max(colorOut.g, colorOut.b))); // Normalize luminance in HDR to preserve intensity + colorOut = mix(vec3f(normalizedGrey), colorOut, sat); + + // contrast + return mix(vec3f(0.5), colorOut, con); + } + + fn applyGrading(color: vec3f) -> vec3f { + return colorGradingHDR(color, + uniform.brightnessContrastSaturation.x, + uniform.brightnessContrastSaturation.z, + uniform.brightnessContrastSaturation.y); + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-ssao.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-ssao.js new file mode 100644 index 00000000000..1ed51e0a1ac --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-ssao.js @@ -0,0 +1,27 @@ +export default /* wgsl */` + #ifdef SSAO + #define SSAO_TEXTURE + #endif + + #if DEBUG_COMPOSE == ssao + #define SSAO_TEXTURE + #endif + + #ifdef SSAO_TEXTURE + var ssaoTexture: texture_2d; + var ssaoTextureSampler: sampler; + + // Global variable for debug + var dSsao: f32; + + fn applySsao(color: vec3f, uv: vec2f) -> vec3f { + dSsao = textureSampleLevel(ssaoTexture, ssaoTextureSampler, uv, 0.0).r; + + #ifdef SSAO + return color * dSsao; + #else + return color; + #endif + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-vignette.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-vignette.js new file mode 100644 index 00000000000..4b04b47fabe --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose-vignette.js @@ -0,0 +1,29 @@ +export default /* wgsl */` + #ifdef VIGNETTE + uniform vignetterParams: vec4f; + + // Global variable for debug + var dVignette: f32; + + fn calcVignette(uv: vec2f) -> f32 { + let inner = uniform.vignetterParams.x; + let outer = uniform.vignetterParams.y; + let curvature = uniform.vignetterParams.z; + let intensity = uniform.vignetterParams.w; + + // edge curvature + let curve = pow(abs(uv * 2.0 - 1.0), vec2f(1.0 / curvature)); + + // distance to edge + let edge = pow(length(curve), curvature); + + // gradient and intensity + dVignette = 1.0 - intensity * smoothstep(inner, outer, edge); + return dVignette; + } + + fn applyVignette(color: vec3f, uv: vec2f) -> vec3f { + return color * calcVignette(uv); + } + #endif +`; diff --git a/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose.js b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose.js new file mode 100644 index 00000000000..a0ccc11c9b2 --- /dev/null +++ b/src/scene/shader-lib/wgsl/chunks/render-pass/frag/compose/compose.js @@ -0,0 +1,92 @@ +export default /* wgsl */` + #include "tonemappingPS" + #include "gammaPS" + + varying uv0: vec2f; + var sceneTexture: texture_2d; + var sceneTextureSampler: sampler; + uniform sceneTextureInvRes: vec2f; + + #include "composeBloomPS" + #include "composeDofPS" + #include "composeSsaoPS" + #include "composeGradingPS" + #include "composeVignettePS" + #include "composeFringingPS" + #include "composeCasPS" + + @fragment + fn fragmentMain(input: FragmentInput) -> FragmentOutput { + var output: FragmentOutput; + var uv = uv0; + + // TAA pass renders upside-down on WebGPU, flip it here + #ifdef TAA + uv.y = 1.0 - uv.y; + #endif + + let scene = textureSampleLevel(sceneTexture, sceneTextureSampler, uv, 0.0); + var result = scene.rgb; + + // Apply CAS + #ifdef CAS + result = applyCas(result, uv, uniform.sharpness); + #endif + + // Apply DOF + #ifdef DOF + result = applyDof(result, uv0); + #endif + + // Apply SSAO + #ifdef SSAO_TEXTURE + result = applySsao(result, uv0); + #endif + + // Apply Fringing + #ifdef FRINGING + result = applyFringing(result, uv); + #endif + + // Apply Bloom + #ifdef BLOOM + result = applyBloom(result, uv0); + #endif + + // Apply Color Grading + #ifdef GRADING + result = applyGrading(result); + #endif + + // Apply Tone Mapping + result = toneMap(result); + + // Apply Vignette + #ifdef VIGNETTE + result = applyVignette(result, uv); + #endif + + // Debug output handling in one centralized location + #ifdef DEBUG_COMPOSE + #if DEBUG_COMPOSE == scene + result = scene.rgb; + #elif defined(BLOOM) && DEBUG_COMPOSE == bloom + result = dBloom * uniform.bloomIntensity; + #elif defined(DOF) && DEBUG_COMPOSE == dofcoc + result = vec3f(dCoc, 0.0); + #elif defined(DOF) && DEBUG_COMPOSE == dofblur + result = dBlur; + #elif defined(SSAO_TEXTURE) && DEBUG_COMPOSE == ssao + result = vec3f(dSsao); + #elif defined(VIGNETTE) && DEBUG_COMPOSE == vignette + result = vec3f(dVignette); + #endif + #endif + + // Apply gamma correction + result = gammaCorrectOutput(result); + + output.color = vec4f(result, scene.a); + return output; + } +`; diff --git a/src/scene/shader-lib/wgsl/collections/compose-chunks-wgsl.js b/src/scene/shader-lib/wgsl/collections/compose-chunks-wgsl.js new file mode 100644 index 00000000000..28fcd531dac --- /dev/null +++ b/src/scene/shader-lib/wgsl/collections/compose-chunks-wgsl.js @@ -0,0 +1,19 @@ +import composePS from '../chunks/render-pass/frag/compose/compose.js'; +import composeBloomPS from '../chunks/render-pass/frag/compose/compose-bloom.js'; +import composeDofPS from '../chunks/render-pass/frag/compose/compose-dof.js'; +import composeSsaoPS from '../chunks/render-pass/frag/compose/compose-ssao.js'; +import composeGradingPS from '../chunks/render-pass/frag/compose/compose-grading.js'; +import composeVignettePS from '../chunks/render-pass/frag/compose/compose-vignette.js'; +import composeFringingPS from '../chunks/render-pass/frag/compose/compose-fringing.js'; +import composeCasPS from '../chunks/render-pass/frag/compose/compose-cas.js'; + +export const composeChunksWGSL = { + composePS, + composeBloomPS, + composeDofPS, + composeSsaoPS, + composeGradingPS, + composeVignettePS, + composeFringingPS, + composeCasPS +}; diff --git a/treemap.html b/treemap.html new file mode 100644 index 00000000000..8b3be80dcac --- /dev/null +++ b/treemap.html @@ -0,0 +1,4949 @@ + + + + + + + + Rollup Visualizer + + + +
+ + + + +