Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/extras/render-passes/render-pass-camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ class RenderPassCameraFrame extends RenderPass {
// create a compose pass, which combines the results of the scene and other passes
this.composePass = new RenderPassCompose(this.device);
this.composePass.bloomTexture = this.bloomPass?.bloomTexture;
this.composePass.hdrScene = this.hdrFormat !== PIXELFORMAT_RGBA8;
this.composePass.taaEnabled = options.taaEnabled;
this.composePass.cocTexture = this.dofPass?.cocTexture;
this.composePass.blurTexture = this.dofPass?.blurTexture;
Expand Down
20 changes: 18 additions & 2 deletions src/extras/render-passes/render-pass-compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class RenderPassCompose extends RenderPassShaderQuad {

_taaEnabled = false;

_hdrScene = true;

_sharpness = 0.5;

_gammaCorrection = GAMMA_SRGB;
Expand Down Expand Up @@ -268,6 +270,17 @@ class RenderPassCompose extends RenderPassShaderQuad {
return this._sharpness > 0;
}

set hdrScene(value) {
if (this._hdrScene !== value) {
this._hdrScene = value;
this._shaderDirty = true;
}
}

get hdrScene() {
return this._hdrScene;
}

postInit() {
// clear all buffers to avoid them being loaded from memory
this.setClearColor(Color.BLACK);
Expand Down Expand Up @@ -322,7 +335,7 @@ class RenderPassCompose extends RenderPassShaderQuad {
`-${this.vignetteEnabled ? 'vignette' : 'novignette'}` +
`-${this.fringingEnabled ? 'fringing' : 'nofringing'}` +
`-${this.taaEnabled ? 'taa' : 'notaa'}` +
`-${this.isSharpnessEnabled ? 'cas' : 'nocas'}` +
`-${this.isSharpnessEnabled ? (this._hdrScene ? 'cashdr' : 'cas') : 'nocas'}` +
`-${this._debug ?? ''}` +
`-decl${declHash}-start${startHash}-end${endHash}`;

Expand All @@ -342,7 +355,10 @@ class RenderPassCompose extends RenderPassShaderQuad {
if (this.vignetteEnabled) defines.set('VIGNETTE', true);
if (this.fringingEnabled) defines.set('FRINGING', true);
if (this.taaEnabled) defines.set('TAA', true);
if (this.isSharpnessEnabled) defines.set('CAS', true);
if (this.isSharpnessEnabled) {
defines.set('CAS', true);
if (this._hdrScene) defines.set('CAS_HDR', true);
}
if (this._debug) defines.set('DEBUG_COMPOSE', this._debug);

const includes = new Map(shaderChunks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ export default /* glsl */`
uniform float sharpness;

// reversible LDR <-> HDR tone mapping, as CAS needs LDR input
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment // reversible LDR <-> HDR tone mapping, as CAS needs LDR input (line 10 in the GLSL file and same in WGSL) now precedes a #ifdef CAS_HDR / #else block. In the #else (LDR) branch, no tone mapping is performed at all — toSDR and toHDR are identity functions. The comment is therefore misleading in the LDR case. It should be updated to clarify that the tone mapping is only applied for HDR scenes, for example: // reversible LDR <-> HDR tone mapping (HDR only), as CAS needs LDR input.

Suggested change
// reversible LDR <-> HDR tone mapping, as CAS needs LDR input
// reversible LDR <-> HDR tone mapping (HDR only), as CAS needs LDR input

Copilot uses AI. Check for mistakes.
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)); }
#ifdef CAS_HDR
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)); }
#else
vec3 toSDR(vec3 c) { return c; }
vec3 toHDR(vec3 c) { return c; }
#endif

vec3 applyCas(vec3 color, vec2 uv, float sharpness) {
float x = sceneTextureInvRes.x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,38 @@ export default /* wgsl */`
uniform sharpness: f32;

// reversible LDR <-> HDR tone mapping, as CAS needs LDR input
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment // reversible LDR <-> HDR tone mapping, as CAS needs LDR input (line 10) now precedes a #ifdef CAS_HDR / #else block. In the #else (LDR) branch, no tone mapping is performed at all — toSDR and toHDR are identity functions. The comment is therefore misleading in the LDR case. It should be updated to clarify that the tone mapping is only applied for HDR scenes, for example: // reversible LDR <-> HDR tone mapping (HDR only), as CAS needs LDR input.

Suggested change
// reversible LDR <-> HDR tone mapping, as CAS needs LDR input
// reversible LDR <-> HDR tone mapping (HDR only), as CAS needs LDR input

Copilot uses AI. Check for mistakes.
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)); }
#ifdef CAS_HDR
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)); }
#else
fn toSDR(c: vec3f) -> vec3f { return c; }
fn toHDR(c: vec3f) -> vec3f { return c; }
#endif

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);
let a: half3 = half3(toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(0.0, -y), 0.0).rgb));
let b: half3 = half3(toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(-x, 0.0), 0.0).rgb));
let c: half3 = half3(toSDR(color.rgb));
let d: half3 = half3(toSDR(textureSampleLevel(sceneTexture, sceneTextureSampler, uv + vec2f(x, 0.0), 0.0).rgb));
let e: half3 = half3(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);
let sharpening_amount = sqrt(min(half(1.0) - max_g, min_g) / max_g);
let w = sharpening_amount * half(uniform.sharpness);
var res = (w * (a + b + d + e) + c) / (half(4.0) * w + half(1.0));

// remove negative colors
res = max(res, vec3f(0.0));
res = max(res, half3(0.0));

// convert back to HDR
return toHDR(res);
return toHDR(vec3f(res));
}
#endif
`;