diff --git a/package/Shaders/Common/DICETonemapper.hlsli b/package/Shaders/Common/DICETonemapper.hlsli index d820a8e69..7614506eb 100644 --- a/package/Shaders/Common/DICETonemapper.hlsli +++ b/package/Shaders/Common/DICETonemapper.hlsli @@ -156,3 +156,38 @@ float3 HuePreservingHejlBurgessDawson(float3 col) return col; } + +float3 HuePreservingReinhard(float3 col) +{ + float3 ictcp = RGBToICtCp(col); + + // Hue-preserving range compression requires desaturation in order to achieve a natural look. We adaptively desaturate the input based on its luminance. + float saturationAmount = pow(smoothstep(1.0, 0.3, ictcp.x), 1.3); + col = ICtCpToRGB(ictcp * float3(1, saturationAmount.xx)); + + // Hue preserving mapping + float maxCol = Color::RGBToLuminance(col); + float mappedMax = GetTonemapFactorReinhard(maxCol); + float3 compressedHuePreserving = col * mappedMax / maxCol; + + // Non-hue preserving mapping + float3 perChannelCompressed = GetTonemapFactorReinhard(col); + // Compensate for modified tonemapping desaturating colors + perChannelCompressed = lerp(Color::RGBToLuminance(perChannelCompressed), perChannelCompressed, 1.5); + + col = lerp(perChannelCompressed, compressedHuePreserving, 0.6); + + float3 ictcpMapped = RGBToICtCp(col); + + // Smoothly ramp off saturation as brightness increases, but keep some even for very bright input + float postCompressionSaturationBoost = 0.3 * smoothstep(1.0, 0.5, ictcp.x); + + // Re-introduce some hue from the pre-compression color. Something similar could be accomplished by delaying the luma-dependent desaturation before range compression. + // Doing it here however does a better job of preserving perceptual luminance of highly saturated colors. Because in the hue-preserving path we only range-compress the max channel, + // saturated colors lose luminance. By desaturating them more aggressively first, compressing, and then re-adding some saturation, we can preserve their brightness to a greater extent. + ictcpMapped.yz = lerp(ictcpMapped.yz, ictcp.yz * ictcpMapped.x / max(1e-3, ictcp.x), postCompressionSaturationBoost); + + col = ICtCpToRGB(ictcpMapped); + + return col; +} diff --git a/package/Shaders/ISHDR.hlsl b/package/Shaders/ISHDR.hlsl index 0a81d9475..bce58b2f6 100644 --- a/package/Shaders/ISHDR.hlsl +++ b/package/Shaders/ISHDR.hlsl @@ -111,7 +111,7 @@ PS_OUTPUT main(PS_INPUT input) if (Param.z > 0.5) { blendedColor = HuePreservingHejlBurgessDawson(inputColor); } else { - blendedColor = GetTonemapFactorReinhard(inputColor); + blendedColor = HuePreservingReinhard(inputColor); } blendedColor += saturate(Param.x - blendedColor) * bloomColor;