@@ -68,11 +68,11 @@ ImageViewer::ImageViewer(
6868 auto ditherMatrix = nanogui::ditherMatrix (ditherScale);
6969 mDitherMatrix ->upload ((uint8_t *)ditherMatrix.data ());
7070
71- #if defined(NANOGUI_USE_OPENGL)
71+ # if defined(NANOGUI_USE_OPENGL)
7272 std::string preamble = " #version 110\n " ;
73- #elif defined(NANOGUI_USE_GLES)
73+ # elif defined(NANOGUI_USE_GLES)
7474 std::string preamble = " #version 100\n precision highp float; precision highp sampler2D;\n " ;
75- #endif
75+ # endif
7676 auto vertexShader = preamble + R"glsl(
7777 uniform vec2 ditherScale;
7878
@@ -95,10 +95,13 @@ ImageViewer::ImageViewer(
9595 uniform sampler2D framebufferTexture;
9696 uniform sampler2D ditherMatrix;
9797
98- uniform float displaySDRLevel;
98+ uniform float displaySdrWhiteLevel;
99+ uniform float minLuminance;
100+ uniform float maxLuminance;
101+
99102 uniform int outTransferFunction;
100103 uniform mat3 displayColorMatrix;
101- uniform bool clipToLdr ;
104+ uniform bool clipToUnitInterval ;
102105
103106 #define CM_TRANSFER_FUNCTION_BT1886 1
104107 #define CM_TRANSFER_FUNCTION_GAMMA22 2
@@ -147,12 +150,6 @@ ImageViewer::ImageViewer(
147150 #define HLG_B 0.28466892
148151 #define HLG_C 0.55991073
149152
150- #define SDR_MIN_LUMINANCE 0.2
151- #define SDR_MAX_LUMINANCE 80.0
152- #define HDR_MIN_LUMINANCE 0.005
153- #define HDR_MAX_LUMINANCE 10000.0
154- #define HLG_MAX_LUMINANCE 1000.0
155-
156153 #define M_E 2.718281828459045
157154
158155 vec3 mixb(vec3 a, vec3 b, bvec3 mask) {
@@ -166,14 +163,14 @@ ImageViewer::ImageViewer(
166163 return pow(
167164 (max(E - PQ_C1, vec3(0.0))) / max(PQ_C2 - PQ_C3 * E, vec3(1e-5)),
168165 vec3(PQ_INV_M1)
169- ) * 10000.0 / 203.0 ;
166+ );
170167 }
171168
172169 vec3 tfInvHLG(vec3 color) {
173170 bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT));
174171 vec3 lo = color.rgb * color.rgb / 3.0;
175172 vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0;
176- return mixb(hi, lo, isLow) * 1000.0 / 203.0 ;
173+ return mixb(hi, lo, isLow);
177174 }
178175
179176 // Many transfer functions (including sRGB) follow the same pattern: a linear
@@ -213,7 +210,6 @@ ImageViewer::ImageViewer(
213210 // Forward transfer functions corresponding to the inverse functions above.
214211 // Inputs are assumed to have 1 == 80 nits with a scale factor pre-applied to adjust for SDR white!
215212 vec3 tfPQ(vec3 color) {
216- color *= 80.0 / 10000.0;
217213 vec3 E = pow(max(color.rgb, vec3(0.0)), vec3(PQ_M1));
218214 return pow(
219215 (vec3(PQ_C1) + PQ_C2 * E) / max(vec3(1.0) + PQ_C3 * E, vec3(1e-5)),
@@ -222,7 +218,6 @@ ImageViewer::ImageViewer(
222218 }
223219
224220 vec3 tfHLG(vec3 color) {
225- color *= 80.0 / 1000.0;
226221 bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT));
227222 vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0);
228223 vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C;
@@ -323,22 +318,38 @@ ImageViewer::ImageViewer(
323318 }
324319 }
325320
321+ float transferWhiteLevel(int tf) {
322+ if (tf == CM_TRANSFER_FUNCTION_ST2084_PQ) {
323+ return 10000.0;
324+ } else if (tf == CM_TRANSFER_FUNCTION_HLG) {
325+ return 1000.0;
326+ } else if (tf == CM_TRANSFER_FUNCTION_BT1886) {
327+ return 100.0;
328+ } else {
329+ return 80.0;
330+ }
331+ }
332+
326333 vec3 dither(vec3 color) {
327334 return color + texture2D(ditherMatrix, fract(ditherUv)).r;
328335 }
329336
330337 void main() {
331338 vec4 color = texture2D(framebufferTexture, imageUv);
332- color = vec4(
333- fromLinearRGB(
334- displayColorMatrix * (toLinearRGB(color.rgb, CM_TRANSFER_FUNCTION_EXT_SRGB) * displaySDRLevel / 80.0),
335- outTransferFunction
336- ),
337- color.a
338- );
339339
340- color.rgb = dither(color.rgb);
341- if (clipToLdr) {
340+ // tev handles colors in linear sRGB with a scale that assumes SDR white corresponds to a value of 1. Hence, to convert to
341+ // absolute nits in the display's color space, we need to multiply by the SDR white level of the display, as well as its
342+ // color transform.
343+ vec3 nits = displayColorMatrix * (displaySdrWhiteLevel * toLinearRGB(color.rgb, CM_TRANSFER_FUNCTION_EXT_SRGB));
344+
345+ // Some displays perform strange tonemapping when provided with values outside of their luminance range. Make sure we don't
346+ // let this happen -- we strongly prefer hard clipping because we want the displayable colors to be preserved.
347+ if (maxLuminance > 0.0) {
348+ nits = clamp(nits, vec3(minLuminance), vec3(maxLuminance));
349+ }
350+
351+ color.rgb = dither(fromLinearRGB(nits / transferWhiteLevel(outTransferFunction), outTransferFunction));
352+ if (clipToUnitInterval) {
342353 color = clamp(color, vec4(0.0), vec4(1.0));
343354 }
344355
@@ -348,31 +359,25 @@ ImageViewer::ImageViewer(
348359
349360 try {
350361 m_cm_shader = new Shader (nullptr , " color_management" , vertexShader, fragmentShader);
351- } catch (const std::runtime_error& e) {
352- fprintf (stderr, " Error creating color management shader: %s\n " , e.what ());
353- }
362+ } catch (const std::runtime_error& e) { fprintf (stderr, " Error creating color management shader: %s\n " , e.what ()); }
354363
355364 uint32_t indices[3 * 2 ] = {0 , 1 , 2 , 2 , 3 , 0 };
356365 float positions[2 * 4 ] = {-1 .f , -1 .f , 1 .f , -1 .f , 1 .f , 1 .f , -1 .f , 1 .f };
357366
358367 m_cm_shader->set_buffer (" indices" , VariableType::UInt32, {3 * 2 }, indices);
359368 m_cm_shader->set_buffer (" position" , VariableType::Float32, {4 , 2 }, positions);
360369 m_cm_shader->set_texture (" ditherMatrix" , mDitherMatrix );
361-
362- const auto displayChroma = chromaFromWpPrimaries (m_display_primaries);
363- mDisplayColorMatrix = inverse (toMatrix3 (chromaToRec709Matrix (displayChroma)));
364- m_cm_shader->set_uniform (" displayColorMatrix" , mDisplayColorMatrix );
365370 }
366371#endif
367372
368- auto tf = ituth273::fromWpTransfer (m_display_transfer_function );
373+ auto tf = ituth273::fromWpTransfer (glfwGetWindowTransfer (m_glfw_window) );
369374 mSupportsHdr = m_float_buffer || tf == ituth273::ETransferCharacteristics::PQ || tf == ituth273::ETransferCharacteristics::HLG;
370375
371376 tlog::info () << fmt::format (
372377 " Obtained {} bit {} point frame buffer with primaries={} and transfer={}.{}" ,
373378 this ->bits_per_sample (),
374379 m_float_buffer ? " float" : " fixed" ,
375- wpPrimariesToString (m_display_primaries ),
380+ wpPrimariesToString (glfwGetWindowPrimaries (m_glfw_window) ),
376381 ituth273::toString (tf),
377382 mSupportsHdr ? " HDR display is supported." : " HDR is *not* supported."
378383 );
@@ -1371,12 +1376,19 @@ void ImageViewer::draw_contents() {
13711376
13721377 // Color management
13731378 if (m_cm_shader) {
1374- float displaySdrLevel = m_display_sdr_level_override ? m_display_sdr_level_override.value () : glfwGetWindowSdrWhiteLevel (m_glfw_window);
1375- m_cm_shader->set_uniform (" displaySDRLevel" , displaySdrLevel);
1376- m_cm_shader->set_uniform (" outTransferFunction" , m_display_transfer_function);
1377- m_cm_shader->set_uniform (" displayColorMatrix" , mDisplayColorMatrix );
1379+ float displaySdrWhiteLevel = m_display_sdr_white_level_override ? m_display_sdr_white_level_override.value () :
1380+ glfwGetWindowSdrWhiteLevel (m_glfw_window);
1381+ m_cm_shader->set_uniform (" displaySdrWhiteLevel" , displaySdrWhiteLevel);
1382+ m_cm_shader->set_uniform (" outTransferFunction" , (int )glfwGetWindowTransfer (m_glfw_window));
1383+
1384+ const auto displayChroma = chromaFromWpPrimaries (glfwGetWindowPrimaries (m_glfw_window));
1385+ const auto displayColorMatrix = inverse (toMatrix3 (chromaToRec709Matrix (displayChroma)));
1386+ m_cm_shader->set_uniform (" displayColorMatrix" , displayColorMatrix);
1387+
1388+ m_cm_shader->set_uniform (" minLuminance" , glfwGetWindowMinLuminance (m_glfw_window));
1389+ m_cm_shader->set_uniform (" maxLuminance" , glfwGetWindowMaxLuminance (m_glfw_window));
13781390
1379- m_cm_shader->set_uniform (" clipToLdr " , !m_float_buffer);
1391+ m_cm_shader->set_uniform (" clipToUnitInterval " , !m_float_buffer);
13801392
13811393 m_cm_shader->set_uniform (" ditherScale" , (1 .0f / DITHER_MATRIX_SIZE) * Vector2f (m_fbsize[0 ], m_fbsize[1 ]));
13821394 m_cm_shader->set_texture (" ditherMatrix" , mDitherMatrix );
0 commit comments