From 42e2659636f0495f6c0e97b75f37f2212f0d861e Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 24 Oct 2024 15:39:43 +0200 Subject: [PATCH] filter: Add Khronos PBR Neutral tonemapping operator Closes #1661 Fixes #1659 Co-authored-by: Raytopia <54505044+raytopianprojects@users.noreply.github.com> --- direct/src/filter/CommonFilters.py | 68 ++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/direct/src/filter/CommonFilters.py b/direct/src/filter/CommonFilters.py index 8c4857f1f9..06c2875796 100644 --- a/direct/src/filter/CommonFilters.py +++ b/direct/src/filter/CommonFilters.py @@ -99,9 +99,15 @@ void fshader(out float4 o_color : COLOR, """ +class ToneMap: + ACES = object() + PBR_NEUTRAL = object() + + class FilterConfig: pass + class CommonFilters: """ Class CommonFilters implements certain common image postprocessing @@ -279,16 +285,19 @@ class CommonFilters: text = "//Cg\n" if "HighDynamicRange" in configuration: - text += "static const float3x3 aces_input_mat = {\n" - text += " {0.59719, 0.35458, 0.04823},\n" - text += " {0.07600, 0.90834, 0.01566},\n" - text += " {0.02840, 0.13383, 0.83777},\n" - text += "};\n" - text += "static const float3x3 aces_output_mat = {\n" - text += " { 1.60475, -0.53108, -0.07367},\n" - text += " {-0.10208, 1.10813, -0.00605},\n" - text += " {-0.00327, -0.07276, 1.07602},\n" - text += "};\n" + tonemap = configuration["HighDynamicRange"] + if tonemap is ToneMap.ACES: + text += "static const float3x3 aces_input_mat = {\n" + text += " {0.59719, 0.35458, 0.04823},\n" + text += " {0.07600, 0.90834, 0.01566},\n" + text += " {0.02840, 0.13383, 0.83777},\n" + text += "};\n" + text += "static const float3x3 aces_output_mat = {\n" + text += " { 1.60475, -0.53108, -0.07367},\n" + text += " {-0.10208, 1.10813, -0.00605},\n" + text += " {-0.00327, -0.07276, 1.07602},\n" + text += "};\n" + text += "void vshader(float4 vtx_position : POSITION,\n" text += " out float4 l_position : POSITION,\n" @@ -381,10 +390,30 @@ class CommonFilters: if ("ExposureAdjust" in configuration): text += " o_color.rgb *= k_exposure;\n" - # With thanks to Stephen Hill! - if ("HighDynamicRange" in configuration): - text += " float3 aces_color = mul(aces_input_mat, o_color.rgb);\n" - text += " o_color.rgb = saturate(mul(aces_output_mat, (aces_color * (aces_color + 0.0245786f) - 0.000090537f) / (aces_color * (0.983729f * aces_color + 0.4329510f) + 0.238081f)));\n" + if "HighDynamicRange" in configuration: + tonemap = configuration["HighDynamicRange"] + if tonemap is ToneMap.ACES: + # With thanks to Stephen Hill! + text += " float3 aces_color = mul(aces_input_mat, o_color.rgb);\n" + text += " o_color.rgb = saturate(mul(aces_output_mat, (aces_color * (aces_color + 0.0245786f) - 0.000090537f) / (aces_color * (0.983729f * aces_color + 0.4329510f) + 0.238081f)));\n" + elif tonemap is ToneMap.PBR_NEUTRAL: + text += " const float start_compression = 0.8 - 0.04;\n" + text += " const float desaturation = 0.15;\n" + + text += " float x = min(o_color.r, min(o_color.g, o_color.b));\n" + text += " float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n" + text += " o_color.rgb -= offset;\n" + + text += " float peak = max(o_color.r, max(o_color.g, o_color.b));\n" + + text += " if (peak >= start_compression) {\n" + text += " const float d = 1.0 - start_compression;\n" + text += " float new_peak = 1.0 - d * d / (peak + d - start_compression);\n" + text += " o_color.rgb *= new_peak / peak;\n" + text += " float g = 1.0 - 1.0 / (desaturation * (peak - new_peak) + 1.0);\n" + + text += " o_color.rgb = lerp(o_color.rgb, new_peak * float3(1, 1, 1), g);\n" + text += "}\n" if ("GammaAdjust" in configuration): gamma = configuration["GammaAdjust"] @@ -662,10 +691,10 @@ class CommonFilters: return self.reconfigure(old_enable, "SrgbEncode") return True - def setHighDynamicRange(self): + def setHighDynamicRange(self, tonemap=ToneMap.ACES): """ Enables HDR rendering by using a floating-point framebuffer, disabling color clamping on the main scene, and applying a tone map - operator (ACES). + operator (ACES or Khronos PBR Neutral). It may also be necessary to use setExposureAdjust to perform exposure compensation on the scene, depending on the lighting intensity. @@ -673,8 +702,11 @@ class CommonFilters: .. versionadded:: 1.10.7 """ - fullrebuild = (("HighDynamicRange" in self.configuration) is False) - self.configuration["HighDynamicRange"] = 1 + fullrebuild = "HighDynamicRange" not in self.configuration or \ + self.configuration["HighDynamicRange"] is not tonemap + if tonemap is not ToneMap.ACES and tonemap is not ToneMap.PBR_NEUTRAL: + raise ValueError("Invalid value for tonemap") + self.configuration["HighDynamicRange"] = tonemap return self.reconfigure(fullrebuild, "HighDynamicRange") def delHighDynamicRange(self):