From 8d5f01e58cc3c55681cc077216628c625fb333ba Mon Sep 17 00:00:00 2001
From: Michael Migliore <mcmigliore@gmail.com>
Date: Wed, 5 Jun 2024 17:21:48 +0200
Subject: [PATCH] PBR Neutral tone mapper

---
 .../release/dev/pbr-neutral-tone-mapping.md   |  4 ++++
 .../Testing/Cxx/TestToneMappingPass.cxx       | 22 ++++++++++---------
 .../Baseline/TestToneMappingPass.png.sha512   |  2 +-
 Rendering/OpenGL2/vtkToneMappingPass.cxx      | 19 ++++++++++++++++
 Rendering/OpenGL2/vtkToneMappingPass.h        |  5 +++--
 5 files changed, 39 insertions(+), 13 deletions(-)
 create mode 100644 Documentation/release/dev/pbr-neutral-tone-mapping.md

diff --git a/Documentation/release/dev/pbr-neutral-tone-mapping.md b/Documentation/release/dev/pbr-neutral-tone-mapping.md
new file mode 100644
index 00000000000..c0e85f5a760
--- /dev/null
+++ b/Documentation/release/dev/pbr-neutral-tone-mapping.md
@@ -0,0 +1,4 @@
+## PBR Neutral tone mapping
+
+A new alternative tone mapping method has been added to `vtkToneMappingPass`.
+It's based on the reference implementation of Khronos PBR Neutral.
diff --git a/Rendering/OpenGL2/Testing/Cxx/TestToneMappingPass.cxx b/Rendering/OpenGL2/Testing/Cxx/TestToneMappingPass.cxx
index 164cc2d6a0e..04a375d1fee 100644
--- a/Rendering/OpenGL2/Testing/Cxx/TestToneMappingPass.cxx
+++ b/Rendering/OpenGL2/Testing/Cxx/TestToneMappingPass.cxx
@@ -14,6 +14,7 @@
 #include "vtkOpaquePass.h"
 #include "vtkOpenGLRenderer.h"
 #include "vtkPolyDataMapper.h"
+#include "vtkProperty.h"
 #include "vtkRenderPassCollection.h"
 #include "vtkRenderWindow.h"
 #include "vtkRenderWindowInteractor.h"
@@ -24,7 +25,7 @@
 int TestToneMappingPass(int argc, char* argv[])
 {
   vtkNew<vtkRenderWindow> renWin;
-  renWin->SetSize(400, 800);
+  renWin->SetSize(900, 900);
 
   vtkNew<vtkRenderWindowInteractor> iren;
   iren->SetRenderWindow(renWin);
@@ -33,8 +34,7 @@ int TestToneMappingPass(int argc, char* argv[])
   sphere->SetThetaResolution(20);
   sphere->SetPhiResolution(20);
 
-  double y = 0.0;
-  for (int i = 0; i < 8; i++)
+  for (int i = 0; i < 9; i++)
   {
     vtkNew<vtkRenderer> renderer;
 
@@ -84,19 +84,20 @@ int TestToneMappingPass(int argc, char* argv[])
         toneMappingP->SetGenericFilmicUncharted2Presets();
         toneMappingP->SetUseACES(false);
         break;
+      case 8:
+        toneMappingP->SetToneMappingType(vtkToneMappingPass::NeutralPBR);
+        break;
     }
     toneMappingP->SetDelegatePass(cameraP);
 
     vtkOpenGLRenderer::SafeDownCast(renderer)->SetPass(toneMappingP);
 
-    double x = 0.5 * (i & 1);
-    if (i)
-    {
-      y += 1 / 4.f * !(i & 1);
-    }
+    double oneThird = 1.0 / 3.0;
+
+    double x = (i % 3) * oneThird;
+    double y = (i / 3) * oneThird;
 
-    renderer->SetViewport(x, y, x + 0.5, y + 1 / 4.f);
-    renderer->SetBackground(0.5, 0.5, 0.5);
+    renderer->SetViewport(x, y, x + oneThird, y + oneThird);
     renWin->AddRenderer(renderer);
 
     // add one light in front of the object
@@ -141,6 +142,7 @@ int TestToneMappingPass(int argc, char* argv[])
 
     vtkNew<vtkActor> actor;
     actor->SetMapper(mapper);
+    actor->GetProperty()->SetInterpolationToPBR();
     renderer->AddActor(actor);
 
     renderer->ResetCamera();
diff --git a/Rendering/OpenGL2/Testing/Data/Baseline/TestToneMappingPass.png.sha512 b/Rendering/OpenGL2/Testing/Data/Baseline/TestToneMappingPass.png.sha512
index 741f700d08e..ab82260e07c 100644
--- a/Rendering/OpenGL2/Testing/Data/Baseline/TestToneMappingPass.png.sha512
+++ b/Rendering/OpenGL2/Testing/Data/Baseline/TestToneMappingPass.png.sha512
@@ -1 +1 @@
-4595f98960d268ccf75b17b50761f5559aab38e975f0c74fb61687ea1ca0ef8d1260661bb317d074dc8824f3d4e8aee0378d3dd538c038bcf06a59bdde042ad3
+af28d7481039a61d908c16db917bf1655c3b8931b2ab771b7f6dcd39825efc6c3ee5072b52301a4d6c1795597f1305eccee1556afff45e71b681eca50ce40418
diff --git a/Rendering/OpenGL2/vtkToneMappingPass.cxx b/Rendering/OpenGL2/vtkToneMappingPass.cxx
index 9aa77c9adb7..d4f6c127abc 100644
--- a/Rendering/OpenGL2/vtkToneMappingPass.cxx
+++ b/Rendering/OpenGL2/vtkToneMappingPass.cxx
@@ -188,6 +188,25 @@ void vtkToneMappingPass::Render(const vtkRenderState* s)
           "  toned = clamp(toned, vec3(0.f), vec3(1.f));\n"
           "//VTK::FSQ::Impl");
         break;
+      case NeutralPBR:
+        // adapted from Khronos reference implementation:
+        // https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl
+        vtkShaderProgram::Substitute(FSSource, "//VTK::FSQ::Impl",
+          "  const float startCompression = 0.8 - 0.04;\n"
+          "  const float desaturation = 0.15;\n"
+          "  float x = min(color.r, min(color.g, color.b));\n"
+          "  float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n"
+          "  vec3 toned = color - vec3(offset);\n"
+          "  float peak = max(toned.r, max(toned.g, toned.b));\n"
+          "  if (peak >= startCompression)\n"
+          "  {\n"
+          "    const float d = 1. - startCompression;\n"
+          "    float newPeak = 1. - d * d / (peak + d - startCompression);\n"
+          "    toned *= newPeak / peak;\n"
+          "    float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);\n"
+          "    toned = mix(toned, newPeak * vec3(1, 1, 1), g);\n"
+          "  }\n"
+          "//VTK::FSQ::Impl");
     }
 
     // Recorrect gamma and output
diff --git a/Rendering/OpenGL2/vtkToneMappingPass.h b/Rendering/OpenGL2/vtkToneMappingPass.h
index c0f1a5dcf37..67139b259ee 100644
--- a/Rendering/OpenGL2/vtkToneMappingPass.h
+++ b/Rendering/OpenGL2/vtkToneMappingPass.h
@@ -68,7 +68,8 @@ public:
     Clamp = 0,
     Reinhard = 1,
     Exponential = 2,
-    GenericFilmic = 3
+    GenericFilmic = 3,
+    NeutralPBR = 4
   };
 
   ///@{
@@ -76,7 +77,7 @@ public:
    * Get/Set the tone mapping type.
    * Default is GenericFilmic
    */
-  vtkSetClampMacro(ToneMappingType, int, 0, 3);
+  vtkSetClampMacro(ToneMappingType, int, 0, 4);
   vtkGetMacro(ToneMappingType, int);
   ///@}
 
-- 
GitLab