From ab9d132964acb88e69fb90e33cb4ee0c3aae6694 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Fri, 20 Aug 2021 15:40:45 -0700 Subject: [PATCH] Use wide gamut color grading and gamut mapping (#4524) * Use wide gamut color grading and gamut mapping This change introduces a new API on ColorGrading to enable or disable gamut mapping at will. This feature is currently off by default and while useful and recommended, it isn't cheap and we need to figure out what we want our defaults to be especially on mobile targets. Gamut mapping can be combined with luminance scaling to properly control hue skews. * Small cleanup * More precise Rec.2020 conversion --- .../src/main/cpp/ColorGrading.cpp | 7 ++ .../google/android/filament/ColorGrading.java | 19 ++++ filament/include/filament/ColorGrading.h | 17 +++- filament/src/ColorGrading.cpp | 98 ++++++++++++++----- filament/src/ColorSpace.cpp | 29 ++++-- filament/src/ColorSpace.h | 24 +++-- filament/src/ToneMapper.cpp | 11 ++- libs/viewer/include/viewer/Settings.h | 1 + libs/viewer/src/Settings.cpp | 5 + libs/viewer/src/SimpleViewer.cpp | 1 + libs/viewer/tests/test_settings.cpp | 1 + 11 files changed, 162 insertions(+), 51 deletions(-) diff --git a/android/filament-android/src/main/cpp/ColorGrading.cpp b/android/filament-android/src/main/cpp/ColorGrading.cpp index 91c3e3ad76..d690216bb5 100644 --- a/android/filament-android/src/main/cpp/ColorGrading.cpp +++ b/android/filament-android/src/main/cpp/ColorGrading.cpp @@ -76,6 +76,13 @@ Java_com_google_android_filament_ColorGrading_nBuilderLuminanceScaling(JNIEnv*, builder->luminanceScaling(luminanceScaling); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_ColorGrading_nBuilderGamutMapping(JNIEnv*, jclass, + jlong nativeBuilder, jboolean gamutMapping) { + ColorGrading::Builder* builder = (ColorGrading::Builder*) nativeBuilder; + builder->gamutMapping(gamutMapping); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_ColorGrading_nBuilderExposure(JNIEnv*, jclass, jlong nativeBuilder, jfloat exposure) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java index 01401a1eaa..ca45d37019 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java +++ b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java @@ -65,6 +65,7 @@ import static com.google.android.filament.Asserts.assertFloat4In; *
  • Curves
  • *
  • Tone mapping
  • *
  • Luminance scaling
  • + *
  • Gamut mapping
  • * * *

    Defaults

    @@ -83,6 +84,7 @@ import static com.google.android.filament.Asserts.assertFloat4In; *
  • Curves: gamma {1,1,1}, midPoint {1,1,1}, and scale {1,1,1}
  • *
  • Tone mapping: {@link ToneMapper.ACESLegacy}
  • *
  • Luminance scaling: false
  • + *
  • Gamut mapping: false
  • * * * @see View @@ -213,6 +215,22 @@ public class ColorGrading { return this; } + /** + * Enables or disables gamut mapping to the destination color space's gamut. When gamut + * mapping is turned off, out-of-gamut colors are clipped to the destination's gamut, + * which may produce hue skews (blue skewing to purple, green to yellow, etc.). When + * gamut mapping is enabled, out-of-gamut colors are brought back in gamut by trying to + * preserve the perceived chroma and lightness of the original values. + * + * @param gamutMapping Enables or disables gamut mapping + * + * @return This Builder, for chaining calls + */ + public Builder gamutMapping(boolean gamutMapping) { + nBuilderGamutMapping(mNativeBuilder, gamutMapping); + return this; + } + /** * Adjusts the exposure of this image. The exposure is specified in stops: * each stop brightens (positive values) or darkens (negative values) the image by @@ -524,6 +542,7 @@ public class ColorGrading { private static native void nBuilderToneMapper(long nativeBuilder, long toneMapper); private static native void nBuilderToneMapping(long nativeBuilder, int toneMapping); private static native void nBuilderLuminanceScaling(long nativeBuilder, boolean luminanceScaling); + private static native void nBuilderGamutMapping(long nativeBuilder, boolean gamutMapping); private static native void nBuilderExposure(long nativeBuilder, float exposure); private static native void nBuilderWhiteBalance(long nativeBuilder, float temperature, float tint); private static native void nBuilderChannelMixer(long nativeBuilder, float[] outRed, float[] outGreen, float[] outBlue); diff --git a/filament/include/filament/ColorGrading.h b/filament/include/filament/ColorGrading.h index 9b583a5e54..1f54741537 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -76,6 +76,7 @@ class FColorGrading; * - Curves * - Tone mapping * - Luminance scaling + * - Gamut mapping * * Defaults * ======== @@ -93,6 +94,7 @@ class FColorGrading; * - Curves: gamma {1,1,1}, midPoint {1,1,1}, and scale {1,1,1} * - Tone mapping: ACESLegacyToneMapper * - Luminance scaling: false + * - Gamut mapping: false * * @see View */ @@ -186,12 +188,25 @@ public: * When luminance scaling is enabled, tone mapping is performed on the luminance of each * pixel instead of per-channel. * - * @param luminanceScaling Enables or disables EVILS post-tone mapping + * @param luminanceScaling Enables or disables luminance scaling post-tone mapping * * @return This Builder, for chaining calls */ Builder& luminanceScaling(bool luminanceScaling) noexcept; + /** + * Enables or disables gamut mapping to the destination color space's gamut. When gamut + * mapping is turned off, out-of-gamut colors are clipped to the destination's gamut, + * which may produce hue skews (blue skewing to purple, green to yellow, etc.). When + * gamut mapping is enabled, out-of-gamut colors are brought back in gamut by trying to + * preserve the perceived chroma and lightness of the original values. + * + * @param gamutMapping Enables or disables gamut mapping + * + * @return This Builder, for chaining calls + */ + Builder& gamutMapping(bool gamutMapping) noexcept; + /** * Adjusts the exposure of this image. The exposure is specified in stops: * each stop brightens (positive values) or darkens (negative values) the image by diff --git a/filament/src/ColorGrading.cpp b/filament/src/ColorGrading.cpp index 8fc5732bae..804cb77c06 100644 --- a/filament/src/ColorGrading.cpp +++ b/filament/src/ColorGrading.cpp @@ -56,8 +56,9 @@ struct ColorGrading::BuilderDetails { // Everything below must be part of the == comparison operator ColorGrading::QualityLevel quality = QualityLevel::MEDIUM; - // Luminance scaling + // Out-of-gamut color handling bool luminanceScaling = false; + bool gamutMapping = false; // Exposure float exposure = 0.0f; // White balance @@ -92,6 +93,7 @@ struct ColorGrading::BuilderDetails { // Note: Do NOT compare hasAdjustments and toneMapper return quality == rhs.quality && luminanceScaling == rhs.luminanceScaling && + gamutMapping == rhs.gamutMapping && exposure == rhs.exposure && whiteBalance == rhs.whiteBalance && outRed == rhs.outRed && @@ -141,6 +143,11 @@ ColorGrading::Builder& ColorGrading::Builder::luminanceScaling(bool luminanceSca return *this; } +ColorGrading::Builder& ColorGrading::Builder::gamutMapping(bool gamutMapping) noexcept { + mImpl->gamutMapping = gamutMapping; + return *this; +} + ColorGrading::Builder& ColorGrading::Builder::exposure(float exposure) noexcept { mImpl->exposure = exposure; return *this; @@ -268,22 +275,21 @@ inline float3 adjustExposure(float3 v, float exposure) { // the von Kries method, using the CIECAT16 transform. // See https://en.wikipedia.org/wiki/Chromatic_adaptation // See https://en.wikipedia.org/wiki/CIECAM02#Chromatic_adaptation -UTILS_ALWAYS_INLINE -inline float3 adaptationTransform(float2 whiteBalance) { +constexpr mat3f adaptationTransform(float2 whiteBalance) noexcept { // See Mathematica notebook in docs/math/White Balance.nb float k = whiteBalance.x; // temperature float t = whiteBalance.y; // tint - float x = ILLUMINANT_D65_xyY.x - k * (k < 0.0f ? 0.0214f : 0.066f); + float x = ILLUMINANT_D65_xyY[0] - k * (k < 0.0f ? 0.0214f : 0.066f); float y = chromaticityCoordinateIlluminantD(x) + t * 0.066f; float3 lms = XYZ_to_CIECAT16 * xyY_to_XYZ({x, y, 1.0f}); - return ILLUMINANT_D65_LMS_CAT16 / lms; + return LMS_CAT16_to_REC2020 * mat3f{ILLUMINANT_D65_LMS_CAT16 / lms} * REC2020_to_LMS_CAT16; } UTILS_ALWAYS_INLINE -inline float3 chromaticAdaptation(float3 v, float3 adaptationTransform) { - return LMS_CAT16_to_sRGB * (adaptationTransform * (sRGB_to_LMS_CAT16 * v)); +inline float3 chromaticAdaptation(float3 v, mat3f adaptationTransform) { + return adaptationTransform * v; } //------------------------------------------------------------------------------ @@ -405,7 +411,7 @@ static float3 luminanceScaling(float3 x, // Quality //------------------------------------------------------------------------------ -size_t selectLutDimension(ColorGrading::QualityLevel quality) { +static size_t selectLutDimension(ColorGrading::QualityLevel quality) noexcept { switch (quality) { case ColorGrading::QualityLevel::LOW: return 16u; case ColorGrading::QualityLevel::MEDIUM: return 32u; @@ -414,8 +420,8 @@ size_t selectLutDimension(ColorGrading::QualityLevel quality) { } } -void selectLutTextureParams(ColorGrading::QualityLevel quality, - TextureFormat& internalFormat, PixelDataFormat& format, PixelDataType& type) { +static void selectLutTextureParams(ColorGrading::QualityLevel quality, + TextureFormat& internalFormat, PixelDataFormat& format, PixelDataType& type) noexcept { // We use RGBA16F for high quality modes instead of RGB16F because RGB16F // is not supported everywhere switch (quality) { @@ -442,13 +448,45 @@ void selectLutTextureParams(ColorGrading::QualityLevel quality, } } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +// The following functions exist to preserve backward compatibility with the +// `FILMIC` set via the deprecated `ToneMapping` API. Selecting `ToneMapping::FILMIC` +// forces post-processing to be performed in sRGB to guarantee that the inverse tone +// mapping function in the shaders will match the forward tone mapping step exactly. + +static mat3f selectColorGradingTransformIn(ColorGrading::ToneMapping toneMapping) noexcept { + if (toneMapping == ColorGrading::ToneMapping::FILMIC) { + return mat3f{}; + } + return sRGB_to_REC2020; +} + +static mat3f selectColorGradingTransformOut(ColorGrading::ToneMapping toneMapping) noexcept { + if (toneMapping == ColorGrading::ToneMapping::FILMIC) { + return mat3f{}; + } + return REC2020_to_sRGB; +} + +static float3 selectColorGradingLuminance(ColorGrading::ToneMapping toneMapping) noexcept { + if (toneMapping == ColorGrading::ToneMapping::FILMIC) { + return LUMINANCE_REC709; + } + return LUMINANCE_REC2020; +} +#pragma clang diagnostic pop + //------------------------------------------------------------------------------ // Color grading implementation //------------------------------------------------------------------------------ struct Config { size_t lutDimension; - float3 adaptationTransform; + mat3f adaptationTransform; + mat3f colorGradingIn; + mat3f colorGradingOut; + float3 colorGradingLuminance; }; // Inside the FColorGrading constructor, TSAN sporadically detects a data race on the config struct; @@ -466,8 +504,11 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { utils::SpinLock configLock; { std::lock_guard lock(configLock); - c.lutDimension = selectLutDimension(builder->quality); - c.adaptationTransform = adaptationTransform(builder->whiteBalance); + c.lutDimension = selectLutDimension(builder->quality); + c.adaptationTransform = adaptationTransform(builder->whiteBalance); + c.colorGradingIn = selectColorGradingTransformIn(builder->toneMapping); + c.colorGradingOut = selectColorGradingTransformOut(builder->toneMapping); + c.colorGradingLuminance = selectColorGradingLuminance(builder->toneMapping); } mDimension = c.lutDimension; @@ -511,18 +552,16 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { // LogC encoding v = LogC_to_linear(v); - // TODO: Performed in sRGB, should be in Rec.2020 or AP1 + // Move to color grading color space + v = c.colorGradingIn * v; if (builder->hasAdjustments) { // Exposure v = adjustExposure(v, builder->exposure); + // White balance v = chromaticAdaptation(v, config.adaptationTransform); - } - // TODO: We should convert to a color grading color space here - - if (builder->hasAdjustments) { // Kill negative values before the next transforms v = max(v, 0.0f); @@ -530,12 +569,11 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue); // Shadows/mid-tones/highlights - v = tonalRanges(v, LUMA_REC709, + v = tonalRanges(v, c.colorGradingLuminance, builder->shadows, builder->midtones, builder->highlights, builder->tonalRanges); - // The adjustments below behave better in log space using the ACEScct - // color space. + // The adjustments below behave better in log space v = linear_to_LogC(v); // ASC CDL @@ -548,12 +586,12 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { v = LogC_to_linear(v); // Vibrance in linear space - v = vibrance(v, LUMA_REC709, builder->vibrance); + v = vibrance(v, c.colorGradingLuminance, builder->vibrance); // Saturation in linear space - v = saturation(v, LUMA_REC709, builder->saturation); + v = saturation(v, c.colorGradingLuminance, builder->saturation); - // Kill negative values before tone mapping + // Kill negative values before curves v = max(v, 0.0f); // RGB curves @@ -563,15 +601,23 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { // Tone mapping if (builder->luminanceScaling) { - v = luminanceScaling(v, *builder->toneMapper, LUMA_HK_REC709); + v = luminanceScaling(v, *builder->toneMapper, c.colorGradingLuminance); } else { v = (*builder->toneMapper)(v); } + // Go back to display color space + v = c.colorGradingOut * v; + + // Apply gamut mapping + if (builder->gamutMapping) { + // TODO: This should depend on the output color space + v = gamutMapping_sRGB(v); + } + // TODO: We should convert to the output color space if we use a working // color space that's not sRGB // TODO: Allow the user to customize the output color space - // TODO: Gamut mapping // We need to clamp for the output transfer function v = saturate(v); diff --git a/filament/src/ColorSpace.cpp b/filament/src/ColorSpace.cpp index 93d31c662c..9b64f5b95f 100644 --- a/filament/src/ColorSpace.cpp +++ b/filament/src/ColorSpace.cpp @@ -45,10 +45,14 @@ using namespace math; * SOFTWARE. */ +// TODO: Replace compute_max_saturation() with solution from +// https://simonstechblog.blogspot.com/2021/06/implementing-gamut-mapping.html to +// target arbitrary output gamuts. The solution below works only for sRGB/Rec.709 + // Finds the maximum saturation possible for a given hue that fits in sRGB // Saturation here is defined as S = C/L // a and b must be normalized so a^2 + b^2 == 1 -float compute_max_saturation(float a, float b) noexcept { +static float compute_max_saturation(float a, float b) noexcept { // Max saturation will be when one of r, g or b goes below zero. // Select different coefficients depending on which component goes below zero first @@ -127,13 +131,13 @@ float compute_max_saturation(float a, float b) noexcept { // finds L_cusp and C_cusp for a given hue // a and b must be normalized so a^2 + b^2 == 1 -float2 find_cusp(float a, float b) noexcept { +static float2 find_cusp(float a, float b) noexcept { // First, find the maximum saturation (saturation S = C/L) float S_cusp = compute_max_saturation(a, b); // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1: float3 rgb_at_max = OkLab_to_sRGB({1.0f, S_cusp * a, S_cusp * b}); - float L_cusp = std::cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b)); + float L_cusp = std::cbrt(1.0f / max(rgb_at_max)); float C_cusp = L_cusp * S_cusp; return {L_cusp, C_cusp}; @@ -143,11 +147,11 @@ float2 find_cusp(float a, float b) noexcept { // L = L0 * (1 - t) + t * L1; // C = t * C1; // a and b must be normalized so a^2 + b^2 == 1 -float find_gamut_intersection(float a, float b, float L1, float C1, float L0) noexcept { +static float find_gamut_intersection(float a, float b, float L1, float C1, float L0) noexcept { // Find the cusp of the gamut triangle float2 cusp = find_cusp(a, b); - // Find the intersection for upper and lower half seprately + // Find the intersection for upper and lower half separately float t; if (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f) { // Lower half @@ -231,8 +235,15 @@ constexpr float sgn(float x) noexcept { return (float) (0.f < x) - (float) (x < 0.f); } -inline float3 gamut_clip_adaptive_L0_0_5(float3 rgb, float alpha = 0.05f) noexcept { - if (all(lessThanEqual(rgb, float3{1.0f})) && all(greaterThanEqual(rgb, float3{0.0f}))) { +// Adaptive L0 = 0.5, with alpha set to 0.05 by default +// The threshold parameters defines a flexible range above 1.0 and below 0.0 where out-of-gamut +// values are still considered in-gamut. This helps control for inaccuracies in the previous +// color grading steps that may slightly deviate from in-gamut values when they shouldn't. +inline float3 gamut_clip_adaptive_L0_0_5(float3 rgb, + float alpha = 0.05f, float threshold = 0.03f) noexcept { + + if (all(lessThanEqual(rgb, float3{1.0f + threshold})) && + all(greaterThanEqual(rgb, float3{-threshold}))) { return rgb; } @@ -246,7 +257,7 @@ inline float3 gamut_clip_adaptive_L0_0_5(float3 rgb, float alpha = 0.05f) noexce float Ld = L - 0.5f; float e1 = 0.5f + std::abs(Ld) + alpha * C; - float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - std::sqrt(e1 * e1 - 2.f * std::abs(Ld)))); + float L0 = 0.5f * (1.0f + sgn(Ld) * (e1 - std::sqrt(e1 * e1 - 2.0f * std::abs(Ld)))); float t = find_gamut_intersection(a_, b_, L, C, L0); float L_clipped = L0 * (1.f - t) + t * L; @@ -256,7 +267,7 @@ inline float3 gamut_clip_adaptive_L0_0_5(float3 rgb, float alpha = 0.05f) noexce } float3 gamutMapping_sRGB(float3 rgb) noexcept { - return gamut_clip_adaptive_L0_0_5(rgb, 0.5f); + return gamut_clip_adaptive_L0_0_5(rgb); } /* diff --git a/filament/src/ColorSpace.h b/filament/src/ColorSpace.h index 28194cd300..9622ef337b 100644 --- a/filament/src/ColorSpace.h +++ b/filament/src/ColorSpace.h @@ -53,15 +53,15 @@ constexpr mat3f sRGB_to_XYZ{ }; constexpr mat3f REC2020_to_XYZ{ - 0.6369580f, 0.2627002f, 0.0000000f, - 0.1446169f, 0.6779981f, 0.0280727f, - 0.1688810f, 0.0593017f, 1.0609851f + 0.6369530f, 0.2626983f, 0.0000000f, + 0.1446169f, 0.6780088f, 0.0280731f, + 0.1688558f, 0.0592929f, 1.0608272f }; constexpr mat3f XYZ_to_REC2020{ - 1.7166512f, -0.6666844f, 0.0176399f, - -0.3556708f, 1.6164812f, -0.0427706f, - -0.2533663f, 0.0157685f, 0.9421031f + 1.7166634f, -0.6666738f, 0.0176425f, + -0.3556733f, 1.6164557f, -0.0427770f, + -0.2533681f, 0.0157683f, 0.9422433f }; constexpr mat3f XYZ_to_CIECAT16{ @@ -156,6 +156,10 @@ constexpr mat3f sRGB_to_LMS_CAT16 = XYZ_to_CIECAT16 * sRGB_to_XYZ; constexpr mat3f LMS_CAT16_to_sRGB = XYZ_to_sRGB * CIECAT16_to_XYZ; +constexpr mat3f REC2020_to_LMS_CAT16 = XYZ_to_CIECAT16 * REC2020_to_XYZ; + +constexpr mat3f LMS_CAT16_to_REC2020 = XYZ_to_REC2020 * CIECAT16_to_XYZ; + constexpr mat3f REC2020_to_AP0 = AP1_to_AP0 * XYZ_to_AP1 * REC2020_to_XYZ; constexpr mat3f AP1_to_REC2020 = XYZ_to_REC2020 * AP1_to_XYZ; @@ -172,16 +176,16 @@ constexpr float3 ILLUMINANT_D65_xyY{0.31271f, 0.32902f, 1.0f}; constexpr float3 ILLUMINANT_D65_LMS_CAT16{0.975533f, 1.016483f, 1.084837f}; // RGB to luminance coefficients for Rec.2020, from REC2020_to_XYZ -constexpr float3 LUMA_REC2020{0.2627002f, 0.6779981f, 0.0593017f}; +constexpr float3 LUMINANCE_REC2020{0.2627002f, 0.6779981f, 0.0593017f}; // RGB to luminance coefficients for ACEScg (AP1), from AP1_to_XYZ -constexpr float3 LUMA_AP1{0.272229f, 0.674082f, 0.0536895f}; +constexpr float3 LUMINANCE_AP1{0.272229f, 0.674082f, 0.0536895f}; // RGB to luminance coefficients for Rec.709, from sRGB_to_XYZ -constexpr float3 LUMA_REC709{0.2126730f, 0.7151520f, 0.0721750f}; +constexpr float3 LUMINANCE_REC709{0.2126730f, 0.7151520f, 0.0721750f}; // RGB to luminance coefficients for Rec.709 with HK-like weighting -constexpr float3 LUMA_HK_REC709{0.13913043f, 0.73043478f, 0.13043478f}; +constexpr float3 LUMINANCE_HK_REC709{0.13913043f, 0.73043478f, 0.13043478f}; constexpr float MIDDLE_GRAY_ACEScg = 0.18f; diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index 7fdf4eb189..29d84327ef 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -135,7 +135,7 @@ float3 ACES(float3 color, float brightness) noexcept { constexpr float RRT_SAT_FACTOR = 0.96f; constexpr float ODT_SAT_FACTOR = 0.93f; - float3 ap0 = sRGB_to_AP0 * color; + float3 ap0 = REC2020_to_AP0 * color; // Glow module float saturation = rgb_2_saturation(ap0); @@ -156,7 +156,7 @@ float3 ACES(float3 color, float brightness) noexcept { float3 ap1 = clamp(AP0_to_AP1 * ap0, 0.0f, (float) std::numeric_limits::max()); // Global desaturation - ap1 = mix(float3(dot(ap1, LUMA_AP1)), ap1, RRT_SAT_FACTOR); + ap1 = mix(float3(dot(ap1, LUMINANCE_AP1)), ap1, RRT_SAT_FACTOR); // NOTE: This is specific to Filament and added only to match ACES to our legacy tone mapper // which was a fit of ACES in Rec.709 but with a brightness boost. @@ -175,9 +175,9 @@ float3 ACES(float3 color, float brightness) noexcept { float3 linearCV = darkSurround_to_dimSurround(rgbPost); // Apply desaturation to compensate for luminance difference - linearCV = mix(float3(dot(linearCV, LUMA_AP1)), linearCV, ODT_SAT_FACTOR); + linearCV = mix(float3(dot(linearCV, LUMINANCE_AP1)), linearCV, ODT_SAT_FACTOR); - return AP1_to_sRGB * linearCV; + return AP1_to_REC2020 * linearCV; } } // namespace aces @@ -260,7 +260,8 @@ float3 DisplayRangeToneMapper::operator()(math::float3 c) const noexcept { // The 5th color in the array (cyan) represents middle gray (18%) // Every stop above or below middle gray causes a color shift - float v = log2(dot(c, LUMA_REC709) / 0.18f); + // TODO: This should depend on the working color grading color space + float v = log2(dot(c, LUMINANCE_REC2020) / 0.18f); v = clamp(v + 5.0f, 0.0f, 15.0f); size_t index = size_t(v); diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index 59413898a1..8daad7244f 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -118,6 +118,7 @@ struct ColorGradingSettings { ToneMapping toneMapping = ToneMapping::ACES_LEGACY; GenericToneMapperSettings genericToneMapper; bool luminanceScaling = false; + bool gamutMapping = false; float exposure = 0.0f; float temperature = 0.0f; float tint = 0.0f; diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index a255f5b6e4..954db89c9d 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -310,6 +310,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGra i = parse(tokens, i + 1, jsonChunk, &out->genericToneMapper); } else if (compare(tok, jsonChunk, "luminanceScaling") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->luminanceScaling); + } else if (compare(tok, jsonChunk, "gamutMapping") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->gamutMapping); } else if (compare(tok, jsonChunk, "exposure") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->exposure); } else if (compare(tok, jsonChunk, "temperature") == 0) { @@ -1034,6 +1036,7 @@ ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* e .curves(settings.gamma, settings.midPoint, settings.scale) .toneMapper(toneMapper) .luminanceScaling(settings.luminanceScaling) + .gamutMapping(settings.gamutMapping) .build(*engine); delete toneMapper; return colorGrading; @@ -1156,6 +1159,7 @@ static std::ostream& operator<<(std::ostream& out, const ColorGradingSettings& i << "\"toneMapping\": " << (in.toneMapping) << ",\n" << "\"genericToneMapper\": " << (in.genericToneMapper) << ",\n" << "\"luminanceScaling\": " << to_string(in.luminanceScaling) << ",\n" + << "\"gamutMapping\": " << to_string(in.gamutMapping) << ",\n" << "\"exposure\": " << (in.exposure) << ",\n" << "\"temperature\": " << (in.temperature) << ",\n" << "\"tint\": " << (in.tint) << ",\n" @@ -1427,6 +1431,7 @@ bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const { toneMapping == rhs.toneMapping && genericToneMapper == rhs.genericToneMapper && luminanceScaling == rhs.luminanceScaling && + gamutMapping == rhs.gamutMapping && exposure == rhs.exposure && temperature == rhs.temperature && tint == rhs.tint && diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 18112bdb00..6a6afb03c9 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -211,6 +211,7 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo ImGui::PopStyleColor(); ImGui::Checkbox("Luminance scaling", &colorGrading.luminanceScaling); + ImGui::Checkbox("Gamut mapping", &colorGrading.gamutMapping); ImGui::SliderFloat("Exposure", &colorGrading.exposure, -10.0f, 10.0f); diff --git a/libs/viewer/tests/test_settings.cpp b/libs/viewer/tests/test_settings.cpp index 2f4f6f4a43..75f3e84c90 100644 --- a/libs/viewer/tests/test_settings.cpp +++ b/libs/viewer/tests/test_settings.cpp @@ -45,6 +45,7 @@ static const char* JSON_TEST_DEFAULTS = R"TXT( "hdrMax": 16.0 }, "luminanceScaling": false, + "gamutMapping": false, "exposure": 0, "temperature": 0, "tint": 0,