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.
This commit is contained in:
+1
-1
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -18,22 +18,19 @@
|
||||
|
||||
#include "ColorSpace.h"
|
||||
|
||||
#include <image/ColorTransform.h>
|
||||
|
||||
#include <math/mat3.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <math/scalar.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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 <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <image/ColorTransform.h>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
@@ -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
|
||||
|
||||
+100
-7
@@ -23,13 +23,23 @@
|
||||
#include <math/vec3.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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
|
||||
|
||||
+34
-30
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -491,6 +491,13 @@ private:
|
||||
return v;
|
||||
}
|
||||
|
||||
friend inline VECTOR<T> MATH_PURE log2(VECTOR<T> v) {
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
v[i] = std::log2(v[i]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
friend inline constexpr VECTOR<T> MATH_PURE saturate(const VECTOR<T>& lv) {
|
||||
return clamp(lv, T(0), T(1));
|
||||
}
|
||||
|
||||
@@ -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<int*>(¶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.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user