UnityGame/Library/PackageCache/com.unity.render-pipelines.core/ShaderLibrary/ACES.hlsl

1522 lines
53 KiB
HLSL
Raw Normal View History

2024-10-27 10:53:47 +03:00
#ifndef __ACES__
#define __ACES__
#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH || defined(UNITY_UNIFIED_SHADER_PRECISION_MODEL)
#pragma warning (disable : 3205) // conversion of larger type to smaller
#endif
/**
* https://github.com/ampas/aces-dev
*
* Academy Color Encoding System (ACES) software and tools are provided by the
* Academy under the following terms and conditions: A worldwide, royalty-free,
* non-exclusive right to copy, modify, create derivatives, and use, in source and
* binary forms, is hereby granted, subject to acceptance of this license.
*
* Copyright 2015 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.).
* Portions contributed by others as indicated. All rights reserved.
*
* Performance of any of the aforementioned acts indicates acceptance to be bound
* by the following terms and conditions:
*
* * Copies of source code, in whole or in part, must retain the above copyright
* notice, this list of conditions and the Disclaimer of Warranty.
*
* * Use in binary form must retain the above copyright notice, this list of
* conditions and the Disclaimer of Warranty in the documentation and/or other
* materials provided with the distribution.
*
* * Nothing in this license shall be deemed to grant any rights to trademarks,
* copyrights, patents, trade secrets or any other intellectual property of
* A.M.P.A.S. or any contributors, except as expressly stated herein.
*
* * Neither the name "A.M.P.A.S." nor the name of any other contributors to this
* software may be used to endorse or promote products derivative of or based on
* this software without express prior written permission of A.M.P.A.S. or the
* contributors, as appropriate.
*
* This license shall be construed pursuant to the laws of the State of
* California, and any disputes related thereto shall be subject to the
* jurisdiction of the courts therein.
*
* Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY
* CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY
* DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR
* OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR
* APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR
* UNDISCLOSED.
*/
#include "Common.hlsl"
#define ACEScc_MAX 1.4679964
#define ACEScc_MIDGRAY 0.4135884
//
// Precomputed matrices (pre-transposed)
// See https://github.com/ampas/aces-dev/blob/master/transforms/ctl/README-MATRIX.md
//
static const half3x3 sRGB_2_AP0 = {
0.4397010, 0.3829780, 0.1773350,
0.0897923, 0.8134230, 0.0967616,
0.0175440, 0.1115440, 0.8707040
};
static const half3x3 sRGB_2_AP1 = {
0.61319, 0.33951, 0.04737,
0.07021, 0.91634, 0.01345,
0.02062, 0.10957, 0.86961
};
static const half3x3 AP0_2_sRGB = {
2.52169, -1.13413, -0.38756,
-0.27648, 1.37272, -0.09624,
-0.01538, -0.15298, 1.16835,
};
static const half3x3 AP1_2_sRGB = {
1.70505, -0.62179, -0.08326,
-0.13026, 1.14080, -0.01055,
-0.02400, -0.12897, 1.15297,
};
static const half3x3 AP0_2_AP1_MAT = {
1.4514393161, -0.2365107469, -0.2149285693,
-0.0765537734, 1.1762296998, -0.0996759264,
0.0083161484, -0.0060324498, 0.9977163014
};
static const half3x3 AP1_2_AP0_MAT = {
0.6954522414, 0.1406786965, 0.1638690622,
0.0447945634, 0.8596711185, 0.0955343182,
-0.0055258826, 0.0040252103, 1.0015006723
};
static const half3x3 AP1_2_XYZ_MAT = {
0.6624541811, 0.1340042065, 0.1561876870,
0.2722287168, 0.6740817658, 0.0536895174,
-0.0055746495, 0.0040607335, 1.0103391003
};
static const half3x3 XYZ_2_AP1_MAT = {
1.6410233797, -0.3248032942, -0.2364246952,
-0.6636628587, 1.6153315917, 0.0167563477,
0.0117218943, -0.0082844420, 0.9883948585
};
static const half3x3 XYZ_2_REC709_MAT = {
3.2409699419, -1.5373831776, -0.4986107603,
-0.9692436363, 1.8759675015, 0.0415550574,
0.0556300797, -0.2039769589, 1.0569715142
};
static const half3x3 XYZ_2_REC2020_MAT = {
1.7166511880, -0.3556707838, -0.2533662814,
-0.6666843518, 1.6164812366, 0.0157685458,
0.0176398574, -0.0427706133, 0.9421031212
};
static const half3x3 XYZ_2_DCIP3_MAT = {
2.7253940305, -1.0180030062, -0.4401631952,
-0.7951680258, 1.6897320548, 0.0226471906,
0.0412418914, -0.0876390192, 1.1009293786
};
static const half3x3 XYZ_2_P3D65_MAT = {
2.4934969119, -0.9313836179, -0.4027107845,
-0.8294889696, 1.7626640603, 0.0236246858,
0.0358458302, -0.0761723893, 0.9568845240
};
static const half3 AP1_RGB2Y = half3(0.272229, 0.674082, 0.0536895);
static const half3x3 RRT_SAT_MAT = {
0.9708890, 0.0269633, 0.00214758,
0.0108892, 0.9869630, 0.00214758,
0.0108892, 0.0269633, 0.96214800
};
static const half3x3 ODT_SAT_MAT = {
0.949056, 0.0471857, 0.00375827,
0.019056, 0.9771860, 0.00375827,
0.019056, 0.0471857, 0.93375800
};
static const half3x3 D60_2_D65_CAT = {
0.98722400, -0.00611327, 0.0159533,
-0.00759836, 1.00186000, 0.0053302,
0.00307257, -0.00509595, 1.0816800
};
//
// Unity to ACES
//
// converts Unity raw (sRGB primaries) to
// ACES2065-1 (AP0 w/ linear encoding)
//
half3 unity_to_ACES(half3 x)
{
x = mul(sRGB_2_AP0, x);
return x;
}
//
// ACES to Unity
//
// converts ACES2065-1 (AP0 w/ linear encoding)
// Unity raw (sRGB primaries) to
//
half3 ACES_to_unity(half3 x)
{
x = mul(AP0_2_sRGB, x);
return x;
}
//
// Unity to ACEScg
//
// converts Unity raw (sRGB primaries) to
// ACEScg (AP1 w/ linear encoding)
//
half3 unity_to_ACEScg(half3 x)
{
x = mul(sRGB_2_AP1, x);
return x;
}
//
// ACEScg to Unity
//
// converts ACEScg (AP1 w/ linear encoding) to
// Unity raw (sRGB primaries)
//
half3 ACEScg_to_unity(half3 x)
{
x = mul(AP1_2_sRGB, x);
return x;
}
half3 ACEScg_to_Rec2020(half3 x)
{
half3 xyz = mul(AP1_2_XYZ_MAT, x);
return mul(XYZ_2_REC2020_MAT, xyz);
}
//
// ACES Color Space Conversion - ACES to ACEScc
//
// converts ACES2065-1 (AP0 w/ linear encoding) to
// ACEScc (AP1 w/ logarithmic encoding)
//
// This transform follows the formulas from section 4.4 in S-2014-003
//
half ACES_to_ACEScc(half x)
{
if (x <= 0.0)
return -0.35828683; // = (log2(pow(2.0, -15.0) * 0.5) + 9.72) / 17.52
else if (x < pow(2.0, -15.0))
return (log2(pow(2.0, -16.0) + x * 0.5) + 9.72) / 17.52;
else // (x >= pow(2.0, -15.0))
return (log2(x) + 9.72) / 17.52;
}
half ACES_to_ACEScc_fast(half x)
{
// x is clamped to [0, HALF_MAX], skip the <= 0 check
return (x < 0.00003051757) ? (log2(0.00001525878 + x * 0.5) + 9.72) / 17.52 : (log2(x) + 9.72) / 17.52;
}
half3 ACES_to_ACEScc(half3 x)
{
x = clamp(x, 0.0, HALF_MAX);
// x is clamped to [0, HALF_MAX], skip the <= 0 check
return half3(
ACES_to_ACEScc_fast(x.r),
ACES_to_ACEScc_fast(x.g),
ACES_to_ACEScc_fast(x.b)
);
/*
return half3(
ACES_to_ACEScc(x.r),
ACES_to_ACEScc(x.g),
ACES_to_ACEScc(x.b)
);
*/
}
//
// ACES Color Space Conversion - ACEScc to ACES
//
// converts ACEScc (AP1 w/ ACESlog encoding) to
// ACES2065-1 (AP0 w/ linear encoding)
//
// This transform follows the formulas from section 4.4 in S-2014-003
//
half ACEScc_to_ACES(half x)
{
// TODO: Optimize me
if (x < -0.3013698630) // (9.72 - 15) / 17.52
return (pow(2.0, x * 17.52 - 9.72) - pow(2.0, -16.0)) * 2.0;
else if (x < (log2(HALF_MAX) + 9.72) / 17.52)
return pow(2.0, x * 17.52 - 9.72);
else // (x >= (log2(HALF_MAX) + 9.72) / 17.52)
return HALF_MAX;
}
half3 ACEScc_to_ACES(half3 x)
{
return half3(
ACEScc_to_ACES(x.r),
ACEScc_to_ACES(x.g),
ACEScc_to_ACES(x.b)
);
}
//
// ACES Color Space Conversion - ACES to ACEScg
//
// converts ACES2065-1 (AP0 w/ linear encoding) to
// ACEScg (AP1 w/ linear encoding)
//
// Uses float3 to avoid going out of half-precision bounds
//
float3 ACES_to_ACEScg(float3 x)
{
return mul(AP0_2_AP1_MAT, x);
}
//
// ACES Color Space Conversion - ACEScg to ACES
//
// converts ACEScg (AP1 w/ linear encoding) to
// ACES2065-1 (AP0 w/ linear encoding)
//
// Uses float3 to avoid going out of half-precision bounds
//
float3 ACEScg_to_ACES(float3 x)
{
return mul(AP1_2_AP0_MAT, x);
}
//
// Reference Rendering Transform (RRT)
//
// Input is ACES
// Output is OCES
//
half rgb_2_saturation(half3 rgb)
{
const half TINY = 1e-4;
half mi = Min3(rgb.r, rgb.g, rgb.b);
half ma = Max3(rgb.r, rgb.g, rgb.b);
return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2);
}
half rgb_2_yc(half3 rgb)
{
const half ycRadiusWeight = 1.75;
// Converts RGB to a luminance proxy, here called YC
// YC is ~ Y + K * Chroma
// Constant YC is a cone-shaped surface in RGB space, with the tip on the
// neutral axis, towards white.
// YC is normalized: RGB 1 1 1 maps to YC = 1
//
// ycRadiusWeight defaults to 1.75, although can be overridden in function
// call to rgb_2_yc
// ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral
// of same value
// ycRadiusWeight = 2 -> YC for pure red, green, blue == YC for neutral of
// same value.
half r = rgb.x;
half g = rgb.y;
half b = rgb.z;
half k = b * (b - g) + g * (g - r) + r * (r - b);
k = max(k, 0.0); // Clamp to avoid precision issue causing k < 0, making sqrt(k) undefined
#if defined(SHADER_API_SWITCH)
half chroma = k == 0.0 ? 0.0 : sqrt(k); // Avoid Nan
#else
half chroma = sqrt(k);
#endif
return (b + g + r + ycRadiusWeight * chroma) / 3.0;
}
half rgb_2_hue(half3 rgb)
{
// Returns a geometric hue angle in degrees (0-360) based on RGB values.
// For neutral colors, hue is undefined and the function will return a quiet NaN value.
half hue;
if (rgb.x == rgb.y && rgb.y == rgb.z)
hue = 0.0; // RGB triplets where RGB are equal have an undefined hue
else
hue = (180.0 / PI) * atan2(sqrt(3.0) * (rgb.y - rgb.z), 2.0 * rgb.x - rgb.y - rgb.z);
if (hue < 0.0) hue = hue + 360.0;
return hue;
}
half center_hue(half hue, half centerH)
{
half hueCentered = hue - centerH;
if (hueCentered < -180.0) hueCentered = hueCentered + 360.0;
else if (hueCentered > 180.0) hueCentered = hueCentered - 360.0;
return hueCentered;
}
half sigmoid_shaper(half x)
{
// Sigmoid function in the range 0 to 1 spanning -2 to +2.
half t = max(1.0 - abs(x / 2.0), 0.0);
half y = 1.0 + half(FastSign(x)) * (1.0 - t * t);
return y / 2.0;
}
half glow_fwd(half ycIn, half glowGainIn, half glowMid)
{
half glowGainOut;
if (ycIn <= 2.0 / 3.0 * glowMid)
glowGainOut = glowGainIn;
else if (ycIn >= 2.0 * glowMid)
glowGainOut = 0.0;
else
glowGainOut = glowGainIn * (glowMid / ycIn - 1.0 / 2.0);
return glowGainOut;
}
/*
half cubic_basis_shaper
(
half x,
half w // full base width of the shaper function (in degrees)
)
{
half M[4][4] = {
{ -1.0 / 6, 3.0 / 6, -3.0 / 6, 1.0 / 6 },
{ 3.0 / 6, -6.0 / 6, 3.0 / 6, 0.0 / 6 },
{ -3.0 / 6, 0.0 / 6, 3.0 / 6, 0.0 / 6 },
{ 1.0 / 6, 4.0 / 6, 1.0 / 6, 0.0 / 6 }
};
half knots[5] = {
-w / 2.0,
-w / 4.0,
0.0,
w / 4.0,
w / 2.0
};
half y = 0.0;
if ((x > knots[0]) && (x < knots[4]))
{
half knot_coord = (x - knots[0]) * 4.0 / w;
int j = knot_coord;
half t = knot_coord - j;
half monomials[4] = { t*t*t, t*t, t, 1.0 };
// (if/else structure required for compatibility with CTL < v1.5.)
if (j == 3)
{
y = monomials[0] * M[0][0] + monomials[1] * M[1][0] +
monomials[2] * M[2][0] + monomials[3] * M[3][0];
}
else if (j == 2)
{
y = monomials[0] * M[0][1] + monomials[1] * M[1][1] +
monomials[2] * M[2][1] + monomials[3] * M[3][1];
}
else if (j == 1)
{
y = monomials[0] * M[0][2] + monomials[1] * M[1][2] +
monomials[2] * M[2][2] + monomials[3] * M[3][2];
}
else if (j == 0)
{
y = monomials[0] * M[0][3] + monomials[1] * M[1][3] +
monomials[2] * M[2][3] + monomials[3] * M[3][3];
}
else
{
y = 0.0;
}
}
return y * 3.0 / 2.0;
}
*/
static const half3x3 M = {
0.5, -1.0, 0.5,
-1.0, 1.0, 0.0,
0.5, 0.5, 0.0
};
half segmented_spline_c5_fwd(half x)
{
const half coefsLow[6] = { -4.0000000000, -4.0000000000, -3.1573765773, -0.4852499958, 1.8477324706, 1.8477324706 }; // coefs for B-spline between minPoint and midPoint (units of log luminance)
const half coefsHigh[6] = { -0.7185482425, 2.0810307172, 3.6681241237, 4.0000000000, 4.0000000000, 4.0000000000 }; // coefs for B-spline between midPoint and maxPoint (units of log luminance)
const half2 minPoint = half2(0.18 * exp2(-15.0), 0.0001); // {luminance, luminance} linear extension below this
const half2 midPoint = half2(0.18, 0.48); // {luminance, luminance}
const half2 maxPoint = half2(0.18 * exp2(18.0), 10000.0); // {luminance, luminance} linear extension above this
const half slopeLow = 0.0; // log-log slope of low linear extension
const half slopeHigh = 0.0; // log-log slope of high linear extension
const int N_KNOTS_LOW = 4;
const int N_KNOTS_HIGH = 4;
// Check for negatives or zero before taking the log. If negative or zero,
// set to ACESMIN.1
half xCheck = x;
if (xCheck <= 0.0) xCheck = HALF_MIN;
half logx = log10(xCheck);
half logy;
if (logx <= log10(minPoint.x))
{
logy = logx * slopeLow + (log10(minPoint.y) - slopeLow * log10(minPoint.x));
}
else if ((logx > log10(minPoint.x)) && (logx < log10(midPoint.x)))
{
half knot_coord = half(N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
int j = knot_coord;
half t = knot_coord - half(j);
half3 cf = half3(coefsLow[j], coefsLow[j + 1], coefsLow[j + 2]);
half3 monomials = half3(t * t, t, 1.0);
logy = dot(monomials, mul(M, cf));
}
else if ((logx >= log10(midPoint.x)) && (logx < log10(maxPoint.x)))
{
half knot_coord = half(N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
int j = knot_coord;
half t = knot_coord - half(j);
half3 cf = half3(coefsHigh[j], coefsHigh[j + 1], coefsHigh[j + 2]);
half3 monomials = half3(t * t, t, 1.0);
logy = dot(monomials, mul(M, cf));
}
else
{ //if (logIn >= log10(maxPoint.x)) {
logy = logx * slopeHigh + (log10(maxPoint.y) - slopeHigh * log10(maxPoint.x));
}
return pow(10.0, logy);
}
struct SegmentedSplineParams_c9
{
float coefsLow[10]; // coefs for B-spline between minPoint and midPoint (units of log luminance)
float coefsHigh[10]; // coefs for B-spline between midPoint and maxPoint (units of log luminance)
half2 minPoint; // {luminance, luminance} linear extension below this
half2 midPoint; // {luminance, luminance}
half2 maxPoint; // {luminance, luminance} linear extension above this
float slopeLow; // log-log slope of low linear extension
float slopeHigh; // log-log slope of high linear extension
};
half segmented_spline_c9_fwd(half x, SegmentedSplineParams_c9 params)
{
const int N_KNOTS_LOW = 8;
const int N_KNOTS_HIGH = 8;
// Check for negatives or zero before taking the log. If negative or zero,
// set to OCESMIN.
half xCheck = x;
if (xCheck <= 0.0) xCheck = 1e-4;
half logx = log10(xCheck);
half logy;
if (logx <= log10(params.minPoint.x))
{
logy = logx * half(params.slopeLow) + (log10(params.minPoint.y) - half(params.slopeLow) * log10(params.minPoint.x));
}
else if ((logx > log10(params.minPoint.x)) && (logx < log10(params.midPoint.x)))
{
half knot_coord = half(N_KNOTS_LOW - 1) * (logx - log10(params.minPoint.x)) / (log10(params.midPoint.x) - log10(params.minPoint.x));
int j = knot_coord;
half t = knot_coord - half(j);
half3 cf = half3(params.coefsLow[j], params.coefsLow[j + 1], params.coefsLow[j + 2]);
half3 monomials = half3(t * t, t, 1.0);
logy = dot(monomials, mul(M, cf));
}
else if ((logx >= log10(params.midPoint.x)) && (logx < log10(params.maxPoint.x)))
{
half knot_coord = half(N_KNOTS_HIGH - 1) * (logx - log10(params.midPoint.x)) / (log10(params.maxPoint.x) - log10(params.midPoint.x));
int j = knot_coord;
half t = knot_coord - half(j);
half3 cf = half3(params.coefsHigh[j], params.coefsHigh[j + 1], params.coefsHigh[j + 2]);
half3 monomials = half3(t * t, t, 1.0);
logy = dot(monomials, mul(M, cf));
}
else
{ //if (logIn >= log10(maxPoint.x)) {
logy = logx * half(params.slopeHigh) + (log10(params.maxPoint.y) - half(params.slopeHigh) * log10(params.maxPoint.x));
}
return pow(10.0, logy);
}
// > 48 Nits from https://github.com/ampas/aces-dev/blob/dev/transforms/ctl/lib/ACESlib.Tonescales.ctl
SegmentedSplineParams_c9 GetSplineParams_ODT48Nits()
{
const SegmentedSplineParams_c9 ODT_48nits =
{
// coefsLow[10]
{ -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414},
// coefsHigh[10]
{ 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 },
{segmented_spline_c5_fwd(0.18*pow(2.,-6.5)), 0.02}, // minPoint
{segmented_spline_c5_fwd(0.18), 4.8}, // midPoint
{segmented_spline_c5_fwd(0.18*pow(2.,6.5)), 48.0}, // maxPoint
0.0, // slopeLow
0.04 // slopeHigh
};
return ODT_48nits;
}
SegmentedSplineParams_c9 GetSplineParams_ODT1000Nits()
{
const SegmentedSplineParams_c9 ODT_1000nits =
{
// coefsLow[10]
{ -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
// coefsHigh[10]
{ 0.8089132070, 1.1910867930, 1.5683, 1.9483, 2.3083, 2.6384, 2.8595, 2.9872608805, 3.0127391195, 3.0127391195 },
{segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
{segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
{segmented_spline_c5_fwd(0.18*pow(2.,10.)), 1000.0}, // maxPoint
3.0, // slopeLow
0.06 // slopeHigh
};
return ODT_1000nits;
}
SegmentedSplineParams_c9 GetSplineParams_ODT2000Nits()
{
const SegmentedSplineParams_c9 ODT_2000nits =
{
// coefsLow[10]
{ -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
// coefsHigh[10]
{ 0.8019952042, 1.1980047958, 1.5943000000, 1.9973000000, 2.3783000000, 2.7684000000, 3.0515000000, 3.2746293562, 3.3274306351, 3.3274306351 },
{segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
{segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
{segmented_spline_c5_fwd(0.18*pow(2.,11.)), 2000.0}, // maxPoint
3.0, // slopeLow
0.12 // slopeHigh
};
return ODT_2000nits;
}
SegmentedSplineParams_c9 GetSplineParams_ODT4000Nits()
{
const SegmentedSplineParams_c9 ODT_4000nits =
{
// coefsLow[10]
{ -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
// coefsHigh[10]
{ 0.7973186613, 1.2026813387, 1.6093000000, 2.0108000000, 2.4148000000, 2.8179000000, 3.1725000000, 3.5344995451, 3.6696204376, 3.6696204376 },
{segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
{segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
{segmented_spline_c5_fwd(0.18*pow(2.,12.)), 4000.0}, // maxPoint
3.0, // slopeLow
0.3 // slopeHigh
};
return ODT_4000nits;
}
half segmented_spline_c9_fwd(half x)
{
return segmented_spline_c9_fwd(x, GetSplineParams_ODT48Nits());
}
static const half RRT_GLOW_GAIN = 0.05;
static const half RRT_GLOW_MID = 0.08;
static const half RRT_RED_SCALE = 0.82;
static const half RRT_RED_PIVOT = 0.03;
static const half RRT_RED_HUE = 0.0;
static const half RRT_RED_WIDTH = 135.0;
static const half RRT_SAT_FACTOR = 0.96;
half3 RRT(half3 aces)
{
// --- Glow module --- //
half saturation = rgb_2_saturation(aces);
half ycIn = rgb_2_yc(aces);
half s = sigmoid_shaper((saturation - 0.4) / 0.2);
half addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
aces *= addedGlow;
// --- Red modifier --- //
half hue = rgb_2_hue(aces);
half centeredHue = center_hue(hue, RRT_RED_HUE);
half hueWeight;
{
//hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH);
hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
hueWeight *= hueWeight;
}
aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1.0 - RRT_RED_SCALE);
// --- ACES to RGB rendering space --- //
aces = clamp(aces, 0.0, HALF_MAX); // avoids saturated negative colors from becoming positive in the matrix
half3 rgbPre = mul(AP0_2_AP1_MAT, aces);
rgbPre = clamp(rgbPre, 0, HALF_MAX);
// --- Global desaturation --- //
//rgbPre = mul(RRT_SAT_MAT, rgbPre);
rgbPre = lerp(dot(rgbPre, AP1_RGB2Y).xxx, rgbPre, RRT_SAT_FACTOR.xxx);
// --- Apply the tonescale independently in rendering-space RGB --- //
half3 rgbPost;
rgbPost.x = segmented_spline_c5_fwd(rgbPre.x);
rgbPost.y = segmented_spline_c5_fwd(rgbPre.y);
rgbPost.z = segmented_spline_c5_fwd(rgbPre.z);
// --- RGB rendering space to OCES --- //
half3 outputVal = mul(AP1_2_AP0_MAT, rgbPost);
return outputVal;
}
//
// Output Device Transform
//
half3 Y_2_linCV(half3 Y, half Ymax, half Ymin)
{
return (Y - Ymin) / (Ymax - Ymin);
}
half3 XYZ_2_xyY(half3 XYZ)
{
half divisor = max(dot(XYZ, (1.0).xxx), 1e-4);
return half3(XYZ.xy / divisor, XYZ.y);
}
half3 xyY_2_XYZ(half3 xyY)
{
half m = xyY.z / max(xyY.y, 1e-4);
half3 XYZ = half3(xyY.xz, (1.0 - xyY.x - xyY.y));
XYZ.xz *= m;
return XYZ;
}
static const half DIM_SURROUND_GAMMA = 0.9811;
half3 darkSurround_to_dimSurround(half3 linearCV)
{
// Extra conversions to float3/half3 are required to avoid floating-point precision issues on some platforms.
half3 XYZ = (half3)mul(AP1_2_XYZ_MAT, (float3)linearCV);
half3 xyY = XYZ_2_xyY(XYZ);
xyY.z = clamp(xyY.z, 0.0, HALF_MAX);
xyY.z = pow(xyY.z, DIM_SURROUND_GAMMA);
XYZ = xyY_2_XYZ(xyY);
return mul(XYZ_2_AP1_MAT, XYZ);
}
half moncurve_r(half y, half gamma, half offs)
{
// Reverse monitor curve
half x;
const half yb = pow(offs * gamma / ((gamma - 1.0) * (1.0 + offs)), gamma);
const half rs = pow((gamma - 1.0) / offs, gamma - 1.0) * pow((1.0 + offs) / gamma, gamma);
if (y >= yb)
x = (1.0 + offs) * pow(y, 1.0 / gamma) - offs;
else
x = y * rs;
return x;
}
half bt1886_r(half L, half gamma, half Lw, half Lb)
{
// The reference EOTF specified in Rec. ITU-R BT.1886
// L = a(max[(V+b),0])^g
half a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma);
half b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma));
half V = pow(max(L / a, 0.0), 1.0 / gamma) - b;
return V;
}
half roll_white_fwd(
half x, // color value to adjust (white scaled to around 1.0)
half new_wht, // white adjustment (e.g. 0.9 for 10% darkening)
half width // adjusted width (e.g. 0.25 for top quarter of the tone scale)
)
{
const half x0 = -1.0;
const half x1 = x0 + width;
const half y0 = -new_wht;
const half y1 = x1;
const half m1 = (x1 - x0);
const half a = y0 - y1 + m1;
const half b = 2.0 * (y1 - y0) - m1;
const half c = y0;
const half t = (-x - x0) / (x1 - x0);
half o = 0.0;
if (t < 0.0)
o = -(t * b + c);
else if (t > 1.0)
o = x;
else
o = -((t * a + b) * t + c);
return o;
}
half3 linear_to_bt1886(half3 x, half gamma, half Lw, half Lb)
{
// Good enough approximation for now, may consider using the exact formula instead
// TODO: Experiment
return pow(max(x, 0.0), 1.0 / 2.4);
// Correct implementation (Reference EOTF specified in Rec. ITU-R BT.1886) :
// L = a(max[(V+b),0])^g
half invgamma = 1.0 / gamma;
half p_Lw = pow(Lw, invgamma);
half p_Lb = pow(Lb, invgamma);
half3 a = pow(p_Lw - p_Lb, gamma).xxx;
half3 b = (p_Lb / p_Lw - p_Lb).xxx;
half3 V = pow(max(x / a, 0.0), invgamma.xxx) - b;
return V;
}
static const half CINEMA_WHITE = 48.0;
static const half CINEMA_BLACK = CINEMA_WHITE / 2400.0;
static const half ODT_SAT_FACTOR = 0.93;
// <ACEStransformID>ODT.Academy.RGBmonitor_100nits_dim.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - sRGB</ACESuserName>
//
// Output Device Transform - RGB computer monitor
//
//
// Summary :
// This transform is intended for mapping OCES onto a desktop computer monitor
// typical of those used in motion picture visual effects production. These
// monitors may occasionally be referred to as "sRGB" displays, however, the
// monitor for which this transform is designed does not exactly match the
// specifications in IEC 61966-2-1:1999.
//
// The assumed observer adapted white is D65, and the viewing environment is
// that of a dim surround.
//
// The monitor specified is intended to be more typical of those found in
// visual effects production.
//
// Device Primaries :
// Primaries are those specified in Rec. ITU-R BT.709
// CIE 1931 chromaticities: x y Y
// Red: 0.64 0.33
// Green: 0.3 0.6
// Blue: 0.15 0.06
// White: 0.3127 0.329 100 cd/m^2
//
// Display EOTF :
// The reference electro-optical transfer function specified in
// IEC 61966-2-1:1999.
//
// Signal Range:
// This transform outputs full range code values.
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.3127 0.329
//
// Viewing Environment:
// This ODT has a compensation for viewing environment variables more typical
// of those associated with video mastering.
//
half3 ODT_RGBmonitor_100nits_dim(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// Apply gamma adjustment to compensate for dim surround
linearCV = darkSurround_to_dimSurround(linearCV);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// Apply CAT from ACES white point to assumed observer adapted white point
XYZ = mul(D60_2_D65_CAT, XYZ);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC709_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// TODO: Revisit when it is possible to deactivate Unity default framebuffer encoding
// with sRGB opto-electrical transfer function (OETF).
/*
// Encode linear code values with transfer function
half3 outputCV;
// moncurve_r with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB)
const half DISPGAMMA = 2.4;
const half OFFSET = 0.055;
outputCV.x = moncurve_r(linearCV.x, DISPGAMMA, OFFSET);
outputCV.y = moncurve_r(linearCV.y, DISPGAMMA, OFFSET);
outputCV.z = moncurve_r(linearCV.z, DISPGAMMA, OFFSET);
outputCV = linear_to_sRGB(linearCV);
*/
// Unity already draws to a sRGB target
return linearCV;
}
// <ACEStransformID>ODT.Academy.RGBmonitor_D60sim_100nits_dim.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - sRGB (D60 sim.)</ACESuserName>
//
// Output Device Transform - RGB computer monitor (D60 simulation)
//
//
// Summary :
// This transform is intended for mapping OCES onto a desktop computer monitor
// typical of those used in motion picture visual effects production. These
// monitors may occasionally be referred to as "sRGB" displays, however, the
// monitor for which this transform is designed does not exactly match the
// specifications in IEC 61966-2-1:1999.
//
// The assumed observer adapted white is D60, and the viewing environment is
// that of a dim surround.
//
// The monitor specified is intended to be more typical of those found in
// visual effects production.
//
// Device Primaries :
// Primaries are those specified in Rec. ITU-R BT.709
// CIE 1931 chromaticities: x y Y
// Red: 0.64 0.33
// Green: 0.3 0.6
// Blue: 0.15 0.06
// White: 0.3127 0.329 100 cd/m^2
//
// Display EOTF :
// The reference electro-optical transfer function specified in
// IEC 61966-2-1:1999.
//
// Signal Range:
// This transform outputs full range code values.
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.32168 0.33767
//
// Viewing Environment:
// This ODT has a compensation for viewing environment variables more typical
// of those associated with video mastering.
//
half3 ODT_RGBmonitor_D60sim_100nits_dim(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// --- Compensate for different white point being darker --- //
// This adjustment is to correct an issue that exists in ODTs where the device
// is calibrated to a white chromaticity other than D60. In order to simulate
// D60 on such devices, unequal code values are sent to the display to achieve
// neutrals at D60. In order to produce D60 on a device calibrated to the DCI
// white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
// 0.351) the red channel is higher than green and blue to compensate for the
// "greenish" DCI white. This is the correct behavior but it means that as
// highlight increase, the red channel will hit the device maximum first and
// clip, resulting in a chromaticity shift as the green and blue channels
// continue to increase.
// To avoid this clipping error, a slight scale factor is applied to allow the
// ODTs to simulate D60 within the D65 calibration white point.
// Scale and clamp white to avoid casted highlights due to D60 simulation
const half SCALE = 0.955;
linearCV = min(linearCV, 1.0) * SCALE;
// Apply gamma adjustment to compensate for dim surround
linearCV = darkSurround_to_dimSurround(linearCV);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC709_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// TODO: Revisit when it is possible to deactivate Unity default framebuffer encoding
// with sRGB opto-electrical transfer function (OETF).
/*
// Encode linear code values with transfer function
half3 outputCV;
// moncurve_r with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB)
const half DISPGAMMA = 2.4;
const half OFFSET = 0.055;
outputCV.x = moncurve_r(linearCV.x, DISPGAMMA, OFFSET);
outputCV.y = moncurve_r(linearCV.y, DISPGAMMA, OFFSET);
outputCV.z = moncurve_r(linearCV.z, DISPGAMMA, OFFSET);
outputCV = linear_to_sRGB(linearCV);
*/
// Unity already draws to a sRGB target
return linearCV;
}
// <ACEStransformID>ODT.Academy.Rec709_100nits_dim.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - Rec.709</ACESuserName>
//
// Output Device Transform - Rec709
//
//
// Summary :
// This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
// that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
// adapted white is D65, and the viewing environment is a dim surround.
//
// A possible use case for this transform would be HDTV/video mastering.
//
// Device Primaries :
// Primaries are those specified in Rec. ITU-R BT.709
// CIE 1931 chromaticities: x y Y
// Red: 0.64 0.33
// Green: 0.3 0.6
// Blue: 0.15 0.06
// White: 0.3127 0.329 100 cd/m^2
//
// Display EOTF :
// The reference electro-optical transfer function specified in
// Rec. ITU-R BT.1886.
//
// Signal Range:
// By default, this transform outputs full range code values. If instead a
// SMPTE "legal" signal is desired, there is a runtime flag to output
// SMPTE legal signal. In ctlrender, this can be achieved by appending
// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.3127 0.329
//
// Viewing Environment:
// This ODT has a compensation for viewing environment variables more typical
// of those associated with video mastering.
//
half3 ODT_Rec709_100nits_dim(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// Apply gamma adjustment to compensate for dim surround
linearCV = darkSurround_to_dimSurround(linearCV);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// Apply CAT from ACES white point to assumed observer adapted white point
XYZ = mul(D60_2_D65_CAT, XYZ);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC709_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// Encode linear code values with transfer function
const half DISPGAMMA = 2.4;
const half L_W = 1.0;
const half L_B = 0.0;
half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
// TODO: Implement support for legal range.
// NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
// by default which will result in double perceptual encoding, thus for now if one want to use
// this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
// compensate for Unity default behaviour.
return outputCV;
}
// <ACEStransformID>ODT.Academy.Rec709_D60sim_100nits_dim.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - Rec.709 (D60 sim.)</ACESuserName>
//
// Output Device Transform - Rec709 (D60 simulation)
//
//
// Summary :
// This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
// that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
// adapted white is D60, and the viewing environment is a dim surround.
//
// A possible use case for this transform would be cinema "soft-proofing".
//
// Device Primaries :
// Primaries are those specified in Rec. ITU-R BT.709
// CIE 1931 chromaticities: x y Y
// Red: 0.64 0.33
// Green: 0.3 0.6
// Blue: 0.15 0.06
// White: 0.3127 0.329 100 cd/m^2
//
// Display EOTF :
// The reference electro-optical transfer function specified in
// Rec. ITU-R BT.1886.
//
// Signal Range:
// By default, this transform outputs full range code values. If instead a
// SMPTE "legal" signal is desired, there is a runtime flag to output
// SMPTE legal signal. In ctlrender, this can be achieved by appending
// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.32168 0.33767
//
// Viewing Environment:
// This ODT has a compensation for viewing environment variables more typical
// of those associated with video mastering.
//
half3 ODT_Rec709_D60sim_100nits_dim(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// --- Compensate for different white point being darker --- //
// This adjustment is to correct an issue that exists in ODTs where the device
// is calibrated to a white chromaticity other than D60. In order to simulate
// D60 on such devices, unequal code values must be sent to the display to achieve
// the chromaticities of D60. More specifically, in order to produce D60 on a device
// calibrated to a D65 white point (i.e. equal code values yield CIE x,y
// chromaticities of 0.3127, 0.329) the red channel must be slightly higher than
// that of green and blue in order to compensate for the relatively more "blue-ish"
// D65 white. This unequalness of color channels is the correct behavior but it
// means that as neutral highlights increase, the red channel will hit the
// device maximum first and clip, resulting in a small chromaticity shift as the
// green and blue channels continue to increase to their maximums.
// To avoid this clipping error, a slight scale factor is applied to allow the
// ODTs to simulate D60 within the D65 calibration white point.
// Scale and clamp white to avoid casted highlights due to D60 simulation
const half SCALE = 0.955;
linearCV = min(linearCV, 1.0) * SCALE;
// Apply gamma adjustment to compensate for dim surround
linearCV = darkSurround_to_dimSurround(linearCV);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC709_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// Encode linear code values with transfer function
const half DISPGAMMA = 2.4;
const half L_W = 1.0;
const half L_B = 0.0;
half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
// TODO: Implement support for legal range.
// NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
// by default which will result in double perceptual encoding, thus for now if one want to use
// this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
// compensate for Unity default behaviour.
return outputCV;
}
// <ACEStransformID>ODT.Academy.Rec2020_100nits_dim.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - Rec.2020</ACESuserName>
//
// Output Device Transform - Rec2020
//
//
// Summary :
// This transform is intended for mapping OCES onto a Rec.2020 broadcast
// monitor that is calibrated to a D65 white point at 100 cd/m^2. The assumed
// observer adapted white is D65, and the viewing environment is that of a dim
// surround.
//
// A possible use case for this transform would be UHDTV/video mastering.
//
// Device Primaries :
// Primaries are those specified in Rec. ITU-R BT.2020
// CIE 1931 chromaticities: x y Y
// Red: 0.708 0.292
// Green: 0.17 0.797
// Blue: 0.131 0.046
// White: 0.3127 0.329 100 cd/m^2
//
// Display EOTF :
// The reference electro-optical transfer function specified in
// Rec. ITU-R BT.1886.
//
// Signal Range:
// By default, this transform outputs full range code values. If instead a
// SMPTE "legal" signal is desired, there is a runtime flag to output
// SMPTE legal signal. In ctlrender, this can be achieved by appending
// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.3127 0.329
//
// Viewing Environment:
// This ODT has a compensation for viewing environment variables more typical
// of those associated with video mastering.
//
half3 ODT_Rec2020_100nits_dim(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// Apply gamma adjustment to compensate for dim surround
linearCV = darkSurround_to_dimSurround(linearCV);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// Apply CAT from ACES white point to assumed observer adapted white point
XYZ = mul(D60_2_D65_CAT, XYZ);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC2020_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// Encode linear code values with transfer function
const half DISPGAMMA = 2.4;
const half L_W = 1.0;
const half L_B = 0.0;
half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
// TODO: Implement support for legal range.
// NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
// by default which will result in double perceptual encoding, thus for now if one want to use
// this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
// compensate for Unity default behaviour.
return outputCV;
}
// <ACEStransformID>ODT.Academy.P3DCI_48nits.a1.0.3</ACEStransformID>
// <ACESuserName>ACES 1.0 Output - P3-DCI</ACESuserName>
//
// Output Device Transform - P3DCI (D60 Simulation)
//
//
// Summary :
// This transform is intended for mapping OCES onto a P3 digital cinema
// projector that is calibrated to a DCI white point at 48 cd/m^2. The assumed
// observer adapted white is D60, and the viewing environment is that of a dark
// theater.
//
// Device Primaries :
// CIE 1931 chromaticities: x y Y
// Red: 0.68 0.32
// Green: 0.265 0.69
// Blue: 0.15 0.06
// White: 0.314 0.351 48 cd/m^2
//
// Display EOTF :
// Gamma: 2.6
//
// Assumed observer adapted white point:
// CIE 1931 chromaticities: x y
// 0.32168 0.33767
//
// Viewing Environment:
// Environment specified in SMPTE RP 431-2-2007
//
half3 ODT_P3DCI_48nits(half3 oces)
{
const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
// --- Compensate for different white point being darker --- //
// This adjustment is to correct an issue that exists in ODTs where the device
// is calibrated to a white chromaticity other than D60. In order to simulate
// D60 on such devices, unequal code values are sent to the display to achieve
// neutrals at D60. In order to produce D60 on a device calibrated to the DCI
// white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
// 0.351) the red channel is higher than green and blue to compensate for the
// "greenish" DCI white. This is the correct behavior but it means that as
// highlight increase, the red channel will hit the device maximum first and
// clip, resulting in a chromaticity shift as the green and blue channels
// continue to increase.
// To avoid this clipping error, a slight scale factor is applied to allow the
// ODTs to simulate D60 within the D65 calibration white point. However, the
// magnitude of the scale factor required for the P3DCI ODT was considered too
// large. Therefore, the scale factor was reduced and the additional required
// compression was achieved via a reshaping of the highlight rolloff in
// conjunction with the scale. The shape of this rolloff was determined
// throught subjective experiments and deemed to best reproduce the
// "character" of the highlights in the P3D60 ODT.
// Roll off highlights to avoid need for as much scaling
const half NEW_WHT = 0.918;
const half ROLL_WIDTH = 0.5;
linearCV.x = roll_white_fwd(linearCV.x, NEW_WHT, ROLL_WIDTH);
linearCV.y = roll_white_fwd(linearCV.y, NEW_WHT, ROLL_WIDTH);
linearCV.z = roll_white_fwd(linearCV.z, NEW_WHT, ROLL_WIDTH);
// Scale and clamp white to avoid casted highlights due to D60 simulation
const half SCALE = 0.96;
linearCV = min(linearCV, NEW_WHT) * SCALE;
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_DCIP3_MAT, XYZ);
// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);
// Encode linear code values with transfer function
const half DISPGAMMA = 2.6;
half3 outputCV = pow(linearCV, 1.0 / DISPGAMMA);
// NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
// by default which will result in double perceptual encoding, thus for now if one want to use
// this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
// compensate for Unity default behaviour.
return outputCV;
}
// IMPORTANT: This will need transforming to the final output space after unlike the standard ODT.
half3 ODT_Rec2020_1000nits_ToLinear(half3 oces)
{
const SegmentedSplineParams_c9 ODT_1000nits = GetSplineParams_ODT1000Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_1000nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_1000nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_1000nits);
// Scale luminance to linear code value
half3 linearCV = Y_2_linCV(rgbPost, ODT_1000nits.maxPoint.y, ODT_1000nits.minPoint.y);
// Apply desaturation to compensate for luminance difference
//linearCV = mul(ODT_SAT_MAT, linearCV);
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
// Convert to display primary encoding
// Rendering space RGB to XYZ
half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
// Apply CAT from ACES white point to assumed observer adapted white point
XYZ = mul(D60_2_D65_CAT, XYZ);
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC2020_MAT, XYZ);
// Handle out-of-gamut values
linearCV = max(linearCV, 0.);
return linearCV;
}
half3 ODT_1000nits_ToAP1(half3 oces)
{
const SegmentedSplineParams_c9 ODT_1000nits = GetSplineParams_ODT1000Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_1000nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_1000nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_1000nits);
return rgbPost;
}
half3 ODT_2000nits_ToAP1(half3 oces)
{
const SegmentedSplineParams_c9 ODT_2000nits = GetSplineParams_ODT2000Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_2000nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_2000nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_2000nits);
return rgbPost;
}
half3 ODT_4000nits_ToAP1(half3 oces)
{
const SegmentedSplineParams_c9 ODT_4000nits = GetSplineParams_ODT4000Nits();
// OCES to RGB rendering space
half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
// Apply the tonescale independently in rendering-space RGB
half3 rgbPost;
rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_4000nits);
rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_4000nits);
rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_4000nits);
return rgbPost;
}
#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH
#pragma warning (enable : 3205) // conversion of larger type to smaller
#endif
#endif // __ACES__