From 1aa57fc4253b17f437c07c2a9cd8d44b3d1fbcfa Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Tue, 2 Jun 2020 22:54:00 -0700 Subject: [PATCH] Color grading: add saturation and contrast (#2627) * Add transforms for ACEScct and enabling/disabling color grading in sample * Add saturation and contrast adjustments * Rename ACES to ACES_LEGACY and introduce ACES ACES_LEGACY is ACES with a brightness boost to match our old tone mapping operator. --- RELEASE_NOTES.md | 2 +- filament/CMakeLists.txt | 1 - filament/include/filament/ColorGrading.h | 45 ++++++++-- filament/src/Color.cpp | 15 ++-- filament/src/ColorGrading.cpp | 107 +++++++++++++++-------- filament/src/ColorSpace.h | 107 +++++++++++++++++++++-- filament/src/ToneMapping.h | 64 +++++++------- filament/src/details/ColorGrading.h | 3 - libs/math/include/math/TVecHelpers.h | 7 ++ samples/material_sandbox.cpp | 52 ++++++----- samples/material_sandbox.h | 5 +- 11 files changed, 292 insertions(+), 116 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6375dbee0d..be6d2403ac 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -15,7 +15,7 @@ A new header is inserted each time a *tag* is created. - Tone mappping now uses the real ACES tone mapper, applied in the proper color space. - Tone mapping is now applied via a LUT. - Color grading capabilities per View: white balance (temperature/tint), channel mixer, - tonal ranges (shadows/mid-tones/highlights). + tonal ranges (shadows/mid-tones/highlights), contrast, saturation. - Fixed bug in the Metal backend when SSR and MSAA were turned on - Fixed Metal issue with `BufferDescriptor` and `PixelBufferDescriptor`s not being called on application thread. diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 78ae52ed3d..c998f89924 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -296,7 +296,6 @@ target_link_libraries(${TARGET} PUBLIC utils) target_link_libraries(${TARGET} PUBLIC geometry) # TODO: remove this dependency after deprecating VertexBuffer::populateTangentQuaternions target_link_libraries(${TARGET} PUBLIC filaflat) target_link_libraries(${TARGET} PUBLIC filabridge) -target_link_libraries(${TARGET} PUBLIC image_headers) target_link_libraries(${TARGET} PUBLIC ibl) # Link in matdbg for Desktop + Debug only since it pulls in filamat and a web server. diff --git a/filament/include/filament/ColorGrading.h b/filament/include/filament/ColorGrading.h index 2272971fc5..866eaf1da8 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -67,6 +67,8 @@ class FColorGrading; * - White balance * - Channel mixer * - Shadows/mid-tones/highlights + * - Contrast + * - Saturation * - Tone mapping * * Defaults @@ -77,7 +79,9 @@ class FColorGrading; * - Channel mixer: red {1,0,0}, green {0,1,0}, blue {0,0,1} * - Shadows/mid-tones/highlights: shadows {1,1,1,0}, mid-tones {1,1,1,0}, highlights {1,1,1,0}, * ranges {0,0.333,0.550,1} - * - Tone mapping: ACES + * - Contrast: 1.0 + * - Saturation: 1.0 + * - Tone mapping: ACES_LEGACY * * @see View */ @@ -90,10 +94,11 @@ public: */ enum class ToneMapping : uint8_t { LINEAR = 0, //!< Linear tone mapping (i.e. no tone mapping) - ACES = 1, //!< ACES tone mapping, with a brightness modifier - FILMIC = 2, //!< Filmic tone mapping, modelled after ACES but applied in sRGB space - REINHARD = 3, //!< Reinhard luma-based tone mapping - DISPLAY_RANGE = 4, //!< Tone mapping used to validate/debug scene exposure + ACES_LEGACY = 1, //!< ACES tone mapping, with a brightness modifier to match Filament's legacy tone mapper + ACES = 2, //!< ACES tone mapping + FILMIC = 3, //!< Filmic tone mapping, modelled after ACES but applied in sRGB space + REINHARD = 4, //!< Reinhard luma-based tone mapping + DISPLAY_RANGE = 5, //!< Tone mapping used to validate/debug scene exposure }; //! Use Builder to construct a ColorGrading object instance @@ -111,7 +116,7 @@ public: * Selects the tone mapping operator to apply to the HDR color buffer as the last * operation of the color grading post-processing step. * - * The default tone mapping operator is ACES. + * The default tone mapping operator is ACES_LEGACY. * * @param toneMapping The tone mapping operator to apply to the HDR color buffer * @@ -202,6 +207,34 @@ public: math::float4 shadows, math::float4 midtones, math::float4 highlights, math::float4 ranges = math::float4{0.0f, 0.333f, 0.550f, 1.0f}) noexcept; + /** + * Adjusts the contrast of the image. Lower values decrease the contrast of the image + * (the tonal range is narrowed), and higher values increase the contrast of the image + * (the tonal range is widened). A value of 1.0 has no effect. + * + * The contrast is defined as a value in the range [0.0...2.0]. Values outside of that + * range will be clipped to that range. + * + * @param constrast Contrast expansion, between 0.0 and 2.0. 1.0 leaves contrast unaffected + * + * @return This Builder, for chaining calls + */ + Builder& contrast(float constrast) noexcept; + + /** + * Adjusts the saturation of the image. Lower values decrease intensity of the colors + * present in the image, and higher values increase the intensity of the colors in the + * image. A value of 1.0 has no effect. + * + * The saturation is defined as a value in the range [0.0...2.0]. Values outside of that + * range will be clipped to that range. + * + * @param constrast Saturation, between 0.0 and 2.0. 1.0 leaves saturation unaffected + * + * @return This Builder, for chaining calls + */ + Builder& saturation(float saturation) noexcept; + /** * Creates the ColorGrading object and returns a pointer to it. * diff --git a/filament/src/Color.cpp b/filament/src/Color.cpp index e5f432a87d..7711a3f24e 100644 --- a/filament/src/Color.cpp +++ b/filament/src/Color.cpp @@ -18,22 +18,19 @@ #include "ColorSpace.h" -#include - #include - -#include +#include namespace filament { using namespace math; float3 Color::sRGBToLinear(float3 color) noexcept { - return image::sRGBToLinear(color); + return EOCF_sRGB(color); } float3 Color::linearToSRGB(float3 color) noexcept { - return image::linearToSRGB(color); + return OECF_sRGB(color); } LinearColor Color::cct(float K) { @@ -47,7 +44,7 @@ LinearColor Color::cct(float K) { float d = 1.0f / (2.0f * u - 8.0f * v + 4.0f); float3 linear = XYZ_to_sRGB * xyY_to_XYZ({3.0f * u * d, 2.0f * v * d, 1.0f}); // normalize and saturate - return saturate(linear / std::max(1e-5f, max(linear))); + return saturate(linear / max(1e-5f, max(linear))); } LinearColor Color::illuminantD(float K) { @@ -61,11 +58,11 @@ LinearColor Color::illuminantD(float K) { float3 linear = XYZ_to_sRGB * xyY_to_XYZ({x, y, 1.0f}); // normalize and saturate - return saturate(linear / std::max(1e-5f, max(linear))); + return saturate(linear / max(1e-5f, max(linear))); } LinearColor Color::absorptionAtDistance(LinearColor const& color, float distance) { - return -log(clamp(color, 1e-5f, 1.0f)) / std::max(1e-5f, distance); + return -log(clamp(color, 1e-5f, 1.0f)) / max(1e-5f, distance); } } // namespace filament diff --git a/filament/src/ColorGrading.cpp b/filament/src/ColorGrading.cpp index 77574ced1f..c59aa952ef 100644 --- a/filament/src/ColorGrading.cpp +++ b/filament/src/ColorGrading.cpp @@ -21,18 +21,13 @@ #include "FilamentAPI-impl.h" #include "ColorSpace.h" -// When defined, the ACES tone mapper will match the brightness of the filmic ("ACES sRGB") -// tone mapper. It is *not* correct, but it helps for compatibility -// TODO: Always enable this, expose a float to control this (1.0 == real ACES) -#define TONEMAP_ACES_MATCH_BRIGHTNESS + #include "ToneMapping.h" #include #include #include -#include - #include #include @@ -51,7 +46,7 @@ static constexpr size_t LUT_DIMENSION = 32u; //------------------------------------------------------------------------------ struct ColorGrading::BuilderDetails { - ToneMapping toneMapping = ToneMapping::ACES; + ToneMapping toneMapping = ToneMapping::ACES_LEGACY; float2 whiteBalance = {0.0f, 0.0f}; float3 outRed = {1.0f, 0.0f, 0.0f}; float3 outGreen = {0.0f, 1.0f, 0.0f}; @@ -60,6 +55,8 @@ struct ColorGrading::BuilderDetails { float3 midtones = {1.0f, 1.0f, 1.0f}; float3 highlights = {1.0f, 1.0f, 1.0f}; float4 tonalRanges = {0.0f, 0.333f, 0.550f, 1.0f}; // defaults in DaVinci Resolve + float saturation = 1.0f; + float contrast = 1.0f; }; using BuilderType = ColorGrading; @@ -106,6 +103,16 @@ ColorGrading::Builder& ColorGrading::Builder::shadowsMidtonesHighlights( return *this; } +ColorGrading::Builder& ColorGrading::Builder::saturation(float saturation) noexcept { + mImpl->saturation = clamp(saturation, 0.0f, 2.0f); + return *this; +} + +ColorGrading::Builder& ColorGrading::Builder::contrast(float contrast) noexcept { + mImpl->contrast = clamp(contrast, 0.0f, 2.0f); + return *this; +} + ColorGrading* ColorGrading::Builder::build(Engine& engine) { return upcast(engine).createColorGrading(*this); } @@ -126,7 +133,7 @@ inline float3 adaptationTransform(float2 whiteBalance) { float t = whiteBalance.y; // tint float x = ILLUMINANT_D65_xyY.x - k * (k < 0.0f ? 0.0214f : 0.066f); - float y = illuminantD_y(x) + t * 0.066f; + float y = chromaticityCoordinateIlluminantD(x) + t * 0.066f; float3 lms = XYZ_to_CIECAT02 * xyY_to_XYZ({x, y, 1.0f}); return ILLUMINANT_D65_LMS / lms; @@ -144,8 +151,31 @@ inline float3 chromaticAdaptation(float3 v, float2 whiteBalance) { // General color grading //------------------------------------------------------------------------------ +using ColorTransform = float3(*)(float3); + +ColorTransform selectLinearToLogTransform(ColorGrading::ToneMapping toneMapping) { + switch (toneMapping) { + case ColorGrading::ToneMapping::ACES_LEGACY: + case ColorGrading::ToneMapping::ACES: + return linearAP1_to_ACEScct; + default: + return linear_to_LogC; + } +} + +ColorTransform selectLogToLinearTransform(ColorGrading::ToneMapping toneMapping) { + switch (toneMapping) { + case ColorGrading::ToneMapping::ACES_LEGACY: + case ColorGrading::ToneMapping::ACES: + return ACEScct_to_linearAP1; + default: + return LogC_to_linear; + } +} + mat3f selectColorGradingTransform(ColorGrading::ToneMapping toneMapping) { switch (toneMapping) { + case ColorGrading::ToneMapping::ACES_LEGACY: case ColorGrading::ToneMapping::ACES: return sRGB_to_AP1; default: @@ -155,6 +185,7 @@ mat3f selectColorGradingTransform(ColorGrading::ToneMapping toneMapping) { float3 selectLumaTransform(ColorGrading::ToneMapping toneMapping) { switch (toneMapping) { + case ColorGrading::ToneMapping::ACES_LEGACY: case ColorGrading::ToneMapping::ACES: return LUMA_AP1; default: @@ -185,16 +216,26 @@ inline constexpr float3 tonalRanges( return v * s * shadows + v * m * midtones + v * h * highlights; } +UTILS_ALWAYS_INLINE +inline constexpr float3 colorAdjustments(float3 v, float contrast, float saturation) { + // Matches contrast as applied in DaVinci Resolve + v = MIDDLE_GRAY_ACEScct + contrast * (v - MIDDLE_GRAY_ACEScct); + + // S-2016-001 uses Rec.709 luma coefficients for the ASC CDL, including saturation + float3 y = dot(v, LUMA_REC709); + return y + saturation * (v - y); +} + //------------------------------------------------------------------------------ // Tone mapping //------------------------------------------------------------------------------ -using ToneMapper = float3(*)(float3); - -ToneMapper selectToneMapping(ColorGrading::ToneMapping toneMapping) { +ColorTransform selectToneMapping(ColorGrading::ToneMapping toneMapping) { switch (toneMapping) { case ColorGrading::ToneMapping::LINEAR: return tonemap::Linear; + case ColorGrading::ToneMapping::ACES_LEGACY: + return tonemap::ACES_Legacy; case ColorGrading::ToneMapping::ACES: return tonemap::ACES; case ColorGrading::ToneMapping::FILMIC: @@ -214,7 +255,9 @@ ToneMapper selectToneMapping(ColorGrading::ToneMapping toneMapping) { struct Config { mat3f colorGradingTransform; float3 lumaTransform; - ToneMapper toneMapper; + ColorTransform linearToLogTransform; + ColorTransform logToLinearTransform; + ColorTransform toneMapper; }; FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { @@ -229,7 +272,9 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { Config config{ .colorGradingTransform = selectColorGradingTransform(builder->toneMapping), .lumaTransform = selectLumaTransform(builder->toneMapping), - .toneMapper = selectToneMapping(builder->toneMapping), + .linearToLogTransform = selectLinearToLogTransform(builder->toneMapping), + .logToLinearTransform = selectLogToLinearTransform(builder->toneMapping), + .toneMapper = selectToneMapping(builder->toneMapping) }; //auto now = std::chrono::steady_clock::now(); @@ -247,7 +292,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { float3 v = float3{r, g, b} * (1.0f / (LUT_DIMENSION - 1u)); // LogC encoding - v = lutToLinear(v); + v = LogC_to_linear(v); // White balance v = chromaticAdaptation(v, builder->whiteBalance); @@ -255,11 +300,6 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { // Convert to color grading color space v = config.colorGradingTransform * v; - // TODO: Should any of the color grading transforms be applied in ACEScc or - // ACEScct isntead of ACEScg? The primaries are the same (AP1) but - // ACEScc/cct use a log encoding which may be better suited to some kinds - // of transforms - // Channel mixer v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue); @@ -268,6 +308,15 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { builder->shadows, builder->midtones, builder->highlights, builder->tonalRanges); + // The adjustments below behave better in log space using the ACEScct + // color space. + v = config.linearToLogTransform(v); + + // Constrast and saturation + v = colorAdjustments(v, builder->contrast, builder->saturation); + + v = config.logToLinearTransform(v); + // Kill negative values before tone mapping v = max(v, 0.0f); @@ -277,7 +326,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { // Apply OECF // TODO: allow to customize the output color space, // here we assume we are in the sRGB gamut already - v = image::linearTosRGB(v); + v = OECF_sRGB(v); *p++ = half4{v, 0.0f}; } @@ -315,22 +364,4 @@ void FColorGrading::terminate(FEngine& engine) { driver.destroyTexture(mLutHandle); } -float3 FColorGrading::lutToLinear(float3 x) noexcept { - // Alexa LogC EI 1000 curve - const float ia = 1.0f / 5.555556f; - const float b = 0.047996f; - const float ic = 1.0f / 0.244161f; - const float d = 0.386036f; - return (pow(10.0f, (x - d) * ic) - b) * ia; -} - -float3 FColorGrading::linearToLut(float3 x) noexcept { - // Alexa LogC EI 1000 curve - const float a = 5.555556f; - const float b = 0.047996f; - const float c = 0.244161f; - const float d = 0.386036f; - return c * log10(a * x + b) + d; -} - } //namespace filament diff --git a/filament/src/ColorSpace.h b/filament/src/ColorSpace.h index 728f7fb25f..e7cea3fa50 100644 --- a/filament/src/ColorSpace.h +++ b/filament/src/ColorSpace.h @@ -23,13 +23,23 @@ #include #include +#include + namespace filament { using namespace math; +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + using xyY = float3; using XYZ = float3; +//------------------------------------------------------------------------------ +// Color space conversion matrices +//------------------------------------------------------------------------------ + constexpr mat3f XYZ_to_sRGB{ 3.2404542f, -0.9692660f, 0.0556434f, -1.5371385f, 1.8760108f, -0.2040259f, @@ -94,6 +104,10 @@ constexpr mat3f sRGB_to_LMS = XYZ_to_CIECAT02 * sRGB_to_XYZ; constexpr mat3f LMS_to_sRGB = XYZ_to_sRGB * CIECAT02_to_XYZ; +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + // Standard CIE 1931 2° illuminant D65, in xyY space constexpr float3 ILLUMINANT_D65_xyY{0.31271f, 0.32902f, 1.0f}; @@ -107,20 +121,99 @@ constexpr float3 LUMA_AP1{0.272229f, 0.674082f, 0.0536895f}; // RGB to luma coefficients for Rec.709, from sRGB_to_XYZ constexpr float3 LUMA_REC709{0.2126730f, 0.7151520f, 0.0721750f}; -inline constexpr XYZ xyY_to_XYZ(xyY v) { +constexpr float MIDDLE_GRAY_ACEScg = 0.18f; + +constexpr float MIDDLE_GRAY_ACEScct = 0.4135884f; + +//------------------------------------------------------------------------------ +// Chromaticity helpers +//------------------------------------------------------------------------------ + +// Returns the y chromaticity coordinate in xyY for an illuminant series D, +// given its x chromaticity coordinate. +inline constexpr float chromaticityCoordinateIlluminantD(float x) noexcept { + // See http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D + return 2.87f * x - 3.0f * x * x - 0.275f; +} + +//------------------------------------------------------------------------------ +// Color space conversions +//------------------------------------------------------------------------------ + +inline constexpr XYZ xyY_to_XYZ(xyY v) noexcept { const float a = v.z / max(v.y, 1e-5f); return XYZ{v.x * a, v.z, (1.0f - v.x - v.y) * a}; } -inline constexpr xyY XYZ_to_xyY(XYZ v) { +inline constexpr xyY XYZ_to_xyY(XYZ v) noexcept { return float3(v.xy / max(v.x + v.y + v.z, 1e-5f), v.y); } -// Returns the y chromaticity coordinate in xyY for an illuminant series D, -// given its x chromaticity coordinate. -inline constexpr float illuminantD_y(float x) { - // See http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D - return 2.87f * x - 3.0f * x * x - 0.275f; +//------------------------------------------------------------------------------ +// Conversion functions and encoding/decoding +//------------------------------------------------------------------------------ + +// Decodes a linear value from LogC using the Alexa LogC EI 1000 curve +inline float3 LogC_to_linear(float3 x) noexcept { + const float ia = 1.0f / 5.555556f; + const float b = 0.047996f; + const float ic = 1.0f / 0.244161f; + const float d = 0.386036f; + return (pow(10.0f, (x - d) * ic) - b) * ia; +} + +// Encodes a linear value in LogC using the Alexa LogC EI 1000 curve +inline float3 linear_to_LogC(float3 x) noexcept { + const float a = 5.555556f; + const float b = 0.047996f; + const float c = 0.244161f; + const float d = 0.386036f; + return c * log10(a * x + b) + d; +} + +inline float3 ACEScct_to_linearAP1(float3 x) noexcept { + constexpr float l = 1.467996312f; // (log2(65504) + 9.72) / 17.52 + for (size_t i = 0; i < 3; i++) { + if (x[i] <= 0.155251141552511f) { + x[i] = (x[i] - 0.0729055341958355f) / 10.5402377416545f; + } else if (x[i] < l) { + x[i] = pow(2.0f, x[i] * 17.52f - 9.72f); + } else { + x[i] = 65504.0f; + } + } + return x; +} + +inline float3 linearAP1_to_ACEScct(float3 x) noexcept { + for (size_t i = 0; i < 3; i++) { + x[i] = x[i] < 0.0078125f + ? 10.5402377416545f * x[i] + 0.0729055341958355f + : (log2(x[i]) + 9.72f) / 17.52f; + } + return x; +} + +inline float3 OECF_sRGB(float3 x) noexcept { + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float b = 12.92f; + constexpr float p = 1 / 2.4f; + for (size_t i = 0; i < 3; i++) { + x[i] = x[i] <= 0.0031308f ? x[i] * b : a1 * pow(x[i], p) - a; + } + return x; +} + +inline float3 EOCF_sRGB(float3 x) noexcept { + constexpr float a = 0.055f; + constexpr float a1 = 1.055f; + constexpr float b = 1.0f / 12.92f; + constexpr float p = 2.4f; + for (size_t i = 0; i < 3; i++) { + x[i] = x[i] <= 0.04045f ? x[i] * b : pow((x[i] + a) / a1, p); + } + return x; } } // namespace filament diff --git a/filament/src/ToneMapping.h b/filament/src/ToneMapping.h index 2dc36a8e9f..c4b750066a 100644 --- a/filament/src/ToneMapping.h +++ b/filament/src/ToneMapping.h @@ -38,14 +38,14 @@ namespace aces { inline float rgb_2_saturation(float3 rgb) { // Input: ACES // Output: OCES - const float TINY = 1e-5f; + constexpr float TINY = 1e-5f; float mi = min(rgb); float ma = max(rgb); return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2f); } inline float rgb_2_yc(float3 rgb) { - const float ycRadiusWeight = 1.75f; + constexpr float ycRadiusWeight = 1.75f; // Converts RGB to a luminance proxy, here called YC // YC is ~ Y + K * Chroma @@ -114,7 +114,7 @@ inline float center_hue(float hue, float centerH) { } inline float3 darkSurround_to_dimSurround(float3 linearCV) { - const float DIM_SURROUND_GAMMA = 0.9811f; + constexpr float DIM_SURROUND_GAMMA = 0.9811f; float3 XYZ = AP1_to_XYZ * linearCV; float3 xyY = XYZ_to_xyY(XYZ); @@ -127,24 +127,24 @@ inline float3 darkSurround_to_dimSurround(float3 linearCV) { } UTILS_ALWAYS_INLINE -inline float3 ACES(float3 color) { +inline float3 ACES(float3 color, float brightness) { // Some bits were removed to adapt to our desired output // Input: ACEScg (AP1) // Output: linear sRGB // "Glow" module constants - const float RRT_GLOW_GAIN = 0.05f; - const float RRT_GLOW_MID = 0.08f; + constexpr float RRT_GLOW_GAIN = 0.05f; + constexpr float RRT_GLOW_MID = 0.08f; // Red modifier constants - const float RRT_RED_SCALE = 0.82f; - const float RRT_RED_PIVOT = 0.03f; - const float RRT_RED_HUE = 0.0f; - const float RRT_RED_WIDTH = 135.0f; + constexpr float RRT_RED_SCALE = 0.82f; + constexpr float RRT_RED_PIVOT = 0.03f; + constexpr float RRT_RED_HUE = 0.0f; + constexpr float RRT_RED_WIDTH = 135.0f; // Desaturation contants - const float RRT_SAT_FACTOR = 0.96f; - const float ODT_SAT_FACTOR = 0.93f; + constexpr float RRT_SAT_FACTOR = 0.96f; + constexpr float ODT_SAT_FACTOR = 0.93f; float3 ap0 = AP1_to_AP0 * color; @@ -169,17 +169,17 @@ inline float3 ACES(float3 color) { // Global desaturation ap1 = mix(float3(dot(ap1, LUMA_AP1)), ap1, RRT_SAT_FACTOR); -#if defined(TONEMAP_ACES_MATCH_BRIGHTNESS) - ap1 *= 1.0f / 0.6f; -#endif + // 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. + ap1 *= brightness; // Fitting of RRT + ODT (RGB monitor 100 nits dim) from: // https://github.com/colour-science/colour-unity/blob/master/Assets/Colour/Notebooks/CIECAM02_Unity.ipynb - const float a = 2.785085f; - const float b = 0.107772f; - const float c = 2.936045f; - const float d = 0.887122f; - const float e = 0.806889f; + constexpr float a = 2.785085f; + constexpr float b = 0.107772f; + constexpr float c = 2.936045f; + constexpr float d = 0.887122f; + constexpr float e = 0.806889f; float3 rgbPost = (ap1 * (a * ap1 + b)) / (ap1 * (c * ap1 + d) + e); // Apply gamma adjustment to compensate for dim surround @@ -200,27 +200,30 @@ inline float3 ACES(float3 color) { namespace tonemap { -UTILS_ALWAYS_INLINE float3 Linear(float3 x) noexcept { return x; } float3 Reinhard(float3 x) noexcept { - return x / (1.0f + dot(x, float3{0.2126f, 0.7152f, 0.0722f})); + return x / (1.0f + dot(x, LUMA_REC709)); } float3 Filmic(float3 x) noexcept { // Narkowicz 2015, "ACES Filmic Tone Mapping Curve" - const float a = 2.51f; - const float b = 0.03f; - const float c = 2.43f; - const float d = 0.59f; - const float e = 0.14f; + constexpr float a = 2.51f; + constexpr float b = 0.03f; + constexpr float c = 2.43f; + constexpr float d = 0.59f; + constexpr float e = 0.14f; return (x * (a * x + b)) / (x * (c * x + d) + e); } float3 ACES(float3 x) noexcept { - return aces::ACES(x); + return aces::ACES(x, 1.0f); +} + +float3 ACES_Legacy(float3 x) noexcept { + return aces::ACES(x, 1.0f / 0.6f); } /** @@ -251,7 +254,7 @@ float3 ACES(float3 x) noexcept { float3 DisplayRange(float3 x) noexcept { // 16 debug colors + 1 duplicated at the end for easy indexing - const float3 debugColors[17] = { + constexpr float3 debugColors[17] = { {0.0, 0.0, 0.0}, // black {0.0, 0.0, 0.1647}, // darkest blue {0.0, 0.0, 0.3647}, // darker blue @@ -273,8 +276,9 @@ float3 DisplayRange(float3 x) 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(x, float3{0.2126f, 0.7152f, 0.0722f}) / 0.18f); + float v = log2(dot(x, LUMA_REC709) / 0.18f); v = clamp(v + 5.0f, 0.0f, 15.0f); + size_t index = size_t(v); return mix(debugColors[index], debugColors[index + 1], saturate(v - float(index))); } diff --git a/filament/src/details/ColorGrading.h b/filament/src/details/ColorGrading.h index 143d274171..0a9840562c 100644 --- a/filament/src/details/ColorGrading.h +++ b/filament/src/details/ColorGrading.h @@ -44,9 +44,6 @@ public: backend::TextureHandle getHwHandle() const noexcept { return mLutHandle; } private: - static inline math::float3 lutToLinear(math::float3 x) noexcept; - static inline math::float3 linearToLut(math::float3 x) noexcept; - backend::TextureHandle mLutHandle; }; diff --git a/libs/math/include/math/TVecHelpers.h b/libs/math/include/math/TVecHelpers.h index 1cd7f7765e..484eeaf618 100644 --- a/libs/math/include/math/TVecHelpers.h +++ b/libs/math/include/math/TVecHelpers.h @@ -491,6 +491,13 @@ private: return v; } + friend inline VECTOR MATH_PURE log2(VECTOR v) { + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::log2(v[i]); + } + return v; + } + friend inline constexpr VECTOR MATH_PURE saturate(const VECTOR& lv) { return clamp(lv, T(0), T(1)); } diff --git a/samples/material_sandbox.cpp b/samples/material_sandbox.cpp index 69d044b55c..2480f55365 100644 --- a/samples/material_sandbox.cpp +++ b/samples/material_sandbox.cpp @@ -586,9 +586,10 @@ static void gui(filament::Engine* engine, filament::View*) { } if (ImGui::CollapsingHeader("Color grading")) { + ImGui::Checkbox("Enabled##colorGradin", ¶ms.colorGrading); ImGui::Combo("Tone-mapping", reinterpret_cast(¶ms.colorGradingOptions.toneMapping), - "Linear\0ACES\0Filmic\0Reinhard\0Display Range\0\0"); + "Linear\0ACES (legacy)\0ACES\0Filmic\0Reinhard\0Display Range\0\0"); ImGui::Indent(); if (ImGui::CollapsingHeader("While balance")) { ImGui::SliderInt("Temperature", ¶ms.colorGradingOptions.temperature, -100, 100); @@ -612,6 +613,10 @@ static void gui(filament::Engine* engine, filament::View*) { ImGui::PlotLines("Mid-tones curve", g_rangePlot + 1024, 1024); ImGui::PlotLines("Highlights curve", g_rangePlot + 2048, 1024); } + if (ImGui::CollapsingHeader("Adjustments")) { + ImGui::SliderFloat("Contrast", ¶ms.colorGradingOptions.contrast, 0.0f, 2.0f); + ImGui::SliderFloat("Saturation", ¶ms.colorGradingOptions.saturation, 0.0f, 2.0f); + } ImGui::Unindent(); } @@ -726,27 +731,34 @@ static void preRender(filament::Engine* engine, filament::View* view, filament:: g_params.ssao ? View::AmbientOcclusion::SSAO : View::AmbientOcclusion::NONE); view->setAmbientOcclusionOptions(g_params.ssaoOptions); - if (memcmp(&g_params.colorGradingOptions, &g_lastColorGradingOptions, sizeof(ColorGradingOptions))) { - ColorGradingOptions& options = g_params.colorGradingOptions; - ColorGrading* colorGrading = ColorGrading::Builder() - .whiteBalance(options.temperature / 100.0f, options.tint / 100.0f) - .channelMixer(options.outRed, options.outGreen, options.outBlue) - .shadowsMidtonesHighlights( - Color::toLinear(options.shadows), - Color::toLinear(options.midtones), - Color::toLinear(options.highlights), - options.ranges - ) - .toneMapping(options.toneMapping) - .build(*engine); - view->setColorGrading(colorGrading); + if (g_params.colorGrading) { + if (memcmp(&g_params.colorGradingOptions, &g_lastColorGradingOptions, + sizeof(ColorGradingOptions))) { + ColorGradingOptions &options = g_params.colorGradingOptions; + ColorGrading *colorGrading = ColorGrading::Builder() + .whiteBalance(options.temperature / 100.0f, options.tint / 100.0f) + .channelMixer(options.outRed, options.outGreen, options.outBlue) + .shadowsMidtonesHighlights( + Color::toLinear(options.shadows), + Color::toLinear(options.midtones), + Color::toLinear(options.highlights), + options.ranges + ) + .contrast(options.contrast) + .saturation(options.saturation) + .toneMapping(options.toneMapping) + .build(*engine); - if (g_colorGrading) { - engine->destroy(g_colorGrading); + if (g_colorGrading) { + engine->destroy(g_colorGrading); + } + + g_colorGrading = colorGrading; + g_lastColorGradingOptions = options; } - - g_colorGrading = colorGrading; - g_lastColorGradingOptions = options; + view->setColorGrading(g_colorGrading); + } else { + view->setColorGrading(nullptr); } // Without an IBL, we must clear the swapchain to black before each frame. diff --git a/samples/material_sandbox.h b/samples/material_sandbox.h index 263ad125c0..7202a729a6 100644 --- a/samples/material_sandbox.h +++ b/samples/material_sandbox.h @@ -61,7 +61,7 @@ constexpr uint8_t BLENDING_SOLID_REFRACTION = 4; using namespace filament; struct ColorGradingOptions { - ColorGrading::ToneMapping toneMapping = ColorGrading::ToneMapping::ACES; + ColorGrading::ToneMapping toneMapping = ColorGrading::ToneMapping::ACES_LEGACY; int temperature = 0; int tint = 0; math::float3 outRed{1.0f, 0.0f, 0.0f}; @@ -71,6 +71,8 @@ struct ColorGradingOptions { math::float4 midtones{1.0f, 1.0f, 1.0f, 0.0f}; math::float4 highlights{1.0f, 1.0f, 1.0f, 0.0f}; math::float4 ranges{0.0f, 0.333f, 0.550f, 1.0f}; + float saturation = 1.0f; + float contrast = 1.0f; }; struct SandboxParameters { @@ -141,6 +143,7 @@ struct SandboxParameters { float cameraAperture = 16.0f; float cameraSpeed = 125.0f; float cameraISO = 100.0f; + bool colorGrading = true; ColorGradingOptions colorGradingOptions; };