Add new color grading feature: shadows/mid-tones/highlights (#2623)
* Add new color grading feature: shadows/mid-tones/highlights This feature can be used to color correct specific tonal ranges. The tonal zones can be precisely controlled.
This commit is contained in:
@@ -13,7 +13,8 @@ A new header is inserted each time a *tag* is created.
|
||||
- SSAO now has an optional high(er) quality upsampler.
|
||||
- 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.
|
||||
- Color grading capabilities per View: white balance (temperature/tint), channel mixer,
|
||||
tonal ranges (shadows/mid-tones/highlights).
|
||||
- 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.
|
||||
|
||||
|
||||
197
docs/math/Shadow Midtones Highglits.nb
Normal file
197
docs/math/Shadow Midtones Highglits.nb
Normal file
@@ -0,0 +1,197 @@
|
||||
(* Content-type: application/vnd.wolfram.mathematica *)
|
||||
|
||||
(*** Wolfram Notebook File ***)
|
||||
(* http://www.wolfram.com/nb *)
|
||||
|
||||
(* CreatedBy='Mathematica 12.1' *)
|
||||
|
||||
(*CacheID: 234*)
|
||||
(* Internal cache information:
|
||||
NotebookFileLineBreakTest
|
||||
NotebookFileLineBreakTest
|
||||
NotebookDataPosition[ 158, 7]
|
||||
NotebookDataLength[ 7662, 189]
|
||||
NotebookOptionsPosition[ 6996, 170]
|
||||
NotebookOutlinePosition[ 7393, 186]
|
||||
CellTagsIndexPosition[ 7350, 183]
|
||||
WindowFrame->Normal*)
|
||||
|
||||
(* Beginning of Notebook Content *)
|
||||
Notebook[{
|
||||
Cell[TextData[{
|
||||
StyleBox["Shadows/mid-tones/highlights ranges\n", "Subsection"],
|
||||
"This notebook explores how to define the shadows/mid-tones/highlights \
|
||||
ranges for color grading. This implementation is based loosely on the log mod \
|
||||
of the color wheels palette of DaVinci Resolve (see \
|
||||
https://documents.blackmagicdesign.com/UserManuals/DaVinci_Resolve_14_\
|
||||
Reference_Manual.pdf for details)."
|
||||
}], "Text",
|
||||
CellChangeTimes->{{3.7996982241825457`*^9, 3.7996982477885103`*^9}, {
|
||||
3.800036555016198*^9,
|
||||
3.8000366861328773`*^9}},ExpressionUUID->"f0eee150-e72e-4950-83b3-\
|
||||
9d4eb478523d"],
|
||||
|
||||
Cell[BoxData[
|
||||
RowBox[{
|
||||
RowBox[{"smoothStep", "[",
|
||||
RowBox[{"e0_", ",", " ", "e1_", ",", " ", "x_"}], "]"}], ":=",
|
||||
RowBox[{"Module", "[",
|
||||
RowBox[{
|
||||
RowBox[{"{", "t", "}"}], ",", "\[IndentingNewLine]",
|
||||
RowBox[{
|
||||
RowBox[{"t", "=",
|
||||
RowBox[{"Clip", "[",
|
||||
RowBox[{
|
||||
RowBox[{
|
||||
RowBox[{"(",
|
||||
RowBox[{"x", "-", "e0"}], ")"}], "/",
|
||||
RowBox[{"(",
|
||||
RowBox[{"e1", "-", "e0"}], ")"}]}], ",",
|
||||
RowBox[{"{",
|
||||
RowBox[{"0", ",", "1"}], "}"}]}], "]"}]}], ";",
|
||||
"\[IndentingNewLine]",
|
||||
RowBox[{
|
||||
RowBox[{"t", "^", "2"}], "*",
|
||||
RowBox[{"(",
|
||||
RowBox[{"3", " ", "-", " ",
|
||||
RowBox[{"2", " ", "*", " ", "t"}]}], ")"}]}]}]}],
|
||||
"\[IndentingNewLine]", "]"}]}]], "Input",
|
||||
CellChangeTimes->{{3.800035828984764*^9, 3.800035897263982*^9}, {
|
||||
3.800035928251821*^9, 3.800035935254816*^9}},
|
||||
CellLabel->"In[5]:=",ExpressionUUID->"b2cca214-3159-4432-b089-098eda680ce0"],
|
||||
|
||||
Cell[CellGroupData[{
|
||||
|
||||
Cell[BoxData[
|
||||
RowBox[{"Manipulate", "[", "\[IndentingNewLine]",
|
||||
RowBox[{
|
||||
RowBox[{"Plot", "[",
|
||||
RowBox[{
|
||||
RowBox[{"{",
|
||||
RowBox[{
|
||||
RowBox[{"1", "-",
|
||||
RowBox[{"smoothStep", "[",
|
||||
RowBox[{"shadow", ",", "dark", ",", "x"}], "]"}]}], ",",
|
||||
RowBox[{"1", "-",
|
||||
RowBox[{"(",
|
||||
RowBox[{"1", "-",
|
||||
RowBox[{"smoothStep", "[",
|
||||
RowBox[{"shadow", ",", "dark", ",", "x"}], "]"}]}], ")"}], "-",
|
||||
RowBox[{"smoothStep", "[",
|
||||
RowBox[{"light", ",", "highlight", ",", "x"}], "]"}]}], ",",
|
||||
RowBox[{"smoothStep", "[",
|
||||
RowBox[{"light", ",", "highlight", ",", "x"}], "]"}]}], "}"}], ",",
|
||||
RowBox[{"{",
|
||||
RowBox[{"x", ",", "0", ",", "1"}], "}"}], ",",
|
||||
RowBox[{"ImageSize", "\[Rule]", "Large"}], ",",
|
||||
RowBox[{"Filling", "->", "Axis"}]}], "]"}], ",", "\[IndentingNewLine]",
|
||||
RowBox[{"{",
|
||||
RowBox[{
|
||||
RowBox[{"{",
|
||||
RowBox[{"shadow", ",", "0.0"}], "}"}], ",", "0.0", ",", "0.9999"}],
|
||||
"}"}], ",", "\[IndentingNewLine]",
|
||||
RowBox[{"{",
|
||||
RowBox[{
|
||||
RowBox[{"{",
|
||||
RowBox[{"dark", ",", "0.333"}], "}"}], ",", "0.0001", ",", "0.9999"}],
|
||||
"}"}], ",", "\[IndentingNewLine]",
|
||||
RowBox[{"{",
|
||||
RowBox[{
|
||||
RowBox[{"{",
|
||||
RowBox[{"light", ",", "0.550"}], "}"}], ",", "0.0001", ",", "0.9999"}],
|
||||
"}"}], ",", "\[IndentingNewLine]",
|
||||
RowBox[{"{",
|
||||
RowBox[{
|
||||
RowBox[{"{",
|
||||
RowBox[{"highlight", ",", "1.0"}], "}"}], ",", "0.0001", ",", "1.0"}],
|
||||
"}"}]}], "\[IndentingNewLine]", "]"}]], "Input",
|
||||
CellChangeTimes->{{3.800035899978855*^9, 3.800036212318406*^9}, {
|
||||
3.800036351085218*^9, 3.800036352868197*^9}, {3.8000369201535683`*^9,
|
||||
3.800036935029261*^9}, {3.8000372433968763`*^9, 3.800037243786892*^9}, {
|
||||
3.800037310348159*^9, 3.800037376071424*^9}},
|
||||
CellLabel->"In[28]:=",ExpressionUUID->"0a516cc2-23b9-453a-a83b-dc42b3dce7ac"],
|
||||
|
||||
Cell[BoxData[
|
||||
TagBox[
|
||||
StyleBox[
|
||||
DynamicModuleBox[{$CellContext`dark$$ = 0.333, $CellContext`highlight$$ =
|
||||
1., $CellContext`light$$ = 0.55, $CellContext`shadow$$ = 0.,
|
||||
Typeset`show$$ = True, Typeset`bookmarkList$$ = {},
|
||||
Typeset`bookmarkMode$$ = "Menu", Typeset`animator$$, Typeset`animvar$$ =
|
||||
1, Typeset`name$$ = "\"untitled\"", Typeset`specs$$ = {{{
|
||||
Hold[$CellContext`shadow$$], 0.}, 0., 0.9999}, {{
|
||||
Hold[$CellContext`dark$$], 0.333}, 0.0001, 0.9999}, {{
|
||||
Hold[$CellContext`light$$], 0.55}, 0.0001, 0.9999}, {{
|
||||
Hold[$CellContext`highlight$$], 1.}, 0.0001, 1.}}, Typeset`size$$ = {
|
||||
576., {175., 179.}}, Typeset`update$$ = 0, Typeset`initDone$$,
|
||||
Typeset`skipInitDone$$ = True},
|
||||
DynamicBox[Manipulate`ManipulateBoxes[
|
||||
1, StandardForm,
|
||||
"Variables" :> {$CellContext`dark$$ = 0.333, $CellContext`highlight$$ =
|
||||
1., $CellContext`light$$ = 0.55, $CellContext`shadow$$ = 0.},
|
||||
"ControllerVariables" :> {},
|
||||
"OtherVariables" :> {
|
||||
Typeset`show$$, Typeset`bookmarkList$$, Typeset`bookmarkMode$$,
|
||||
Typeset`animator$$, Typeset`animvar$$, Typeset`name$$,
|
||||
Typeset`specs$$, Typeset`size$$, Typeset`update$$, Typeset`initDone$$,
|
||||
Typeset`skipInitDone$$}, "Body" :>
|
||||
Plot[{1 - $CellContext`smoothStep[$CellContext`shadow$$, \
|
||||
$CellContext`dark$$, $CellContext`x], 1 - (
|
||||
1 - $CellContext`smoothStep[$CellContext`shadow$$, \
|
||||
$CellContext`dark$$, $CellContext`x]) - \
|
||||
$CellContext`smoothStep[$CellContext`light$$, $CellContext`highlight$$, \
|
||||
$CellContext`x],
|
||||
$CellContext`smoothStep[$CellContext`light$$, \
|
||||
$CellContext`highlight$$, $CellContext`x]}, {$CellContext`x, 0, 1}, ImageSize ->
|
||||
Large, Filling -> Axis],
|
||||
"Specifications" :> {{{$CellContext`shadow$$, 0.}, 0.,
|
||||
0.9999}, {{$CellContext`dark$$, 0.333}, 0.0001,
|
||||
0.9999}, {{$CellContext`light$$, 0.55}, 0.0001,
|
||||
0.9999}, {{$CellContext`highlight$$, 1.}, 0.0001, 1.}},
|
||||
"Options" :> {}, "DefaultOptions" :> {}],
|
||||
ImageSizeCache->{621., {262., 268.}},
|
||||
SingleEvaluation->True],
|
||||
Deinitialization:>None,
|
||||
DynamicModuleValues:>{},
|
||||
SynchronousInitialization->True,
|
||||
UndoTrackedVariables:>{Typeset`show$$, Typeset`bookmarkMode$$},
|
||||
UnsavedVariables:>{Typeset`initDone$$},
|
||||
UntrackedVariables:>{Typeset`size$$}], "Manipulate",
|
||||
Deployed->True,
|
||||
StripOnInput->False],
|
||||
Manipulate`InterpretManipulate[1]]], "Output",
|
||||
CellChangeTimes->{{3.800035909887515*^9, 3.800035924906485*^9}, {
|
||||
3.800035963413073*^9, 3.800036037313202*^9}, {3.800036095635211*^9,
|
||||
3.800036213562434*^9}, {3.8000363288112383`*^9, 3.80003635325991*^9},
|
||||
3.8000364348959017`*^9, 3.8000365413816338`*^9, 3.800036962971013*^9, {
|
||||
3.800037354906559*^9, 3.800037376658716*^9}, 3.800037480044058*^9},
|
||||
CellLabel->"Out[28]=",ExpressionUUID->"3b35b3b2-6445-4c38-9b7d-bc731c2eccce"]
|
||||
}, Open ]]
|
||||
},
|
||||
WindowSize->{983, 1064},
|
||||
WindowMargins->{{584, Automatic}, {232, Automatic}},
|
||||
FrontEndVersion->"12.1 for Mac OS X x86 (64-bit) (March 18, 2020)",
|
||||
StyleDefinitions->"Default.nb",
|
||||
ExpressionUUID->"ed9be208-fe57-4431-8fc3-063e24460812"
|
||||
]
|
||||
(* End of Notebook Content *)
|
||||
|
||||
(* Internal cache information *)
|
||||
(*CellTagsOutline
|
||||
CellTagsIndex->{}
|
||||
*)
|
||||
(*CellTagsIndex
|
||||
CellTagsIndex->{}
|
||||
*)
|
||||
(*NotebookFileOutline
|
||||
Notebook[{
|
||||
Cell[558, 20, 590, 11, 110, "Text",ExpressionUUID->"f0eee150-e72e-4950-83b3-9d4eb478523d"],
|
||||
Cell[1151, 33, 974, 27, 94, "Input",ExpressionUUID->"b2cca214-3159-4432-b089-098eda680ce0"],
|
||||
Cell[CellGroupData[{
|
||||
Cell[2150, 64, 1912, 47, 178, "Input",ExpressionUUID->"0a516cc2-23b9-453a-a83b-dc42b3dce7ac"],
|
||||
Cell[4065, 113, 2915, 54, 549, "Output",ExpressionUUID->"3b35b3b2-6445-4c38-9b7d-bc731c2eccce"]
|
||||
}, Open ]]
|
||||
}
|
||||
]
|
||||
*)
|
||||
|
||||
@@ -60,13 +60,24 @@ class FColorGrading;
|
||||
* 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on
|
||||
* the CPU.
|
||||
*
|
||||
* Ordering
|
||||
* ========
|
||||
*
|
||||
* The various transforms held by ColorGrading are applied in the following order:
|
||||
* - White balance
|
||||
* - Channel mixer
|
||||
* - Shadows/mid-tones/highlights
|
||||
* - Tone mapping
|
||||
*
|
||||
* Defaults
|
||||
* ========
|
||||
*
|
||||
* Here are the default color grading options:
|
||||
* - Tone mapping: ACES
|
||||
* - White balance: temperature 0, and tint 0
|
||||
* - 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
|
||||
*
|
||||
* @see View
|
||||
*/
|
||||
@@ -118,8 +129,8 @@ public:
|
||||
* - Temperature, to modify the color temperature. This value will modify the colors
|
||||
* on a blue/yellow axis. Lower values apply a cool color temperature, and higher
|
||||
* values apply a warm color temperature. The lowest value, -1.0f, is equivalent to
|
||||
* a temperature of 2,000K. The highest value, 1.0f, is equivalent to a temperature
|
||||
* of 50,000K.
|
||||
* a temperature of 50,000K. The highest value, 1.0f, is equivalent to a temperature
|
||||
* of 2,000K.
|
||||
* - Tint, to modify the colors on a green/magenta axis. The lowest value, -1.0f, will
|
||||
* apply a strong green cast, and the highest value, 1.0f, will apply a strong magenta
|
||||
* cast.
|
||||
@@ -164,6 +175,33 @@ public:
|
||||
Builder& channelMixer(
|
||||
math::float3 outRed, math::float3 outGreen, math::float3 outBlue) noexcept;
|
||||
|
||||
/**
|
||||
* Adjusts the colors separately in 3 distinct tonal ranges or zones: shadows, mid-tones,
|
||||
* and highlights.
|
||||
*
|
||||
* The tonal zones are by the ranges parameter: the x and y components define the beginning
|
||||
* and end of the transition from shadows to mid-tones, and the z and w components define
|
||||
* the beginning and end of the transition from mid-tones to highlights.
|
||||
*
|
||||
* A smooth transition is applied between the zones which means for instance that the
|
||||
* correction color of the shadows range will partially apply to the mid-tones, and the
|
||||
* other way around. This ensure smooth visual transitions in the final image.
|
||||
*
|
||||
* Each correction color is defined as a linear RGB color and a weight. The weight is a
|
||||
* value (which may be positive or negative) that is added to the linear RGB color before
|
||||
* mixing. This can be used to darken or brighten the selected tonal range.
|
||||
*
|
||||
* @param shadows Linear RGB color (.rgb) and weight (.w) to apply to the shadows
|
||||
* @param midtones Linear RGB color (.rgb) and weight (.w) to apply to the mid-tones
|
||||
* @param highlights Linear RGB color (.rgb) and weight (.w) to apply to the highlights
|
||||
* @param ranges Range of the shadows (x and y), and range of the highlights (z and w)
|
||||
*
|
||||
* @return This Builder, for chaining calls
|
||||
*/
|
||||
Builder& shadowsMidtonesHighlights(
|
||||
math::float4 shadows, math::float4 midtones, math::float4 highlights,
|
||||
math::float4 ranges = math::float4{0.0f, 0.333f, 0.550f, 1.0f}) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the ColorGrading object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <image/ColorTransform.h>
|
||||
|
||||
@@ -55,6 +56,10 @@ struct ColorGrading::BuilderDetails {
|
||||
float3 outRed = {1.0f, 0.0f, 0.0f};
|
||||
float3 outGreen = {0.0f, 1.0f, 0.0f};
|
||||
float3 outBlue = {0.0f, 0.0f, 1.0f};
|
||||
float3 shadows = {1.0f, 1.0f, 1.0f};
|
||||
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
|
||||
};
|
||||
|
||||
using BuilderType = ColorGrading;
|
||||
@@ -86,6 +91,21 @@ ColorGrading::Builder& ColorGrading::Builder::channelMixer(
|
||||
return *this;
|
||||
}
|
||||
|
||||
ColorGrading::Builder& ColorGrading::Builder::shadowsMidtonesHighlights(
|
||||
float4 shadows, float4 midtones, float4 highlights, float4 ranges) noexcept {
|
||||
mImpl->shadows = max(shadows.rgb + shadows.w, 0.0f);
|
||||
mImpl->midtones = max(midtones.rgb + midtones.w, 0.0f);
|
||||
mImpl->highlights = max(highlights.rgb + highlights.w, 0.0f);
|
||||
|
||||
ranges.x = saturate(ranges.x); // shadows
|
||||
ranges.w = saturate(ranges.w); // highlights
|
||||
ranges.y = clamp(ranges.y, ranges.x + 1e-5f, ranges.z - 1e-5f); // darks
|
||||
ranges.z = clamp(ranges.z, ranges.x + 1e-5f, ranges.z - 1e-5f); // lights
|
||||
mImpl->tonalRanges = ranges;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ColorGrading* ColorGrading::Builder::build(Engine& engine) {
|
||||
return upcast(engine).createColorGrading(*this);
|
||||
}
|
||||
@@ -131,7 +151,15 @@ mat3f selectColorGradingTransform(ColorGrading::ToneMapping toneMapping) {
|
||||
default:
|
||||
return mat3f{};
|
||||
}
|
||||
return mat3f{};
|
||||
}
|
||||
|
||||
float3 selectLumaTransform(ColorGrading::ToneMapping toneMapping) {
|
||||
switch (toneMapping) {
|
||||
case ColorGrading::ToneMapping::ACES:
|
||||
return LUMA_AP1;
|
||||
default:
|
||||
return LUMA_REC709;
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_ALWAYS_INLINE
|
||||
@@ -139,6 +167,24 @@ inline constexpr float3 channelMixer(float3 v, float3 r, float3 g, float3 b) {
|
||||
return {dot(v, r), dot(v, g), dot(v, b)};
|
||||
}
|
||||
|
||||
UTILS_ALWAYS_INLINE
|
||||
inline constexpr float3 tonalRanges(
|
||||
float3 v, float3 luma, 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);
|
||||
|
||||
// Shadows curve
|
||||
float s = 1.0f - smoothstep(ranges.x, ranges.y, y);
|
||||
// Highlights curve
|
||||
float h = smoothstep(ranges.z, ranges.w, y);
|
||||
// Mid-tones curves
|
||||
float m = 1.0f - s - h;
|
||||
|
||||
return v * s * shadows + v * m * midtones + v * h * highlights;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tone mapping
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -167,6 +213,7 @@ ToneMapper selectToneMapping(ColorGrading::ToneMapping toneMapping) {
|
||||
|
||||
struct Config {
|
||||
mat3f colorGradingTransform;
|
||||
float3 lumaTransform;
|
||||
ToneMapper toneMapper;
|
||||
};
|
||||
|
||||
@@ -181,6 +228,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
|
||||
Config config{
|
||||
.colorGradingTransform = selectColorGradingTransform(builder->toneMapping),
|
||||
.lumaTransform = selectLumaTransform(builder->toneMapping),
|
||||
.toneMapper = selectToneMapping(builder->toneMapping),
|
||||
};
|
||||
|
||||
@@ -196,7 +244,7 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
half4* UTILS_RESTRICT p = (half4*) data + b * LUT_DIMENSION * LUT_DIMENSION;
|
||||
for (size_t g = 0; g < LUT_DIMENSION; g++) {
|
||||
for (size_t r = 0; r < LUT_DIMENSION; r++) {
|
||||
float3 v = float3{ r, g, b } * (1.0f / (LUT_DIMENSION - 1u));
|
||||
float3 v = float3{r, g, b} * (1.0f / (LUT_DIMENSION - 1u));
|
||||
|
||||
// LogC encoding
|
||||
v = lutToLinear(v);
|
||||
@@ -207,9 +255,19 @@ 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);
|
||||
|
||||
// Shadows/mid-tones/highlights
|
||||
v = tonalRanges(v, config.lumaTransform,
|
||||
builder->shadows, builder->midtones, builder->highlights,
|
||||
builder->tonalRanges);
|
||||
|
||||
// Kill negative values before tone mapping
|
||||
v = max(v, 0.0f);
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@ constexpr mat3f sRGB_to_AP1{
|
||||
0.04737f, 0.01345f, 0.86961f
|
||||
};
|
||||
|
||||
constexpr mat3f sRGB_to_LMS = XYZ_to_CIECAT02 * sRGB_to_XYZ;
|
||||
|
||||
constexpr mat3f LMS_to_sRGB = XYZ_to_sRGB * CIECAT02_to_XYZ;
|
||||
|
||||
// Standard CIE 1931 2° illuminant D65, in xyY space
|
||||
constexpr float3 ILLUMINANT_D65_xyY{0.31271f, 0.32902f, 1.0f};
|
||||
|
||||
@@ -97,9 +101,11 @@ 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};
|
||||
|
||||
constexpr mat3f sRGB_to_LMS = XYZ_to_CIECAT02 * sRGB_to_XYZ;
|
||||
// RGB to luma coefficients for ACEScg (AP1), from AP1_to_XYZ
|
||||
constexpr float3 LUMA_AP1{0.272229f, 0.674082f, 0.0536895f};
|
||||
|
||||
constexpr mat3f LMS_to_sRGB = XYZ_to_sRGB * CIECAT02_to_XYZ;
|
||||
// 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) {
|
||||
const float a = v.z / max(v.y, 1e-5f);
|
||||
|
||||
@@ -164,12 +164,10 @@ inline float3 ACES(float3 color) {
|
||||
ap0.r += hueWeight * saturation * (RRT_RED_PIVOT - ap0.r) * (1.0f - RRT_RED_SCALE);
|
||||
|
||||
// ACES to RGB rendering space
|
||||
float3 ap1 = clamp(AP0_to_AP1 * ap0, 0.0f, (float)std::numeric_limits<math::half>::max());
|
||||
float3 ap1 = clamp(AP0_to_AP1 * ap0, 0.0f, (float) std::numeric_limits<math::half>::max());
|
||||
|
||||
// Global desaturation
|
||||
const float3 AP1_RGB2Y{ 0.272229f, 0.674082f, 0.0536895f };
|
||||
|
||||
ap1 = mix(float3(dot(ap1, AP1_RGB2Y)), ap1, RRT_SAT_FACTOR);
|
||||
ap1 = mix(float3(dot(ap1, LUMA_AP1)), ap1, RRT_SAT_FACTOR);
|
||||
|
||||
#if defined(TONEMAP_ACES_MATCH_BRIGHTNESS)
|
||||
ap1 *= 1.0f / 0.6f;
|
||||
@@ -188,7 +186,7 @@ inline float3 ACES(float3 color) {
|
||||
float3 linearCV = darkSurround_to_dimSurround(rgbPost);
|
||||
|
||||
// Apply desaturation to compensate for luminance difference
|
||||
linearCV = mix(float3(dot(linearCV, AP1_RGB2Y)), linearCV, ODT_SAT_FACTOR);
|
||||
linearCV = mix(float3(dot(linearCV, LUMA_AP1)), linearCV, ODT_SAT_FACTOR);
|
||||
|
||||
// Convert to display primary encoding (Rec.709 primaries, D65 white point)
|
||||
return AP1_to_sRGB * linearCV;
|
||||
|
||||
@@ -71,6 +71,7 @@ static ColorGrading* g_colorGrading = nullptr;
|
||||
static Config g_config;
|
||||
static bool g_shadowPlane = false;
|
||||
static bool g_singleMode = false;
|
||||
static float g_rangePlot[1024 * 3];
|
||||
|
||||
static void printUsage(char* name) {
|
||||
std::string exec_name(Path(name).getName());
|
||||
@@ -417,6 +418,18 @@ static filament::MaterialInstance* updateInstances(
|
||||
return materialInstance;
|
||||
}
|
||||
|
||||
static void computeRangePlot(SandboxParameters ¶meters) {
|
||||
const ColorGradingOptions& options = parameters.colorGradingOptions;
|
||||
for (size_t i = 0; i < 1024; i++) {
|
||||
float x = i / 1024.0f;
|
||||
float s = 1.0f - smoothstep(options.ranges.x, options.ranges.y, x);
|
||||
float h = smoothstep(options.ranges.z, options.ranges.w, x);
|
||||
g_rangePlot[i] = s;
|
||||
g_rangePlot[1024 + i] = 1.0f - s - h;
|
||||
g_rangePlot[2048 + i] = h;
|
||||
}
|
||||
}
|
||||
|
||||
static void gui(filament::Engine* engine, filament::View*) {
|
||||
auto& params = g_params;
|
||||
ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
|
||||
@@ -576,11 +589,30 @@ static void gui(filament::Engine* engine, filament::View*) {
|
||||
ImGui::Combo("Tone-mapping",
|
||||
reinterpret_cast<int*>(¶ms.colorGradingOptions.toneMapping),
|
||||
"Linear\0ACES\0Filmic\0Reinhard\0Display Range\0\0");
|
||||
ImGui::SliderInt("Temperature", ¶ms.colorGradingOptions.temperature, -100, 100);
|
||||
ImGui::SliderInt("Tint", ¶ms.colorGradingOptions.tint, -100, 100);
|
||||
ImGui::SliderFloat3("Out Red", ¶ms.colorGradingOptions.outRed.x, -2.0f, 2.0f);
|
||||
ImGui::SliderFloat3("Out Green", ¶ms.colorGradingOptions.outGreen.x, -2.0f, 2.0f);
|
||||
ImGui::SliderFloat3("Out Blue", ¶ms.colorGradingOptions.outBlue.x, -2.0f, 2.0f);
|
||||
ImGui::Indent();
|
||||
if (ImGui::CollapsingHeader("While balance")) {
|
||||
ImGui::SliderInt("Temperature", ¶ms.colorGradingOptions.temperature, -100, 100);
|
||||
ImGui::SliderInt("Tint", ¶ms.colorGradingOptions.tint, -100, 100);
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Channel mixer")) {
|
||||
ImGui::SliderFloat3("Out Red", ¶ms.colorGradingOptions.outRed.x, -2.0f, 2.0f);
|
||||
ImGui::SliderFloat3("Out Green", ¶ms.colorGradingOptions.outGreen.x, -2.0f, 2.0f);
|
||||
ImGui::SliderFloat3("Out Blue", ¶ms.colorGradingOptions.outBlue.x, -2.0f, 2.0f);
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Tonal ranges")) {
|
||||
ImGui::ColorEdit3("Shadows", ¶ms.colorGradingOptions.shadows.x);
|
||||
ImGui::SliderFloat("Shadows weight", ¶ms.colorGradingOptions.shadows.w, -2.0f, 2.0f);
|
||||
ImGui::ColorEdit3("Mid-tones", ¶ms.colorGradingOptions.midtones.x);
|
||||
ImGui::SliderFloat("Mid-tones weight", ¶ms.colorGradingOptions.midtones.w, -2.0f, 2.0f);
|
||||
ImGui::ColorEdit3("Highlights", ¶ms.colorGradingOptions.highlights.x);
|
||||
ImGui::SliderFloat("Highlights weight", ¶ms.colorGradingOptions.highlights.w, -2.0f, 2.0f);
|
||||
ImGui::SliderFloat4("Ranges", ¶ms.colorGradingOptions.ranges.x, 0.0f, 1.0f);
|
||||
computeRangePlot(params);
|
||||
ImGui::PlotLines("Shadows curve", g_rangePlot, 1024, 0);
|
||||
ImGui::PlotLines("Mid-tones curve", g_rangePlot + 1024, 1024);
|
||||
ImGui::PlotLines("Highlights curve", g_rangePlot + 2048, 1024);
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Debug")) {
|
||||
@@ -699,6 +731,12 @@ static void preRender(filament::Engine* engine, filament::View* view, filament::
|
||||
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);
|
||||
|
||||
@@ -67,6 +67,10 @@ struct ColorGradingOptions {
|
||||
math::float3 outRed{1.0f, 0.0f, 0.0f};
|
||||
math::float3 outGreen{0.0f, 1.0f, 0.0f};
|
||||
math::float3 outBlue{0.0f, 0.0f, 1.0f};
|
||||
math::float4 shadows{1.0f, 1.0f, 1.0f, 0.0f};
|
||||
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};
|
||||
};
|
||||
|
||||
struct SandboxParameters {
|
||||
|
||||
Reference in New Issue
Block a user