tree sway and shadow improvements
[WindSway-HDRP.git] / Library / PackageCache / com.unity.postprocessing@2.1.2 / PostProcessing / Shaders / ACES.hlsl
blob9f77efeab65500d64758471d740fde4429f57e81
1 #ifndef __ACES__
2 #define __ACES__
4 /**
5  * https://github.com/ampas/aces-dev
6  *
7  * Academy Color Encoding System (ACES) software and tools are provided by the
8  * Academy under the following terms and conditions: A worldwide, royalty-free,
9  * non-exclusive right to copy, modify, create derivatives, and use, in source and
10  * binary forms, is hereby granted, subject to acceptance of this license.
11  *
12  * Copyright 2015 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.).
13  * Portions contributed by others as indicated. All rights reserved.
14  *
15  * Performance of any of the aforementioned acts indicates acceptance to be bound
16  * by the following terms and conditions:
17  *
18  * * Copies of source code, in whole or in part, must retain the above copyright
19  * notice, this list of conditions and the Disclaimer of Warranty.
20  *
21  * * Use in binary form must retain the above copyright notice, this list of
22  * conditions and the Disclaimer of Warranty in the documentation and/or other
23  * materials provided with the distribution.
24  *
25  * * Nothing in this license shall be deemed to grant any rights to trademarks,
26  * copyrights, patents, trade secrets or any other intellectual property of
27  * A.M.P.A.S. or any contributors, except as expressly stated herein.
28  *
29  * * Neither the name "A.M.P.A.S." nor the name of any other contributors to this
30  * software may be used to endorse or promote products derivative of or based on
31  * this software without express prior written permission of A.M.P.A.S. or the
32  * contributors, as appropriate.
33  *
34  * This license shall be construed pursuant to the laws of the State of
35  * California, and any disputes related thereto shall be subject to the
36  * jurisdiction of the courts therein.
37  *
38  * Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS
39  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
40  * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
41  * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY
42  * CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43  * SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
45  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
46  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
47  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
48  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49  *
50  * WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY
51  * DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR
52  * OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR
53  * APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR
54  * UNDISCLOSED.
55  */
57 #include "StdLib.hlsl"
59 #define ACEScc_MAX      1.4679964
60 #define ACEScc_MIDGRAY  0.4135884
63 // Precomputed matrices (pre-transposed)
64 // See https://github.com/ampas/aces-dev/blob/master/transforms/ctl/README-MATRIX.md
66 static const half3x3 sRGB_2_AP0 = {
67     0.4397010, 0.3829780, 0.1773350,
68     0.0897923, 0.8134230, 0.0967616,
69     0.0175440, 0.1115440, 0.8707040
72 static const half3x3 sRGB_2_AP1 = {
73     0.61319, 0.33951, 0.04737,
74     0.07021, 0.91634, 0.01345,
75     0.02062, 0.10957, 0.86961
78 static const half3x3 AP0_2_sRGB = {
79     2.52169, -1.13413, -0.38756,
80     -0.27648, 1.37272, -0.09624,
81     -0.01538, -0.15298, 1.16835,
84 static const half3x3 AP1_2_sRGB = {
85     1.70505, -0.62179, -0.08326,
86     -0.13026, 1.14080, -0.01055,
87     -0.02400, -0.12897, 1.15297,
90 static const half3x3 AP0_2_AP1_MAT = {
91      1.4514393161, -0.2365107469, -0.2149285693,
92     -0.0765537734,  1.1762296998, -0.0996759264,
93      0.0083161484, -0.0060324498,  0.9977163014
96 static const half3x3 AP1_2_AP0_MAT = {
97      0.6954522414, 0.1406786965, 0.1638690622,
98      0.0447945634, 0.8596711185, 0.0955343182,
99     -0.0055258826, 0.0040252103, 1.0015006723
102 static const half3x3 AP1_2_XYZ_MAT = {
103      0.6624541811, 0.1340042065, 0.1561876870,
104      0.2722287168, 0.6740817658, 0.0536895174,
105     -0.0055746495, 0.0040607335, 1.0103391003
108 static const half3x3 XYZ_2_AP1_MAT = {
109      1.6410233797, -0.3248032942, -0.2364246952,
110     -0.6636628587,  1.6153315917,  0.0167563477,
111      0.0117218943, -0.0082844420,  0.9883948585
114 static const half3x3 XYZ_2_REC709_MAT = {
115      3.2409699419, -1.5373831776, -0.4986107603,
116     -0.9692436363,  1.8759675015,  0.0415550574,
117      0.0556300797, -0.2039769589,  1.0569715142
120 static const half3x3 XYZ_2_REC2020_MAT = {
121      1.7166511880, -0.3556707838, -0.2533662814,
122     -0.6666843518,  1.6164812366,  0.0157685458,
123      0.0176398574, -0.0427706133,  0.9421031212
126 static const half3x3 XYZ_2_DCIP3_MAT = {
127      2.7253940305, -1.0180030062, -0.4401631952,
128     -0.7951680258,  1.6897320548,  0.0226471906,
129      0.0412418914, -0.0876390192,  1.1009293786
132 static const half3 AP1_RGB2Y = half3(0.272229, 0.674082, 0.0536895);
134 static const half3x3 RRT_SAT_MAT = {
135     0.9708890, 0.0269633, 0.00214758,
136     0.0108892, 0.9869630, 0.00214758,
137     0.0108892, 0.0269633, 0.96214800
140 static const half3x3 ODT_SAT_MAT = {
141     0.949056, 0.0471857, 0.00375827,
142     0.019056, 0.9771860, 0.00375827,
143     0.019056, 0.0471857, 0.93375800
146 static const half3x3 D60_2_D65_CAT = {
147      0.98722400, -0.00611327, 0.0159533,
148     -0.00759836,  1.00186000, 0.0053302,
149      0.00307257, -0.00509595, 1.0816800
153 // Unity to ACES
155 // converts Unity raw (sRGB primaries) to
156 //          ACES2065-1 (AP0 w/ linear encoding)
158 half3 unity_to_ACES(half3 x)
160     x = mul(sRGB_2_AP0, x);
161     return x;
165 // ACES to Unity
167 // converts ACES2065-1 (AP0 w/ linear encoding)
168 //          Unity raw (sRGB primaries) to
170 half3 ACES_to_unity(half3 x)
172     x = mul(AP0_2_sRGB, x);
173     return x;
177 // Unity to ACEScg
179 // converts Unity raw (sRGB primaries) to
180 //          ACEScg (AP1 w/ linear encoding)
182 half3 unity_to_ACEScg(half3 x)
184     x = mul(sRGB_2_AP1, x);
185     return x;
189 // ACEScg to Unity
191 // converts ACEScg (AP1 w/ linear encoding) to
192 //          Unity raw (sRGB primaries)
194 half3 ACEScg_to_unity(half3 x)
196     x = mul(AP1_2_sRGB, x);
197     return x;
201 // ACES Color Space Conversion - ACES to ACEScc
203 // converts ACES2065-1 (AP0 w/ linear encoding) to
204 //          ACEScc (AP1 w/ logarithmic encoding)
206 // This transform follows the formulas from section 4.4 in S-2014-003
208 half ACES_to_ACEScc(half x)
210     if (x <= 0.0)
211         return -0.35828683; // = (log2(pow(2.0, -15.0) * 0.5) + 9.72) / 17.52
212     else if (x < pow(2.0, -15.0))
213         return (log2(pow(2.0, -16.0) + x * 0.5) + 9.72) / 17.52;
214     else // (x >= pow(2.0, -15.0))
215         return (log2(x) + 9.72) / 17.52;
218 half3 ACES_to_ACEScc(half3 x)
220     x = clamp(x, 0.0, HALF_MAX);
222     // x is clamped to [0, HALF_MAX], skip the <= 0 check
223     return (x < 0.00003051757) ? (log2(0.00001525878 + x * 0.5) + 9.72) / 17.52 : (log2(x) + 9.72) / 17.52;
225     /*
226     return half3(
227         ACES_to_ACEScc(x.r),
228         ACES_to_ACEScc(x.g),
229         ACES_to_ACEScc(x.b)
230     );
231     */
235 // ACES Color Space Conversion - ACEScc to ACES
237 // converts ACEScc (AP1 w/ ACESlog encoding) to
238 //          ACES2065-1 (AP0 w/ linear encoding)
240 // This transform follows the formulas from section 4.4 in S-2014-003
242 half ACEScc_to_ACES(half x)
244     // TODO: Optimize me
245     if (x < -0.3013698630) // (9.72 - 15) / 17.52
246         return (pow(2.0, x * 17.52 - 9.72) - pow(2.0, -16.0)) * 2.0;
247     else if (x < (log2(HALF_MAX) + 9.72) / 17.52)
248         return pow(2.0, x * 17.52 - 9.72);
249     else // (x >= (log2(HALF_MAX) + 9.72) / 17.52)
250         return HALF_MAX;
253 half3 ACEScc_to_ACES(half3 x)
255     return half3(
256         ACEScc_to_ACES(x.r),
257         ACEScc_to_ACES(x.g),
258         ACEScc_to_ACES(x.b)
259     );
263 // ACES Color Space Conversion - ACES to ACEScg
265 // converts ACES2065-1 (AP0 w/ linear encoding) to
266 //          ACEScg (AP1 w/ linear encoding)
268 half3 ACES_to_ACEScg(half3 x)
270     return mul(AP0_2_AP1_MAT, x);
274 // ACES Color Space Conversion - ACEScg to ACES
276 // converts ACEScg (AP1 w/ linear encoding) to
277 //          ACES2065-1 (AP0 w/ linear encoding)
279 half3 ACEScg_to_ACES(half3 x)
281     return mul(AP1_2_AP0_MAT, x);
285 // Reference Rendering Transform (RRT)
287 //   Input is ACES
288 //   Output is OCES
290 half rgb_2_saturation(half3 rgb)
292     const half TINY = 1e-4;
293     half mi = Min3(rgb.r, rgb.g, rgb.b);
294     half ma = Max3(rgb.r, rgb.g, rgb.b);
295     return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2);
298 half rgb_2_yc(half3 rgb)
300     const half ycRadiusWeight = 1.75;
302     // Converts RGB to a luminance proxy, here called YC
303     // YC is ~ Y + K * Chroma
304     // Constant YC is a cone-shaped surface in RGB space, with the tip on the
305     // neutral axis, towards white.
306     // YC is normalized: RGB 1 1 1 maps to YC = 1
307     //
308     // ycRadiusWeight defaults to 1.75, although can be overridden in function
309     // call to rgb_2_yc
310     // ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral
311     // of same value
312     // ycRadiusWeight = 2 -> YC for pure red, green, blue  == YC for  neutral of
313     // same value.
315     half r = rgb.x;
316     half g = rgb.y;
317     half b = rgb.z;
318     half chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b));
319     return (b + g + r + ycRadiusWeight * chroma) / 3.0;
322 half rgb_2_hue(half3 rgb)
324     // Returns a geometric hue angle in degrees (0-360) based on RGB values.
325     // For neutral colors, hue is undefined and the function will return a quiet NaN value.
326     half hue;
327     if (rgb.x == rgb.y && rgb.y == rgb.z)
328         hue = 0.0; // RGB triplets where RGB are equal have an undefined hue
329     else
330         hue = (180.0 / PI) * atan2(sqrt(3.0) * (rgb.y - rgb.z), 2.0 * rgb.x - rgb.y - rgb.z);
332     if (hue < 0.0) hue = hue + 360.0;
334     return hue;
337 half center_hue(half hue, half centerH)
339     half hueCentered = hue - centerH;
340     if (hueCentered < -180.0) hueCentered = hueCentered + 360.0;
341     else if (hueCentered > 180.0) hueCentered = hueCentered - 360.0;
342     return hueCentered;
345 half sigmoid_shaper(half x)
347     // Sigmoid function in the range 0 to 1 spanning -2 to +2.
349     half t = max(1.0 - abs(x / 2.0), 0.0);
350     half y = 1.0 + FastSign(x) * (1.0 - t * t);
352     return y / 2.0;
355 half glow_fwd(half ycIn, half glowGainIn, half glowMid)
357     half glowGainOut;
359     if (ycIn <= 2.0 / 3.0 * glowMid)
360         glowGainOut = glowGainIn;
361     else if (ycIn >= 2.0 * glowMid)
362         glowGainOut = 0.0;
363     else
364         glowGainOut = glowGainIn * (glowMid / ycIn - 1.0 / 2.0);
366     return glowGainOut;
370 half cubic_basis_shaper
372     half x,
373     half w   // full base width of the shaper function (in degrees)
376     half M[4][4] = {
377         { -1.0 / 6,  3.0 / 6, -3.0 / 6,  1.0 / 6 },
378         {  3.0 / 6, -6.0 / 6,  3.0 / 6,  0.0 / 6 },
379         { -3.0 / 6,  0.0 / 6,  3.0 / 6,  0.0 / 6 },
380         {  1.0 / 6,  4.0 / 6,  1.0 / 6,  0.0 / 6 }
381     };
383     half knots[5] = {
384         -w / 2.0,
385         -w / 4.0,
386              0.0,
387          w / 4.0,
388          w / 2.0
389     };
391     half y = 0.0;
392     if ((x > knots[0]) && (x < knots[4]))
393     {
394         half knot_coord = (x - knots[0]) * 4.0 / w;
395         int j = knot_coord;
396         half t = knot_coord - j;
398         half monomials[4] = { t*t*t, t*t, t, 1.0 };
400         // (if/else structure required for compatibility with CTL < v1.5.)
401         if (j == 3)
402         {
403             y = monomials[0] * M[0][0] + monomials[1] * M[1][0] +
404                 monomials[2] * M[2][0] + monomials[3] * M[3][0];
405         }
406         else if (j == 2)
407         {
408             y = monomials[0] * M[0][1] + monomials[1] * M[1][1] +
409                 monomials[2] * M[2][1] + monomials[3] * M[3][1];
410         }
411         else if (j == 1)
412         {
413             y = monomials[0] * M[0][2] + monomials[1] * M[1][2] +
414                 monomials[2] * M[2][2] + monomials[3] * M[3][2];
415         }
416         else if (j == 0)
417         {
418             y = monomials[0] * M[0][3] + monomials[1] * M[1][3] +
419                 monomials[2] * M[2][3] + monomials[3] * M[3][3];
420         }
421         else
422         {
423             y = 0.0;
424         }
425     }
427     return y * 3.0 / 2.0;
431 static const half3x3 M = {
432      0.5, -1.0, 0.5,
433     -1.0,  1.0, 0.0,
434      0.5,  0.5, 0.0
437 half segmented_spline_c5_fwd(half x)
439     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)
440     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)
441     const half2 minPoint = half2(0.18 * exp2(-15.0), 0.0001); // {luminance, luminance} linear extension below this
442     const half2 midPoint = half2(0.18, 0.48); // {luminance, luminance}
443     const half2 maxPoint = half2(0.18 * exp2(18.0), 10000.0); // {luminance, luminance} linear extension above this
444     const half slopeLow = 0.0; // log-log slope of low linear extension
445     const half slopeHigh = 0.0; // log-log slope of high linear extension
447     const int N_KNOTS_LOW = 4;
448     const int N_KNOTS_HIGH = 4;
450     // Check for negatives or zero before taking the log. If negative or zero,
451     // set to ACESMIN.1
452     float xCheck = x;
453     if (xCheck <= 0.0) xCheck = 0.00006103515; // = pow(2.0, -14.0);
455     half logx = log10(xCheck);
456     half logy;
458     if (logx <= log10(minPoint.x))
459     {
460         logy = logx * slopeLow + (log10(minPoint.y) - slopeLow * log10(minPoint.x));
461     }
462     else if ((logx > log10(minPoint.x)) && (logx < log10(midPoint.x)))
463     {
464         half knot_coord = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
465         int j = knot_coord;
466         half t = knot_coord - j;
468         half3 cf = half3(coefsLow[j], coefsLow[j + 1], coefsLow[j + 2]);
469         half3 monomials = half3(t * t, t, 1.0);
470         logy = dot(monomials, mul(M, cf));
471     }
472     else if ((logx >= log10(midPoint.x)) && (logx < log10(maxPoint.x)))
473     {
474         half knot_coord = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
475         int j = knot_coord;
476         half t = knot_coord - j;
478         half3 cf = half3(coefsHigh[j], coefsHigh[j + 1], coefsHigh[j + 2]);
479         half3 monomials = half3(t * t, t, 1.0);
480         logy = dot(monomials, mul(M, cf));
481     }
482     else
483     { //if (logIn >= log10(maxPoint.x)) {
484         logy = logx * slopeHigh + (log10(maxPoint.y) - slopeHigh * log10(maxPoint.x));
485     }
487     return pow(10.0, logy);
490 half segmented_spline_c9_fwd(half x)
492     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)
493     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)
494     const half2 minPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(-6.5)), 0.02); // {luminance, luminance} linear extension below this
495     const half2 midPoint = half2(segmented_spline_c5_fwd(0.18), 4.8); // {luminance, luminance}
496     const half2 maxPoint = half2(segmented_spline_c5_fwd(0.18 * exp2(6.5)), 48.0); // {luminance, luminance} linear extension above this
497     const half slopeLow = 0.0; // log-log slope of low linear extension
498     const half slopeHigh = 0.04; // log-log slope of high linear extension
500     const int N_KNOTS_LOW = 8;
501     const int N_KNOTS_HIGH = 8;
503     // Check for negatives or zero before taking the log. If negative or zero,
504     // set to OCESMIN.
505     half xCheck = x;
506     if (xCheck <= 0.0) xCheck = 1e-4;
508     half logx = log10(xCheck);
509     half logy;
511     if (logx <= log10(minPoint.x))
512     {
513         logy = logx * slopeLow + (log10(minPoint.y) - slopeLow * log10(minPoint.x));
514     }
515     else if ((logx > log10(minPoint.x)) && (logx < log10(midPoint.x)))
516     {
517         half knot_coord = (N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
518         int j = knot_coord;
519         half t = knot_coord - j;
521         half3 cf = half3(coefsLow[j], coefsLow[j + 1], coefsLow[j + 2]);
522         half3 monomials = half3(t * t, t, 1.0);
523         logy = dot(monomials, mul(M, cf));
524     }
525     else if ((logx >= log10(midPoint.x)) && (logx < log10(maxPoint.x)))
526     {
527         half knot_coord = (N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
528         int j = knot_coord;
529         half t = knot_coord - j;
531         half3 cf = half3(coefsHigh[j], coefsHigh[j + 1], coefsHigh[j + 2]);
532         half3 monomials = half3(t * t, t, 1.0);
533         logy = dot(monomials, mul(M, cf));
534     }
535     else
536     { //if (logIn >= log10(maxPoint.x)) {
537         logy = logx * slopeHigh + (log10(maxPoint.y) - slopeHigh * log10(maxPoint.x));
538     }
540     return pow(10.0, logy);
543 static const half RRT_GLOW_GAIN = 0.05;
544 static const half RRT_GLOW_MID = 0.08;
546 static const half RRT_RED_SCALE = 0.82;
547 static const half RRT_RED_PIVOT = 0.03;
548 static const half RRT_RED_HUE = 0.0;
549 static const half RRT_RED_WIDTH = 135.0;
551 static const half RRT_SAT_FACTOR = 0.96;
553 half3 RRT(half3 aces)
555     // --- Glow module --- //
556     half saturation = rgb_2_saturation(aces);
557     half ycIn = rgb_2_yc(aces);
558     half s = sigmoid_shaper((saturation - 0.4) / 0.2);
559     half addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
560     aces *= addedGlow;
562     // --- Red modifier --- //
563     half hue = rgb_2_hue(aces);
564     half centeredHue = center_hue(hue, RRT_RED_HUE);
565     half hueWeight;
566     {
567         //hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH);
568         hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
569         hueWeight *= hueWeight;
570     }
572     aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1.0 - RRT_RED_SCALE);
574     // --- ACES to RGB rendering space --- //
575     aces = clamp(aces, 0.0, HALF_MAX);  // avoids saturated negative colors from becoming positive in the matrix
576     half3 rgbPre = mul(AP0_2_AP1_MAT, aces);
577     rgbPre = clamp(rgbPre, 0, HALF_MAX);
579     // --- Global desaturation --- //
580     //rgbPre = mul(RRT_SAT_MAT, rgbPre);
581     rgbPre = lerp(dot(rgbPre, AP1_RGB2Y).xxx, rgbPre, RRT_SAT_FACTOR.xxx);
583     // --- Apply the tonescale independently in rendering-space RGB --- //
584     half3 rgbPost;
585     rgbPost.x = segmented_spline_c5_fwd(rgbPre.x);
586     rgbPost.y = segmented_spline_c5_fwd(rgbPre.y);
587     rgbPost.z = segmented_spline_c5_fwd(rgbPre.z);
589     // --- RGB rendering space to OCES --- //
590     half3 rgbOces = mul(AP1_2_AP0_MAT, rgbPost);
592     return rgbOces;
596 // Output Device Transform
598 half3 Y_2_linCV(half3 Y, half Ymax, half Ymin)
600     return (Y - Ymin) / (Ymax - Ymin);
603 half3 XYZ_2_xyY(half3 XYZ)
605     half divisor = max(dot(XYZ, (1.0).xxx), 1e-4);
606     return half3(XYZ.xy / divisor, XYZ.y);
609 half3 xyY_2_XYZ(half3 xyY)
611     half m = xyY.z / max(xyY.y, 1e-4);
612     half3 XYZ = half3(xyY.xz, (1.0 - xyY.x - xyY.y));
613     XYZ.xz *= m;
614     return XYZ;
617 static const half DIM_SURROUND_GAMMA = 0.9811;
619 half3 darkSurround_to_dimSurround(half3 linearCV)
621     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
623     half3 xyY = XYZ_2_xyY(XYZ);
624     xyY.z = clamp(xyY.z, 0.0, HALF_MAX);
625     xyY.z = pow(xyY.z, DIM_SURROUND_GAMMA);
626     XYZ = xyY_2_XYZ(xyY);
628     return mul(XYZ_2_AP1_MAT, XYZ);
631 half moncurve_r(half y, half gamma, half offs)
633     // Reverse monitor curve
634     half x;
635     const half yb = pow(offs * gamma / ((gamma - 1.0) * (1.0 + offs)), gamma);
636     const half rs = pow((gamma - 1.0) / offs, gamma - 1.0) * pow((1.0 + offs) / gamma, gamma);
637     if (y >= yb)
638         x = (1.0 + offs) * pow(y, 1.0 / gamma) - offs;
639     else
640         x = y * rs;
641     return x;
644 half bt1886_r(half L, half gamma, half Lw, half Lb)
646     // The reference EOTF specified in Rec. ITU-R BT.1886
647     // L = a(max[(V+b),0])^g
648     half a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma);
649     half b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma));
650     half V = pow(max(L / a, 0.0), 1.0 / gamma) - b;
651     return V;
654 half roll_white_fwd(
655     half x,       // color value to adjust (white scaled to around 1.0)
656     half new_wht, // white adjustment (e.g. 0.9 for 10% darkening)
657     half width    // adjusted width (e.g. 0.25 for top quarter of the tone scale)
658     )
660     const half x0 = -1.0;
661     const half x1 = x0 + width;
662     const half y0 = -new_wht;
663     const half y1 = x1;
664     const half m1 = (x1 - x0);
665     const half a = y0 - y1 + m1;
666     const half b = 2.0 * (y1 - y0) - m1;
667     const half c = y0;
668     const half t = (-x - x0) / (x1 - x0);
669     half o = 0.0;
670     if (t < 0.0)
671         o = -(t * b + c);
672     else if (t > 1.0)
673         o = x;
674     else
675         o = -((t * a + b) * t + c);
676     return o;
679 half3 linear_to_sRGB(half3 x)
681     return (x <= 0.0031308 ? (x * 12.9232102) : 1.055 * pow(x, 1.0 / 2.4) - 0.055);
684 half3 linear_to_bt1886(half3 x, half gamma, half Lw, half Lb)
686     // Good enough approximation for now, may consider using the exact formula instead
687     // TODO: Experiment
688     return pow(max(x, 0.0), 1.0 / 2.4);
690     // Correct implementation (Reference EOTF specified in Rec. ITU-R BT.1886) :
691     // L = a(max[(V+b),0])^g
692     half invgamma = 1.0 / gamma;
693     half p_Lw = pow(Lw, invgamma);
694     half p_Lb = pow(Lb, invgamma);
695     half3 a = pow(p_Lw - p_Lb, gamma).xxx;
696     half3 b = (p_Lb / p_Lw - p_Lb).xxx;
697     half3 V = pow(max(x / a, 0.0), invgamma.xxx) - b;
698     return V;
701 static const half CINEMA_WHITE = 48.0;
702 static const half CINEMA_BLACK = CINEMA_WHITE / 2400.0;
703 static const half ODT_SAT_FACTOR = 0.93;
705 // <ACEStransformID>ODT.Academy.RGBmonitor_100nits_dim.a1.0.3</ACEStransformID>
706 // <ACESuserName>ACES 1.0 Output - sRGB</ACESuserName>
709 // Output Device Transform - RGB computer monitor
713 // Summary :
714 //  This transform is intended for mapping OCES onto a desktop computer monitor
715 //  typical of those used in motion picture visual effects production. These
716 //  monitors may occasionally be referred to as "sRGB" displays, however, the
717 //  monitor for which this transform is designed does not exactly match the
718 //  specifications in IEC 61966-2-1:1999.
720 //  The assumed observer adapted white is D65, and the viewing environment is
721 //  that of a dim surround.
723 //  The monitor specified is intended to be more typical of those found in
724 //  visual effects production.
726 // Device Primaries :
727 //  Primaries are those specified in Rec. ITU-R BT.709
728 //  CIE 1931 chromaticities:  x         y         Y
729 //              Red:          0.64      0.33
730 //              Green:        0.3       0.6
731 //              Blue:         0.15      0.06
732 //              White:        0.3127    0.329     100 cd/m^2
734 // Display EOTF :
735 //  The reference electro-optical transfer function specified in
736 //  IEC 61966-2-1:1999.
738 // Signal Range:
739 //    This transform outputs full range code values.
741 // Assumed observer adapted white point:
742 //         CIE 1931 chromaticities:    x            y
743 //                                     0.3127       0.329
745 // Viewing Environment:
746 //   This ODT has a compensation for viewing environment variables more typical
747 //   of those associated with video mastering.
749 half3 ODT_RGBmonitor_100nits_dim(half3 oces)
751     // OCES to RGB rendering space
752     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
754     // Apply the tonescale independently in rendering-space RGB
755     half3 rgbPost;
756     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
757     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
758     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
760     // Scale luminance to linear code value
761     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
763      // Apply gamma adjustment to compensate for dim surround
764     linearCV = darkSurround_to_dimSurround(linearCV);
766     // Apply desaturation to compensate for luminance difference
767     //linearCV = mul(ODT_SAT_MAT, linearCV);
768     linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
770     // Convert to display primary encoding
771     // Rendering space RGB to XYZ
772     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
774     // Apply CAT from ACES white point to assumed observer adapted white point
775     XYZ = mul(D60_2_D65_CAT, XYZ);
777     // CIE XYZ to display primaries
778     linearCV = mul(XYZ_2_REC709_MAT, XYZ);
780     // Handle out-of-gamut values
781     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
782     linearCV = saturate(linearCV);
784     // TODO: Revisit when it is possible to deactivate Unity default framebuffer encoding
785     // with sRGB opto-electrical transfer function (OETF).
786     /*
787     // Encode linear code values with transfer function
788     half3 outputCV;
789     // moncurve_r with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB)
790     const half DISPGAMMA = 2.4;
791     const half OFFSET = 0.055;
792     outputCV.x = moncurve_r(linearCV.x, DISPGAMMA, OFFSET);
793     outputCV.y = moncurve_r(linearCV.y, DISPGAMMA, OFFSET);
794     outputCV.z = moncurve_r(linearCV.z, DISPGAMMA, OFFSET);
796     outputCV = linear_to_sRGB(linearCV);
797     */
799     // Unity already draws to a sRGB target
800     return linearCV;
803 // <ACEStransformID>ODT.Academy.RGBmonitor_D60sim_100nits_dim.a1.0.3</ACEStransformID>
804 // <ACESuserName>ACES 1.0 Output - sRGB (D60 sim.)</ACESuserName>
807 // Output Device Transform - RGB computer monitor (D60 simulation)
811 // Summary :
812 //  This transform is intended for mapping OCES onto a desktop computer monitor
813 //  typical of those used in motion picture visual effects production. These
814 //  monitors may occasionally be referred to as "sRGB" displays, however, the
815 //  monitor for which this transform is designed does not exactly match the
816 //  specifications in IEC 61966-2-1:1999.
818 //  The assumed observer adapted white is D60, and the viewing environment is
819 //  that of a dim surround.
821 //  The monitor specified is intended to be more typical of those found in
822 //  visual effects production.
824 // Device Primaries :
825 //  Primaries are those specified in Rec. ITU-R BT.709
826 //  CIE 1931 chromaticities:  x         y         Y
827 //              Red:          0.64      0.33
828 //              Green:        0.3       0.6
829 //              Blue:         0.15      0.06
830 //              White:        0.3127    0.329     100 cd/m^2
832 // Display EOTF :
833 //  The reference electro-optical transfer function specified in
834 //  IEC 61966-2-1:1999.
836 // Signal Range:
837 //    This transform outputs full range code values.
839 // Assumed observer adapted white point:
840 //         CIE 1931 chromaticities:    x            y
841 //                                     0.32168      0.33767
843 // Viewing Environment:
844 //   This ODT has a compensation for viewing environment variables more typical
845 //   of those associated with video mastering.
847 half3 ODT_RGBmonitor_D60sim_100nits_dim(half3 oces)
849     // OCES to RGB rendering space
850     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
852     // Apply the tonescale independently in rendering-space RGB
853     half3 rgbPost;
854     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
855     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
856     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
858     // Scale luminance to linear code value
859     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
861     // --- Compensate for different white point being darker  --- //
862     // This adjustment is to correct an issue that exists in ODTs where the device
863     // is calibrated to a white chromaticity other than D60. In order to simulate
864     // D60 on such devices, unequal code values are sent to the display to achieve
865     // neutrals at D60. In order to produce D60 on a device calibrated to the DCI
866     // white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
867     // 0.351) the red channel is higher than green and blue to compensate for the
868     // "greenish" DCI white. This is the correct behavior but it means that as
869     // highlight increase, the red channel will hit the device maximum first and
870     // clip, resulting in a chromaticity shift as the green and blue channels
871     // continue to increase.
872     // To avoid this clipping error, a slight scale factor is applied to allow the
873     // ODTs to simulate D60 within the D65 calibration white point.
875     // Scale and clamp white to avoid casted highlights due to D60 simulation
876     const half SCALE = 0.955;
877     linearCV = min(linearCV, 1.0) * SCALE;
879     // Apply gamma adjustment to compensate for dim surround
880     linearCV = darkSurround_to_dimSurround(linearCV);
882     // Apply desaturation to compensate for luminance difference
883     //linearCV = mul(ODT_SAT_MAT, linearCV);
884     linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
886     // Convert to display primary encoding
887     // Rendering space RGB to XYZ
888     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
890     // CIE XYZ to display primaries
891     linearCV = mul(XYZ_2_REC709_MAT, XYZ);
893     // Handle out-of-gamut values
894     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
895     linearCV = saturate(linearCV);
897     // TODO: Revisit when it is possible to deactivate Unity default framebuffer encoding
898     // with sRGB opto-electrical transfer function (OETF).
899     /*
900     // Encode linear code values with transfer function
901     half3 outputCV;
902     // moncurve_r with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB)
903     const half DISPGAMMA = 2.4;
904     const half OFFSET = 0.055;
905     outputCV.x = moncurve_r(linearCV.x, DISPGAMMA, OFFSET);
906     outputCV.y = moncurve_r(linearCV.y, DISPGAMMA, OFFSET);
907     outputCV.z = moncurve_r(linearCV.z, DISPGAMMA, OFFSET);
909     outputCV = linear_to_sRGB(linearCV);
910     */
912     // Unity already draws to a sRGB target
913     return linearCV;
916 // <ACEStransformID>ODT.Academy.Rec709_100nits_dim.a1.0.3</ACEStransformID>
917 // <ACESuserName>ACES 1.0 Output - Rec.709</ACESuserName>
920 // Output Device Transform - Rec709
924 // Summary :
925 //  This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
926 //  that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
927 //  adapted white is D65, and the viewing environment is a dim surround.
929 //  A possible use case for this transform would be HDTV/video mastering.
931 // Device Primaries :
932 //  Primaries are those specified in Rec. ITU-R BT.709
933 //  CIE 1931 chromaticities:  x         y         Y
934 //              Red:          0.64      0.33
935 //              Green:        0.3       0.6
936 //              Blue:         0.15      0.06
937 //              White:        0.3127    0.329     100 cd/m^2
939 // Display EOTF :
940 //  The reference electro-optical transfer function specified in
941 //  Rec. ITU-R BT.1886.
943 // Signal Range:
944 //    By default, this transform outputs full range code values. If instead a
945 //    SMPTE "legal" signal is desired, there is a runtime flag to output
946 //    SMPTE legal signal. In ctlrender, this can be achieved by appending
947 //    '-param1 legalRange 1' after the '-ctl odt.ctl' string.
949 // Assumed observer adapted white point:
950 //         CIE 1931 chromaticities:    x            y
951 //                                     0.3127       0.329
953 // Viewing Environment:
954 //   This ODT has a compensation for viewing environment variables more typical
955 //   of those associated with video mastering.
957 half3 ODT_Rec709_100nits_dim(half3 oces)
959     // OCES to RGB rendering space
960     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
962     // Apply the tonescale independently in rendering-space RGB
963     half3 rgbPost;
964     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
965     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
966     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
968     // Scale luminance to linear code value
969     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
971     // Apply gamma adjustment to compensate for dim surround
972     linearCV = darkSurround_to_dimSurround(linearCV);
974     // Apply desaturation to compensate for luminance difference
975     //linearCV = mul(ODT_SAT_MAT, linearCV);
976     linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
978     // Convert to display primary encoding
979     // Rendering space RGB to XYZ
980     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
982     // Apply CAT from ACES white point to assumed observer adapted white point
983     XYZ = mul(D60_2_D65_CAT, XYZ);
985     // CIE XYZ to display primaries
986     linearCV = mul(XYZ_2_REC709_MAT, XYZ);
988     // Handle out-of-gamut values
989     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
990     linearCV = saturate(linearCV);
992     // Encode linear code values with transfer function
993     const half DISPGAMMA = 2.4;
994     const half L_W = 1.0;
995     const half L_B = 0.0;
996     half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
998     // TODO: Implement support for legal range.
1000     // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1001     // by default which will result in double perceptual encoding, thus for now if one want to use
1002     // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1003     // compensate for Unity default behaviour.
1005     return outputCV;
1008 // <ACEStransformID>ODT.Academy.Rec709_D60sim_100nits_dim.a1.0.3</ACEStransformID>
1009 // <ACESuserName>ACES 1.0 Output - Rec.709 (D60 sim.)</ACESuserName>
1012 // Output Device Transform - Rec709 (D60 simulation)
1016 // Summary :
1017 //  This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
1018 //  that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
1019 //  adapted white is D60, and the viewing environment is a dim surround.
1021 //  A possible use case for this transform would be cinema "soft-proofing".
1023 // Device Primaries :
1024 //  Primaries are those specified in Rec. ITU-R BT.709
1025 //  CIE 1931 chromaticities:  x         y         Y
1026 //              Red:          0.64      0.33
1027 //              Green:        0.3       0.6
1028 //              Blue:         0.15      0.06
1029 //              White:        0.3127    0.329     100 cd/m^2
1031 // Display EOTF :
1032 //  The reference electro-optical transfer function specified in
1033 //  Rec. ITU-R BT.1886.
1035 // Signal Range:
1036 //    By default, this transform outputs full range code values. If instead a
1037 //    SMPTE "legal" signal is desired, there is a runtime flag to output
1038 //    SMPTE legal signal. In ctlrender, this can be achieved by appending
1039 //    '-param1 legalRange 1' after the '-ctl odt.ctl' string.
1041 // Assumed observer adapted white point:
1042 //         CIE 1931 chromaticities:    x            y
1043 //                                     0.32168      0.33767
1045 // Viewing Environment:
1046 //   This ODT has a compensation for viewing environment variables more typical
1047 //   of those associated with video mastering.
1049 half3 ODT_Rec709_D60sim_100nits_dim(half3 oces)
1051     // OCES to RGB rendering space
1052     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1054     // Apply the tonescale independently in rendering-space RGB
1055     half3 rgbPost;
1056     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
1057     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
1058     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
1060     // Scale luminance to linear code value
1061     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1063     // --- Compensate for different white point being darker  --- //
1064     // This adjustment is to correct an issue that exists in ODTs where the device
1065     // is calibrated to a white chromaticity other than D60. In order to simulate
1066     // D60 on such devices, unequal code values must be sent to the display to achieve
1067     // the chromaticities of D60. More specifically, in order to produce D60 on a device
1068     // calibrated to a D65 white point (i.e. equal code values yield CIE x,y
1069     // chromaticities of 0.3127, 0.329) the red channel must be slightly higher than
1070     // that of green and blue in order to compensate for the relatively more "blue-ish"
1071     // D65 white. This unequalness of color channels is the correct behavior but it
1072     // means that as neutral highlights increase, the red channel will hit the
1073     // device maximum first and clip, resulting in a small chromaticity shift as the
1074     // green and blue channels continue to increase to their maximums.
1075     // To avoid this clipping error, a slight scale factor is applied to allow the
1076     // ODTs to simulate D60 within the D65 calibration white point.
1078     // Scale and clamp white to avoid casted highlights due to D60 simulation
1079     const half SCALE = 0.955;
1080     linearCV = min(linearCV, 1.0) * SCALE;
1082     // Apply gamma adjustment to compensate for dim surround
1083     linearCV = darkSurround_to_dimSurround(linearCV);
1085     // Apply desaturation to compensate for luminance difference
1086     //linearCV = mul(ODT_SAT_MAT, linearCV);
1087     linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1089     // Convert to display primary encoding
1090     // Rendering space RGB to XYZ
1091     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1093     // CIE XYZ to display primaries
1094     linearCV = mul(XYZ_2_REC709_MAT, XYZ);
1096     // Handle out-of-gamut values
1097     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1098     linearCV = saturate(linearCV);
1100     // Encode linear code values with transfer function
1101     const half DISPGAMMA = 2.4;
1102     const half L_W = 1.0;
1103     const half L_B = 0.0;
1104     half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
1106     // TODO: Implement support for legal range.
1108     // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1109     // by default which will result in double perceptual encoding, thus for now if one want to use
1110     // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1111     // compensate for Unity default behaviour.
1113     return outputCV;
1116 // <ACEStransformID>ODT.Academy.Rec2020_100nits_dim.a1.0.3</ACEStransformID>
1117 // <ACESuserName>ACES 1.0 Output - Rec.2020</ACESuserName>
1120 // Output Device Transform - Rec2020
1124 // Summary :
1125 //  This transform is intended for mapping OCES onto a Rec.2020 broadcast
1126 //  monitor that is calibrated to a D65 white point at 100 cd/m^2. The assumed
1127 //  observer adapted white is D65, and the viewing environment is that of a dim
1128 //  surround.
1130 //  A possible use case for this transform would be UHDTV/video mastering.
1132 // Device Primaries :
1133 //  Primaries are those specified in Rec. ITU-R BT.2020
1134 //  CIE 1931 chromaticities:  x         y         Y
1135 //              Red:          0.708     0.292
1136 //              Green:        0.17      0.797
1137 //              Blue:         0.131     0.046
1138 //              White:        0.3127    0.329     100 cd/m^2
1140 // Display EOTF :
1141 //  The reference electro-optical transfer function specified in
1142 //  Rec. ITU-R BT.1886.
1144 // Signal Range:
1145 //    By default, this transform outputs full range code values. If instead a
1146 //    SMPTE "legal" signal is desired, there is a runtime flag to output
1147 //    SMPTE legal signal. In ctlrender, this can be achieved by appending
1148 //    '-param1 legalRange 1' after the '-ctl odt.ctl' string.
1150 // Assumed observer adapted white point:
1151 //         CIE 1931 chromaticities:    x            y
1152 //                                     0.3127       0.329
1154 // Viewing Environment:
1155 //   This ODT has a compensation for viewing environment variables more typical
1156 //   of those associated with video mastering.
1159 half3 ODT_Rec2020_100nits_dim(half3 oces)
1161     // OCES to RGB rendering space
1162     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1164     // Apply the tonescale independently in rendering-space RGB
1165     half3 rgbPost;
1166     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
1167     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
1168     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
1170     // Scale luminance to linear code value
1171     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1173     // Apply gamma adjustment to compensate for dim surround
1174     linearCV = darkSurround_to_dimSurround(linearCV);
1176     // Apply desaturation to compensate for luminance difference
1177     //linearCV = mul(ODT_SAT_MAT, linearCV);
1178     linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1180     // Convert to display primary encoding
1181     // Rendering space RGB to XYZ
1182     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1184     // Apply CAT from ACES white point to assumed observer adapted white point
1185     XYZ = mul(D60_2_D65_CAT, XYZ);
1187     // CIE XYZ to display primaries
1188     linearCV = mul(XYZ_2_REC2020_MAT, XYZ);
1190     // Handle out-of-gamut values
1191     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1192     linearCV = saturate(linearCV);
1194     // Encode linear code values with transfer function
1195     const half DISPGAMMA = 2.4;
1196     const half L_W = 1.0;
1197     const half L_B = 0.0;
1198     half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
1200     // TODO: Implement support for legal range.
1202     // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1203     // by default which will result in double perceptual encoding, thus for now if one want to use
1204     // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1205     // compensate for Unity default behaviour.
1207     return outputCV;
1210 // <ACEStransformID>ODT.Academy.P3DCI_48nits.a1.0.3</ACEStransformID>
1211 // <ACESuserName>ACES 1.0 Output - P3-DCI</ACESuserName>
1214 // Output Device Transform - P3DCI (D60 Simulation)
1218 // Summary :
1219 //  This transform is intended for mapping OCES onto a P3 digital cinema
1220 //  projector that is calibrated to a DCI white point at 48 cd/m^2. The assumed
1221 //  observer adapted white is D60, and the viewing environment is that of a dark
1222 //  theater.
1224 // Device Primaries :
1225 //  CIE 1931 chromaticities:  x         y         Y
1226 //              Red:          0.68      0.32
1227 //              Green:        0.265     0.69
1228 //              Blue:         0.15      0.06
1229 //              White:        0.314     0.351     48 cd/m^2
1231 // Display EOTF :
1232 //  Gamma: 2.6
1234 // Assumed observer adapted white point:
1235 //         CIE 1931 chromaticities:    x            y
1236 //                                     0.32168      0.33767
1238 // Viewing Environment:
1239 //  Environment specified in SMPTE RP 431-2-2007
1241 half3 ODT_P3DCI_48nits(half3 oces)
1243     // OCES to RGB rendering space
1244     half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1246     // Apply the tonescale independently in rendering-space RGB
1247     half3 rgbPost;
1248     rgbPost.x = segmented_spline_c9_fwd(rgbPre.x);
1249     rgbPost.y = segmented_spline_c9_fwd(rgbPre.y);
1250     rgbPost.z = segmented_spline_c9_fwd(rgbPre.z);
1252     // Scale luminance to linear code value
1253     half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1255     // --- Compensate for different white point being darker  --- //
1256     // This adjustment is to correct an issue that exists in ODTs where the device
1257     // is calibrated to a white chromaticity other than D60. In order to simulate
1258     // D60 on such devices, unequal code values are sent to the display to achieve
1259     // neutrals at D60. In order to produce D60 on a device calibrated to the DCI
1260     // white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
1261     // 0.351) the red channel is higher than green and blue to compensate for the
1262     // "greenish" DCI white. This is the correct behavior but it means that as
1263     // highlight increase, the red channel will hit the device maximum first and
1264     // clip, resulting in a chromaticity shift as the green and blue channels
1265     // continue to increase.
1266     // To avoid this clipping error, a slight scale factor is applied to allow the
1267     // ODTs to simulate D60 within the D65 calibration white point. However, the
1268     // magnitude of the scale factor required for the P3DCI ODT was considered too
1269     // large. Therefore, the scale factor was reduced and the additional required
1270     // compression was achieved via a reshaping of the highlight rolloff in
1271     // conjunction with the scale. The shape of this rolloff was determined
1272     // throught subjective experiments and deemed to best reproduce the
1273     // "character" of the highlights in the P3D60 ODT.
1275     // Roll off highlights to avoid need for as much scaling
1276     const half NEW_WHT = 0.918;
1277     const half ROLL_WIDTH = 0.5;
1278     linearCV.x = roll_white_fwd(linearCV.x, NEW_WHT, ROLL_WIDTH);
1279     linearCV.y = roll_white_fwd(linearCV.y, NEW_WHT, ROLL_WIDTH);
1280     linearCV.z = roll_white_fwd(linearCV.z, NEW_WHT, ROLL_WIDTH);
1282     // Scale and clamp white to avoid casted highlights due to D60 simulation
1283     const half SCALE = 0.96;
1284     linearCV = min(linearCV, NEW_WHT) * SCALE;
1286     // Convert to display primary encoding
1287     // Rendering space RGB to XYZ
1288     half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1290     // CIE XYZ to display primaries
1291     linearCV = mul(XYZ_2_DCIP3_MAT, XYZ);
1293     // Handle out-of-gamut values
1294     // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1295     linearCV = saturate(linearCV);
1297     // Encode linear code values with transfer function
1298     const half DISPGAMMA = 2.6;
1299     half3 outputCV = pow(linearCV, 1.0 / DISPGAMMA);
1301     // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1302     // by default which will result in double perceptual encoding, thus for now if one want to use
1303     // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1304     // compensate for Unity default behaviour.
1306     return outputCV;
1309 #endif // __ACES__