| | |
| | | #ifndef __ACES__ |
| | | #define __ACES__ |
| | | |
| | | /** |
| | | * 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. |
| | | */ |
| | | |
| | | //#define CUSTOM_WHITE_POINT |
| | | |
| | | /* |
| | | Basic usage : |
| | | |
| | | half4 color = tex2D(_MainTex, i.uv); |
| | | half3 aces = unity_to_ACES(color.rgb); |
| | | half3 oces = RRT(aces); |
| | | half3 odt = ODT_RGBmonitor_100nits_dim(oces); |
| | | return half4(odt, color.a); |
| | | |
| | | If you want to customize the white point, uncomment the previous define and set uniforms accordingly: |
| | | |
| | | float whitePoint = 48f; // Default ACES value |
| | | material.SetFloat("CINEMA_WHITE", whitePoint); |
| | | material.SetFloat("CINEMA_DARK", whitePoint / 2400f); |
| | | */ |
| | | |
| | | #include "Common.cginc" |
| | | |
| | | #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 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; |
| | | } |
| | | |
| | | // |
| | | // 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; |
| | | } |
| | | |
| | | 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 (x < 0.00003051757) ? (log2(0.00001525878 + x * 0.5) + 9.72) / 17.52 : (log2(x) + 9.72) / 17.52; |
| | | |
| | | /* |
| | | 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) |
| | | // |
| | | half3 ACES_to_ACEScg(half3 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) |
| | | // |
| | | half3 ACEScg_to_ACES(half3 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-10; |
| | | half mi = Min3(rgb); |
| | | half ma = Max3(rgb); |
| | | 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 chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b)); |
| | | 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 / UNITY_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 + sign(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 |
| | | float xCheck = x; |
| | | if (xCheck <= 0.0) xCheck = 0.00006103515; // = pow(2.0, -14.0); |
| | | |
| | | 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 = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x)); |
| | | int j = knot_coord; |
| | | half t = knot_coord - 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 = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x)); |
| | | int j = knot_coord; |
| | | half t = knot_coord - 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); |
| | | } |
| | | |
| | | half segmented_spline_c9_fwd(half x) |
| | | { |
| | | const half coefsLow[10] = { -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414 }; // coefs for B-spline between minPoint and midPoint (units of log luminance) |
| | | const half coefsHigh[10] = { 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 }; // coefs for B-spline between midPoint and maxPoint (units of log luminance) |
| | | const half2 minPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(-6.5)), 0.02); // {luminance, luminance} linear extension below this |
| | | const half2 midPoint = half2(segmented_spline_c5_fwd(0.18), 4.8); // {luminance, luminance} |
| | | const half2 maxPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(6.5)), 48.0); // {luminance, luminance} linear extension above this |
| | | const half slopeLow = 0.0; // log-log slope of low linear extension |
| | | const half slopeHigh = 0.04; // log-log slope of high linear extension |
| | | |
| | | 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(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 = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x)); |
| | | int j = knot_coord; |
| | | half t = knot_coord - 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 = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x)); |
| | | int j = knot_coord; |
| | | half t = knot_coord - 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); |
| | | } |
| | | |
| | | 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 rgbOces = mul(AP1_2_AP0_MAT, rgbPost); |
| | | |
| | | return rgbOces; |
| | | } |
| | | |
| | | // |
| | | // 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) |
| | | { |
| | | half3 XYZ = mul(AP1_2_XYZ_MAT, 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_sRGB(half3 x) |
| | | { |
| | | return (x <= 0.0031308 ? (x * 12.9232102) : 1.055 * pow(x, 1.0 / 2.4) - 0.055); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | #if defined(CUSTOM_WHITE_POINT) |
| | | half CINEMA_WHITE; |
| | | half CINEMA_BLACK; |
| | | #else |
| | | static const half CINEMA_WHITE = 48.0; |
| | | static const half CINEMA_BLACK = CINEMA_WHITE / 2400.0; |
| | | #endif |
| | | |
| | | 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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) |
| | | { |
| | | // 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); |
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y); |
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z); |
| | | |
| | | // 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; |
| | | } |
| | | |
| | | #endif // __ACES__ |
| | | #ifndef __ACES__
|
| | | #define __ACES__
|
| | |
|
| | | /**
|
| | | * 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.
|
| | | */
|
| | |
|
| | | //#define CUSTOM_WHITE_POINT
|
| | |
|
| | | /*
|
| | | Basic usage :
|
| | |
|
| | | half4 color = tex2D(_MainTex, i.uv);
|
| | | half3 aces = unity_to_ACES(color.rgb);
|
| | | half3 oces = RRT(aces);
|
| | | half3 odt = ODT_RGBmonitor_100nits_dim(oces);
|
| | | return half4(odt, color.a);
|
| | |
|
| | | If you want to customize the white point, uncomment the previous define and set uniforms accordingly:
|
| | |
|
| | | float whitePoint = 48f; // Default ACES value
|
| | | material.SetFloat("CINEMA_WHITE", whitePoint);
|
| | | material.SetFloat("CINEMA_DARK", whitePoint / 2400f);
|
| | | */
|
| | |
|
| | | #include "Common.cginc"
|
| | |
|
| | | #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 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;
|
| | | }
|
| | |
|
| | | //
|
| | | // 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;
|
| | | }
|
| | |
|
| | | 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 (x < 0.00003051757) ? (log2(0.00001525878 + x * 0.5) + 9.72) / 17.52 : (log2(x) + 9.72) / 17.52;
|
| | |
|
| | | /*
|
| | | 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)
|
| | | //
|
| | | half3 ACES_to_ACEScg(half3 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)
|
| | | //
|
| | | half3 ACEScg_to_ACES(half3 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-10;
|
| | | half mi = Min3(rgb);
|
| | | half ma = Max3(rgb);
|
| | | 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 chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b));
|
| | | 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 / UNITY_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 + sign(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
|
| | | float xCheck = x;
|
| | | if (xCheck <= 0.0) xCheck = 0.00006103515; // = pow(2.0, -14.0);
|
| | |
|
| | | 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 = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
|
| | | int j = knot_coord;
|
| | | half t = knot_coord - 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 = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
|
| | | int j = knot_coord;
|
| | | half t = knot_coord - 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);
|
| | | }
|
| | |
|
| | | half segmented_spline_c9_fwd(half x)
|
| | | {
|
| | | const half coefsLow[10] = { -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414 }; // coefs for B-spline between minPoint and midPoint (units of log luminance)
|
| | | const half coefsHigh[10] = { 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 }; // coefs for B-spline between midPoint and maxPoint (units of log luminance)
|
| | | const half2 minPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(-6.5)), 0.02); // {luminance, luminance} linear extension below this
|
| | | const half2 midPoint = half2(segmented_spline_c5_fwd(0.18), 4.8); // {luminance, luminance}
|
| | | const half2 maxPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(6.5)), 48.0); // {luminance, luminance} linear extension above this
|
| | | const half slopeLow = 0.0; // log-log slope of low linear extension
|
| | | const half slopeHigh = 0.04; // log-log slope of high linear extension
|
| | |
|
| | | 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(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 = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
|
| | | int j = knot_coord;
|
| | | half t = knot_coord - 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 = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
|
| | | int j = knot_coord;
|
| | | half t = knot_coord - 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);
|
| | | }
|
| | |
|
| | | 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 rgbOces = mul(AP1_2_AP0_MAT, rgbPost);
|
| | |
|
| | | return rgbOces;
|
| | | }
|
| | |
|
| | | //
|
| | | // 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)
|
| | | {
|
| | | half3 XYZ = mul(AP1_2_XYZ_MAT, 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_sRGB(half3 x)
|
| | | {
|
| | | return (x <= 0.0031308 ? (x * 12.9232102) : 1.055 * pow(x, 1.0 / 2.4) - 0.055);
|
| | | }
|
| | |
|
| | | 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;
|
| | | }
|
| | |
|
| | | #if defined(CUSTOM_WHITE_POINT)
|
| | | half CINEMA_WHITE;
|
| | | half CINEMA_BLACK;
|
| | | #else
|
| | | static const half CINEMA_WHITE = 48.0;
|
| | | static const half CINEMA_BLACK = CINEMA_WHITE / 2400.0;
|
| | | #endif
|
| | |
|
| | | 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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)
|
| | | {
|
| | | // 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);
|
| | | rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
|
| | | rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
|
| | |
|
| | | // 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;
|
| | | }
|
| | |
|
| | | #endif // __ACES__
|