From 8101663cad5a5a9fc09d4f89e6ebd1632c82b87e Mon Sep 17 00:00:00 2001 From: Musa Haji <131800246+mqhaji@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:16:00 -0500 Subject: [PATCH] Gamma Correction for Resident Evil 4 (#81) * feat(re4remake): add gamma correction, make gamma adjust by channel - add sRGB -> 2.2 gamma correction - make bt709Color a float3 as w is unused - increase DICE ShoulderStart - compensate for black crush with gamma correction in LUT scaling - make gamma adjust work by channel instead of by luminance * feat(re4remake): add gamma correction, make gamma adjust by channel - add sRGB -> 2.2 gamma correction - make bt709Color a float3 as w is unused - increase DICE ShoulderStart - compensate for black crush with gamma correction in LUT scaling - make gamma adjust work by channel instead of by luminance * fix(re4remake): fix gamma correction tooltip --- .../ConvertRec2020PS_0x6737588D.ps_6_6.hlsl | 7 ++-- src/games/re4remake/LUTBlackCorrection.hlsl | 24 ++++++++++++- src/games/re4remake/addon.cpp | 36 ++++++++++++------- src/games/re4remake/shared.h | 3 +- .../tonemap_inside_0x1F9104F3.ps_6_6.hlsl | 2 +- ...Process_WithTonemap_0x973A39FC.ps_6_6.hlsl | 2 +- 6 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/games/re4remake/ConvertRec2020PS_0x6737588D.ps_6_6.hlsl b/src/games/re4remake/ConvertRec2020PS_0x6737588D.ps_6_6.hlsl index e3c9a5d5..0ff09e66 100644 --- a/src/games/re4remake/ConvertRec2020PS_0x6737588D.ps_6_6.hlsl +++ b/src/games/re4remake/ConvertRec2020PS_0x6737588D.ps_6_6.hlsl @@ -39,12 +39,15 @@ float4 main(noperspective float4 SV_Position: SV_Position, linear float2 TEXCOORD: TEXCOORD) : SV_Target { if (injectedData.toneMapType != 0) { - float4 bt709Color = tLinearImage.SampleLevel(PointBorder, TEXCOORD.xy, 0.0f); + float3 bt709Color = tLinearImage.SampleLevel(PointBorder, TEXCOORD.xy, 0.0f).rgb; + if (injectedData.toneMapGammaCorrection == 1.f) { + bt709Color = renodx::color::correct::GammaSafe(bt709Color); + } #if 1 DICESettings config = DefaultDICESettings(); config.Type = 3; - config.ShoulderStart = 0.35; + config.ShoulderStart = 0.45; const float dicePaperWhite = whitePaperNits / renodx::color::srgb::REFERENCE_WHITE; const float dicePeakWhite = max(displayMaxNits, whitePaperNits) / renodx::color::srgb::REFERENCE_WHITE; bt709Color.rgb = DICETonemap(bt709Color.rgb * dicePaperWhite, dicePeakWhite, config) / dicePaperWhite; diff --git a/src/games/re4remake/LUTBlackCorrection.hlsl b/src/games/re4remake/LUTBlackCorrection.hlsl index a33f2ae2..4008637c 100644 --- a/src/games/re4remake/LUTBlackCorrection.hlsl +++ b/src/games/re4remake/LUTBlackCorrection.hlsl @@ -20,6 +20,22 @@ float3 AdjustGammaOnLuminance(float3 linearColor, float gammaAdjustmentFactor) { return linearColor * (adjustedLuminance / originalLuminance); } +/// Adjusts gamma for each color channel, applying the adjustment only for values less than 1. +/// This function ensures that the color's original sign is preserved and that gamma correction is only applied to values below 1. +/// At `gammaAdjustmentFactor` = 1.15, it enhances detail in the midtones and shadows without affecting peak whites or clipping any values. +/// See: https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2408-7-2023-PDF-E.pdf Section 5.1.3.2 +/// @param linearColor The RGB color to adjust. +/// @param gammaAdjustmentFactor Factor to adjust the gamma for values below 1. +/// @return The RGB color with adjusted gamma for components below 1, maintaining the original sign and preserving values of 1 and above. +float3 AdjustGammaByChannel(float3 linearColor, float gammaAdjustmentFactor) { + if (gammaAdjustmentFactor == 1.f) return linearColor; // No adjustment if factor is 1 + + // Create a mask to identify components less than 1 + float3 mask = step(linearColor, float3(1.0f, 1.0f, 1.0f)); // 1 where linearColor < 1, 0 otherwise + // Adjust gamma only for components where the value is less than 1 + return mask * renodx::color::gamma::EncodeSafe(linearColor, 1.f / gammaAdjustmentFactor) + (1.f - mask) * linearColor; +} + /// Applies a modified `renodx::lut::Sample` that accounts for only black level correction, /// leaving peak white untouched as LUTs are already HDR, ensuring no highlight impact. /// @param color_input Input color to apply the LUT to. @@ -30,6 +46,9 @@ float3 LUTBlackCorrection(float3 color_input, Texture3D lut_texture, renodx::lut float3 lutInputColor = renodx::lut::ConvertInput(color_input, lut_config); float3 lutOutputColor = renodx::lut::SampleColor(lutInputColor, lut_config, lut_texture); float3 color_output = renodx::lut::LinearOutput(lutOutputColor, lut_config); + + float3 original_output = color_output; + if (lut_config.scaling != 0) { float3 lutBlack = renodx::lut::SampleColor(renodx::lut::ConvertInput(0, lut_config), lut_config, lut_texture); float3 lutMid = renodx::lut::SampleColor(renodx::lut::ConvertInput(0.18f, lut_config), lut_config, lut_texture); @@ -41,6 +60,9 @@ float3 LUTBlackCorrection(float3 color_input, Texture3D lut_texture, renodx::lut renodx::lut::GammaInput(color_input, lutInputColor, lut_config)); float3 recolored = renodx::lut::RecolorUnclamped(color_output, renodx::lut::LinearUnclampedOutput(unclamped, lut_config)); color_output = lerp(color_output, recolored, lut_config.scaling); + if (injectedData.toneMapGammaCorrection == 1.f) { // fixes crushed blacks with 2.2 gamma correction + color_output = lerp(color_output, original_output, saturate(pow(color_output, lutMid))); + } } color_output = renodx::lut::RestoreSaturationLoss(color_input, color_output, lut_config); if (lut_config.strength != 1.f) { @@ -78,4 +100,4 @@ float3 renoDRTSmoothClamp(float3 untonemapped) { renoDRTColor = lerp(untonemapped, renoDRTColor, saturate(renodx::color::y::from::BT709(untonemapped) / renodrt_config.mid_gray_value)); return min(1, renoDRTColor); -} \ No newline at end of file +} diff --git a/src/games/re4remake/addon.cpp b/src/games/re4remake/addon.cpp index 96e7e75c..ab96b150 100644 --- a/src/games/re4remake/addon.cpp +++ b/src/games/re4remake/addon.cpp @@ -47,6 +47,29 @@ renodx::utils::settings::Settings settings = { .tooltip = "Sets the tone mapper type", .labels = {"Vanilla", "Vanilla+"}, }, + new renodx::utils::settings::Setting{ + .key = "toneMapGammaCorrection", + .binding = &shader_injection.toneMapGammaCorrection, + .value_type = renodx::utils::settings::SettingValueType::BOOLEAN, + .default_value = 1.f, + .can_reset = false, + .label = "Gamma Correction", + .section = "Tone Mapping", + .tooltip = "Emulates a 2.2 EOTF, this is likely what the developers tried to approximate with the vanilla gamma adjustment", + .is_enabled = []() { return shader_injection.toneMapType != 0; }, + }, + new renodx::utils::settings::Setting{ + .key = "toneMapGammaAdjust", + .binding = &shader_injection.toneMapGammaAdjust, + .default_value = 1.f, + .label = "Gamma Adjustment", + .section = "Tone Mapping", + .tooltip = "Adjusts gamma", + .min = 0.75f, + .max = 1.25f, + .format = "%.2f", + .is_enabled = []() { return shader_injection.toneMapType != 0; }, + }, new renodx::utils::settings::Setting{ .key = "colorGradeHighlightContrast", .binding = &shader_injection.colorGradeHighlightContrast, @@ -98,18 +121,6 @@ renodx::utils::settings::Settings settings = { .max = 100.f, .parse = [](float value) { return value * 0.01f; }, }, - new renodx::utils::settings::Setting{ - .key = "colorGradeGammaAdjust", - .binding = &shader_injection.colorGradeGammaAdjust, - .default_value = 1.f, - .label = "Gamma Adjustment (Hue Preserving)", - .section = "Color Grading", - .tooltip = "Adjusts gamma on luminance, only affects luminance values below 1.", - .min = 0.75f, - .max = 1.25f, - .format = "%.2f", - .is_enabled = []() { return shader_injection.toneMapType != 0; }, - }, new renodx::utils::settings::Setting{ .key = "processingInternalSampling", .binding = &shader_injection.processingInternalSampling, @@ -150,6 +161,7 @@ renodx::utils::settings::Settings settings = { void OnPresetOff() { renodx::utils::settings::UpdateSetting("toneMapType", 0.f); + renodx::utils::settings::UpdateSetting("toneMapGammaCorrection", 0.f); renodx::utils::settings::UpdateSetting("colorGradeToeAdjustmentType", 0.f); renodx::utils::settings::UpdateSetting("colorGradeShadowToe", 1.f); renodx::utils::settings::UpdateSetting("colorGradeHighlightContrast", 50.f); diff --git a/src/games/re4remake/shared.h b/src/games/re4remake/shared.h index 162e7a54..d50f83df 100644 --- a/src/games/re4remake/shared.h +++ b/src/games/re4remake/shared.h @@ -9,12 +9,13 @@ // Should be 4x32 struct ShaderInjectData { float toneMapType; + float toneMapGammaCorrection; + float toneMapGammaAdjust; float colorGradeHighlightContrast; float colorGradeToeAdjustmentType; float colorGradeShadowToe; float colorGradeLUTStrength; float colorGradeLUTScaling; - float colorGradeGammaAdjust; float processingInternalSampling; }; diff --git a/src/games/re4remake/tonemap_inside_0x1F9104F3.ps_6_6.hlsl b/src/games/re4remake/tonemap_inside_0x1F9104F3.ps_6_6.hlsl index a9253fa1..40572d41 100644 --- a/src/games/re4remake/tonemap_inside_0x1F9104F3.ps_6_6.hlsl +++ b/src/games/re4remake/tonemap_inside_0x1F9104F3.ps_6_6.hlsl @@ -1416,7 +1416,7 @@ void frag_main() SV_Target.w = 0.0f; if (injectedData.toneMapType != 0) { - SV_Target.rgb = AdjustGammaOnLuminance(SV_Target.rgb, injectedData.colorGradeGammaAdjust); + SV_Target.rgb = AdjustGammaByChannel(SV_Target.rgb, injectedData.toneMapGammaAdjust); } } diff --git a/src/games/re4remake/tonemap_outside_HDRPostProcess_WithTonemap_0x973A39FC.ps_6_6.hlsl b/src/games/re4remake/tonemap_outside_HDRPostProcess_WithTonemap_0x973A39FC.ps_6_6.hlsl index d11f0f3c..13014467 100644 --- a/src/games/re4remake/tonemap_outside_HDRPostProcess_WithTonemap_0x973A39FC.ps_6_6.hlsl +++ b/src/games/re4remake/tonemap_outside_HDRPostProcess_WithTonemap_0x973A39FC.ps_6_6.hlsl @@ -1447,7 +1447,7 @@ void frag_main() SV_Target.w = 0.0f; if (injectedData.toneMapType != 0) { - SV_Target.rgb = AdjustGammaOnLuminance(SV_Target.rgb, injectedData.colorGradeGammaAdjust); + SV_Target.rgb = AdjustGammaByChannel(SV_Target.rgb, injectedData.toneMapGammaAdjust); } }