From 4080a0a5d5a63a61f8da2852dfa66989bce8a8f5 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Wed, 14 Jul 2021 16:23:55 -0700 Subject: [PATCH] Small cleanup and improvements in ColorGrading (#4320) Saturation and vibrance were using the wrong luminance weights, and this change switches EVILS to HK weighted luminance weights to give better results. --- filament/src/ColorGrading.cpp | 33 +++++++++++++++++++---------- filament/src/ColorSpace.h | 7 ++++-- filament/src/PostProcessManager.cpp | 6 +++++- filament/src/ToneMapping.cpp | 12 +++++++---- libs/imageio/src/ImageDecoder.cpp | 3 ++- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/filament/src/ColorGrading.cpp b/filament/src/ColorGrading.cpp index 31cfe46507..d2dd509005 100644 --- a/filament/src/ColorGrading.cpp +++ b/filament/src/ColorGrading.cpp @@ -310,11 +310,14 @@ inline constexpr float3 channelMixer(float3 v, float3 r, float3 g, float3 b) { UTILS_ALWAYS_INLINE inline constexpr float3 tonalRanges( - float3 v, float3 luma, float3 shadows, float3 midtones, float3 highlights, float4 ranges) { + float3 v, float3 luminance, + float3 shadows, float3 midtones, float3 highlights, + float4 ranges +) { // See the Mathematica notebook at docs/math/Shadows Midtones Highlight.nb for // details on how the curves were designed. The default curve values are based // on the defaults from the "Log" color wheels in DaVinci Resolve. - float y = dot(v, luma); + float y = dot(v, luminance); // Shadows curve float s = 1.0f - smoothstep(ranges.x, ranges.y, y); @@ -345,16 +348,16 @@ inline constexpr float3 contrast(float3 v, float contrast) { } UTILS_ALWAYS_INLINE -inline constexpr float3 saturation(float3 v, float saturation) { - const float3 y = dot(v, LUMA_REC709); +inline constexpr float3 saturation(float3 v, float3 luminance, float saturation) { + const float3 y = dot(v, luminance); return y + saturation * (v - y); } UTILS_ALWAYS_INLINE -inline float3 vibrance(float3 v, float vibrance) { +inline float3 vibrance(float3 v, float3 luminance, float vibrance) { float r = v.r - max(v.g, v.b); float s = (vibrance - 1.0f) / (1.0f + std::exp(-r * 3.0f)) + 1.0f; - float3 l{(1.0f - s) * LUMA_REC709}; + float3 l{(1.0f - s) * luminance}; return float3{ dot(v, l + float3{s, 0.0f, 0.0f}), dot(v, l + float3{0.0f, s, 0.0f}), @@ -515,7 +518,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { half4* UTILS_RESTRICT p = (half4*) data + b * config.lutDimension * config.lutDimension; for (size_t g = 0; g < config.lutDimension; g++) { for (size_t r = 0; r < config.lutDimension; r++) { - float3 v = float3{ r, g, b } * (1.0f / float(config.lutDimension - 1u)); + float3 v = float3{r, g, b} * (1.0f / float(config.lutDimension - 1u)); // LogC encoding v = LogC_to_linear(v); @@ -557,10 +560,10 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { v = config.logToLinearTransform(v); // Vibrance in linear space - v = vibrance(v, builder->vibrance); + v = vibrance(v, config.luminanceTransform, builder->vibrance); // Saturation in linear space - v = saturation(v, builder->saturation); + v = saturation(v, config.luminanceTransform, builder->saturation); // Kill negative values before tone mapping v = max(v, 0.0f); @@ -615,8 +618,16 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { //std::chrono::duration duration = std::chrono::steady_clock::now() - now; //slog.d << "LUT generation time: " << duration.count() << " ms" << io::endl; - mLutHandle = driver.createTexture(SamplerType::SAMPLER_3D, 1, textureFormat, 1, - c.lutDimension, c.lutDimension, c.lutDimension, TextureUsage::DEFAULT); + mLutHandle = driver.createTexture( + SamplerType::SAMPLER_3D, + 1, + textureFormat, + 1, + c.lutDimension, + c.lutDimension, + c.lutDimension, + TextureUsage::DEFAULT + ); if (converted) { free(data); diff --git a/filament/src/ColorSpace.h b/filament/src/ColorSpace.h index f4bf37f4d5..d0a54f39a7 100644 --- a/filament/src/ColorSpace.h +++ b/filament/src/ColorSpace.h @@ -131,12 +131,15 @@ constexpr float3 ILLUMINANT_D65_xyY{0.31271f, 0.32902f, 1.0f}; // Result of: XYZ_to_CIECAT02 * xyY_to_XYZ(ILLUMINANT_D65_xyY); constexpr float3 ILLUMINANT_D65_LMS{0.949237f, 1.03542f, 1.08728f}; -// RGB to luma coefficients for ACEScg (AP1), from AP1_to_XYZ +// RGB to luminance coefficients for ACEScg (AP1), from AP1_to_XYZ constexpr float3 LUMA_AP1{0.272229f, 0.674082f, 0.0536895f}; -// RGB to luma coefficients for Rec.709, from sRGB_to_XYZ +// RGB to luminance coefficients for Rec.709, from sRGB_to_XYZ constexpr float3 LUMA_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 float MIDDLE_GRAY_ACEScg = 0.18f; constexpr float MIDDLE_GRAY_ACEScct = 0.4135884f; diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 4444b3e773..115e42e5b9 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -1795,7 +1795,11 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver, FMaterialInstance* mi = material.getMaterialInstance(); mi->setParameter("lut", colorGrading->getHwHandle(), { .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR + .filterMin = SamplerMinFilter::LINEAR, + .wrapS = SamplerWrapMode::CLAMP_TO_EDGE, + .wrapT = SamplerWrapMode::CLAMP_TO_EDGE, + .wrapR = SamplerWrapMode::CLAMP_TO_EDGE, + .anisotropyLog2 = 0 }); const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine()); diff --git a/filament/src/ToneMapping.cpp b/filament/src/ToneMapping.cpp index a96c18ec10..34610188a6 100644 --- a/filament/src/ToneMapping.cpp +++ b/filament/src/ToneMapping.cpp @@ -210,6 +210,10 @@ float genericTonemap(float x, float contrast, float shoulder, return saturate(xc / (std::pow(xc, shoulder) * b + c)); } +constexpr float luminance(float3 v) noexcept { + return dot(v, LUMA_HK_REC709); +} + float3 EVILS(float3 x) noexcept { // Troy Sobotka, 2021, "EVILS - Exposure Value Invariant Luminance Scaling" // https://colab.research.google.com/drive/1iPJzNNKR7PynFmsqSnQm3bCZmQ3CvAJ-#scrollTo=psU43hb-BLzB @@ -225,16 +229,16 @@ float3 EVILS(float3 x) noexcept { constexpr float hdrMax = 64.0f; // We assume an input compatible with Rec.709 luminance weights - float luminanceIn = dot(x, LUMA_REC709); + float luminanceIn = luminance(x); float luminanceOut = genericTonemap(luminanceIn, contrast, shoulder, midGreyIn, midGreyOut, hdrMax); float peak = max(x); float3 chromaRatio = max(x / peak, 0.0f); - float chromaRatioLuminance = dot(chromaRatio, LUMA_REC709); + float chromaRatioLuminance = luminance(chromaRatio); float3 maxReserves = 1.0f - chromaRatio; - float maxReservesLuminance = dot(maxReserves, LUMA_REC709); + float maxReservesLuminance = luminance(maxReserves); float luminanceDifference = std::max(luminanceOut - chromaRatioLuminance, 0.0f); float scaledLuminanceDifference = @@ -243,7 +247,7 @@ float3 EVILS(float3 x) noexcept { float chromaScale = (luminanceOut - luminanceDifference) / std::max(chromaRatioLuminance, std::numeric_limits::min()); - return saturate(chromaScale * chromaRatio + scaledLuminanceDifference * maxReserves); + return chromaScale * chromaRatio + scaledLuminanceDifference * maxReserves; } float3 DisplayRange(float3 x) noexcept { diff --git a/libs/imageio/src/ImageDecoder.cpp b/libs/imageio/src/ImageDecoder.cpp index 373c20166b..1248cf04c9 100644 --- a/libs/imageio/src/ImageDecoder.cpp +++ b/libs/imageio/src/ImageDecoder.cpp @@ -473,7 +473,8 @@ LinearImage EXRDecoder::decode() { size_t i = 0; for (uint32_t y = 0; y < height; y++) { for (uint32_t x = 0; x < width; x++) { - filament::math::float3& pixel = *reinterpret_cast< filament::math::float3*>(image.getPixelRef(x, y)); + filament::math::float3& pixel = + *reinterpret_cast< filament::math::float3*>(image.getPixelRef(x, y)); pixel.r = rgba[i++]; pixel.g = rgba[i++]; pixel.b = rgba[i++];