A game about forced loneliness, made by TACStudios
1#ifndef __ACES__
2#define __ACES__
3
4#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH || defined(UNITY_UNIFIED_SHADER_PRECISION_MODEL)
5#pragma warning (disable : 3205) // conversion of larger type to smaller
6#endif
7
8/**
9 * https://github.com/ampas/aces-dev
10 *
11 * Academy Color Encoding System (ACES) software and tools are provided by the
12 * Academy under the following terms and conditions: A worldwide, royalty-free,
13 * non-exclusive right to copy, modify, create derivatives, and use, in source and
14 * binary forms, is hereby granted, subject to acceptance of this license.
15 *
16 * Copyright 2015 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.).
17 * Portions contributed by others as indicated. All rights reserved.
18 *
19 * Performance of any of the aforementioned acts indicates acceptance to be bound
20 * by the following terms and conditions:
21 *
22 * * Copies of source code, in whole or in part, must retain the above copyright
23 * notice, this list of conditions and the Disclaimer of Warranty.
24 *
25 * * Use in binary form must retain the above copyright notice, this list of
26 * conditions and the Disclaimer of Warranty in the documentation and/or other
27 * materials provided with the distribution.
28 *
29 * * Nothing in this license shall be deemed to grant any rights to trademarks,
30 * copyrights, patents, trade secrets or any other intellectual property of
31 * A.M.P.A.S. or any contributors, except as expressly stated herein.
32 *
33 * * Neither the name "A.M.P.A.S." nor the name of any other contributors to this
34 * software may be used to endorse or promote products derivative of or based on
35 * this software without express prior written permission of A.M.P.A.S. or the
36 * contributors, as appropriate.
37 *
38 * This license shall be construed pursuant to the laws of the State of
39 * California, and any disputes related thereto shall be subject to the
40 * jurisdiction of the courts therein.
41 *
42 * Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS
43 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
44 * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
45 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY
46 * CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47 * SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
48 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
49 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
50 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
51 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
52 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53 *
54 * WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY
55 * DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR
56 * OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR
57 * APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED OR
58 * UNDISCLOSED.
59 */
60
61#include "Common.hlsl"
62
63#define ACEScc_MAX 1.4679964
64#define ACEScc_MIDGRAY 0.4135884
65
66//
67// Precomputed matrices (pre-transposed)
68// See https://github.com/ampas/aces-dev/blob/master/transforms/ctl/README-MATRIX.md
69//
70static const half3x3 sRGB_2_AP0 = {
71 0.4397010, 0.3829780, 0.1773350,
72 0.0897923, 0.8134230, 0.0967616,
73 0.0175440, 0.1115440, 0.8707040
74};
75
76static const half3x3 sRGB_2_AP1 = {
77 0.61319, 0.33951, 0.04737,
78 0.07021, 0.91634, 0.01345,
79 0.02062, 0.10957, 0.86961
80};
81
82static const half3x3 AP0_2_sRGB = {
83 2.52169, -1.13413, -0.38756,
84 -0.27648, 1.37272, -0.09624,
85 -0.01538, -0.15298, 1.16835,
86};
87
88static const half3x3 AP1_2_sRGB = {
89 1.70505, -0.62179, -0.08326,
90 -0.13026, 1.14080, -0.01055,
91 -0.02400, -0.12897, 1.15297,
92};
93
94static const half3x3 AP0_2_AP1_MAT = {
95 1.4514393161, -0.2365107469, -0.2149285693,
96 -0.0765537734, 1.1762296998, -0.0996759264,
97 0.0083161484, -0.0060324498, 0.9977163014
98};
99
100static const half3x3 AP1_2_AP0_MAT = {
101 0.6954522414, 0.1406786965, 0.1638690622,
102 0.0447945634, 0.8596711185, 0.0955343182,
103 -0.0055258826, 0.0040252103, 1.0015006723
104};
105
106static const half3x3 AP1_2_XYZ_MAT = {
107 0.6624541811, 0.1340042065, 0.1561876870,
108 0.2722287168, 0.6740817658, 0.0536895174,
109 -0.0055746495, 0.0040607335, 1.0103391003
110};
111
112static const half3x3 XYZ_2_AP1_MAT = {
113 1.6410233797, -0.3248032942, -0.2364246952,
114 -0.6636628587, 1.6153315917, 0.0167563477,
115 0.0117218943, -0.0082844420, 0.9883948585
116};
117
118static const half3x3 XYZ_2_REC709_MAT = {
119 3.2409699419, -1.5373831776, -0.4986107603,
120 -0.9692436363, 1.8759675015, 0.0415550574,
121 0.0556300797, -0.2039769589, 1.0569715142
122};
123
124static const half3x3 XYZ_2_REC2020_MAT = {
125 1.7166511880, -0.3556707838, -0.2533662814,
126 -0.6666843518, 1.6164812366, 0.0157685458,
127 0.0176398574, -0.0427706133, 0.9421031212
128};
129
130static const half3x3 XYZ_2_DCIP3_MAT = {
131 2.7253940305, -1.0180030062, -0.4401631952,
132 -0.7951680258, 1.6897320548, 0.0226471906,
133 0.0412418914, -0.0876390192, 1.1009293786
134};
135
136static const half3x3 XYZ_2_P3D65_MAT = {
137 2.4934969119, -0.9313836179, -0.4027107845,
138 -0.8294889696, 1.7626640603, 0.0236246858,
139 0.0358458302, -0.0761723893, 0.9568845240
140};
141
142static const half3 AP1_RGB2Y = half3(0.272229, 0.674082, 0.0536895);
143
144static const half3x3 RRT_SAT_MAT = {
145 0.9708890, 0.0269633, 0.00214758,
146 0.0108892, 0.9869630, 0.00214758,
147 0.0108892, 0.0269633, 0.96214800
148};
149
150static const half3x3 ODT_SAT_MAT = {
151 0.949056, 0.0471857, 0.00375827,
152 0.019056, 0.9771860, 0.00375827,
153 0.019056, 0.0471857, 0.93375800
154};
155
156static const half3x3 D60_2_D65_CAT = {
157 0.98722400, -0.00611327, 0.0159533,
158 -0.00759836, 1.00186000, 0.0053302,
159 0.00307257, -0.00509595, 1.0816800
160};
161
162//
163// Unity to ACES
164//
165// converts Unity raw (sRGB primaries) to
166// ACES2065-1 (AP0 w/ linear encoding)
167//
168half3 unity_to_ACES(half3 x)
169{
170 x = mul(sRGB_2_AP0, x);
171 return x;
172}
173
174//
175// ACES to Unity
176//
177// converts ACES2065-1 (AP0 w/ linear encoding)
178// Unity raw (sRGB primaries) to
179//
180half3 ACES_to_unity(half3 x)
181{
182 x = mul(AP0_2_sRGB, x);
183 return x;
184}
185
186//
187// Unity to ACEScg
188//
189// converts Unity raw (sRGB primaries) to
190// ACEScg (AP1 w/ linear encoding)
191//
192half3 unity_to_ACEScg(half3 x)
193{
194 x = mul(sRGB_2_AP1, x);
195 return x;
196}
197
198//
199// ACEScg to Unity
200//
201// converts ACEScg (AP1 w/ linear encoding) to
202// Unity raw (sRGB primaries)
203//
204half3 ACEScg_to_unity(half3 x)
205{
206 x = mul(AP1_2_sRGB, x);
207 return x;
208}
209
210half3 ACEScg_to_Rec2020(half3 x)
211{
212 half3 xyz = mul(AP1_2_XYZ_MAT, x);
213 return mul(XYZ_2_REC2020_MAT, xyz);
214}
215
216//
217// ACES Color Space Conversion - ACES to ACEScc
218//
219// converts ACES2065-1 (AP0 w/ linear encoding) to
220// ACEScc (AP1 w/ logarithmic encoding)
221//
222// This transform follows the formulas from section 4.4 in S-2014-003
223//
224half ACES_to_ACEScc(half x)
225{
226 if (x <= 0.0)
227 return -0.35828683; // = (log2(pow(2.0, -15.0) * 0.5) + 9.72) / 17.52
228 else if (x < pow(2.0, -15.0))
229 return (log2(pow(2.0, -16.0) + x * 0.5) + 9.72) / 17.52;
230 else // (x >= pow(2.0, -15.0))
231 return (log2(x) + 9.72) / 17.52;
232}
233
234half ACES_to_ACEScc_fast(half x)
235{
236 // x is clamped to [0, HALF_MAX], skip the <= 0 check
237 return (x < 0.00003051757) ? (log2(0.00001525878 + x * 0.5) + 9.72) / 17.52 : (log2(x) + 9.72) / 17.52;
238}
239
240half3 ACES_to_ACEScc(half3 x)
241{
242 x = clamp(x, 0.0, HALF_MAX);
243
244 // x is clamped to [0, HALF_MAX], skip the <= 0 check
245 return half3(
246 ACES_to_ACEScc_fast(x.r),
247 ACES_to_ACEScc_fast(x.g),
248 ACES_to_ACEScc_fast(x.b)
249 );
250
251 /*
252 return half3(
253 ACES_to_ACEScc(x.r),
254 ACES_to_ACEScc(x.g),
255 ACES_to_ACEScc(x.b)
256 );
257 */
258}
259
260//
261// ACES Color Space Conversion - ACEScc to ACES
262//
263// converts ACEScc (AP1 w/ ACESlog encoding) to
264// ACES2065-1 (AP0 w/ linear encoding)
265//
266// This transform follows the formulas from section 4.4 in S-2014-003
267//
268half ACEScc_to_ACES(half x)
269{
270 // TODO: Optimize me
271 if (x < -0.3013698630) // (9.72 - 15) / 17.52
272 return (pow(2.0, x * 17.52 - 9.72) - pow(2.0, -16.0)) * 2.0;
273 else if (x < (log2(HALF_MAX) + 9.72) / 17.52)
274 return pow(2.0, x * 17.52 - 9.72);
275 else // (x >= (log2(HALF_MAX) + 9.72) / 17.52)
276 return HALF_MAX;
277}
278
279half3 ACEScc_to_ACES(half3 x)
280{
281 return half3(
282 ACEScc_to_ACES(x.r),
283 ACEScc_to_ACES(x.g),
284 ACEScc_to_ACES(x.b)
285 );
286}
287
288//
289// ACES Color Space Conversion - ACES to ACEScg
290//
291// converts ACES2065-1 (AP0 w/ linear encoding) to
292// ACEScg (AP1 w/ linear encoding)
293//
294// Uses float3 to avoid going out of half-precision bounds
295//
296float3 ACES_to_ACEScg(float3 x)
297{
298 return mul(AP0_2_AP1_MAT, x);
299}
300
301//
302// ACES Color Space Conversion - ACEScg to ACES
303//
304// converts ACEScg (AP1 w/ linear encoding) to
305// ACES2065-1 (AP0 w/ linear encoding)
306//
307// Uses float3 to avoid going out of half-precision bounds
308//
309float3 ACEScg_to_ACES(float3 x)
310{
311 return mul(AP1_2_AP0_MAT, x);
312}
313
314//
315// Reference Rendering Transform (RRT)
316//
317// Input is ACES
318// Output is OCES
319//
320half rgb_2_saturation(half3 rgb)
321{
322 const half TINY = 1e-4;
323 half mi = Min3(rgb.r, rgb.g, rgb.b);
324 half ma = Max3(rgb.r, rgb.g, rgb.b);
325 return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2);
326}
327
328half rgb_2_yc(half3 rgb)
329{
330 const half ycRadiusWeight = 1.75;
331
332 // Converts RGB to a luminance proxy, here called YC
333 // YC is ~ Y + K * Chroma
334 // Constant YC is a cone-shaped surface in RGB space, with the tip on the
335 // neutral axis, towards white.
336 // YC is normalized: RGB 1 1 1 maps to YC = 1
337 //
338 // ycRadiusWeight defaults to 1.75, although can be overridden in function
339 // call to rgb_2_yc
340 // ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral
341 // of same value
342 // ycRadiusWeight = 2 -> YC for pure red, green, blue == YC for neutral of
343 // same value.
344
345 half r = rgb.x;
346 half g = rgb.y;
347 half b = rgb.z;
348 half k = b * (b - g) + g * (g - r) + r * (r - b);
349 k = max(k, 0.0); // Clamp to avoid precision issue causing k < 0, making sqrt(k) undefined
350#if defined(SHADER_API_SWITCH)
351 half chroma = k == 0.0 ? 0.0 : sqrt(k); // Avoid Nan
352#else
353 half chroma = sqrt(k);
354#endif
355 return (b + g + r + ycRadiusWeight * chroma) / 3.0;
356}
357
358half rgb_2_hue(half3 rgb)
359{
360 // Returns a geometric hue angle in degrees (0-360) based on RGB values.
361 // For neutral colors, hue is undefined and the function will return a quiet NaN value.
362 half hue;
363 if (rgb.x == rgb.y && rgb.y == rgb.z)
364 hue = 0.0; // RGB triplets where RGB are equal have an undefined hue
365 else
366 hue = (180.0 / PI) * atan2(sqrt(3.0) * (rgb.y - rgb.z), 2.0 * rgb.x - rgb.y - rgb.z);
367
368 if (hue < 0.0) hue = hue + 360.0;
369
370 return hue;
371}
372
373half center_hue(half hue, half centerH)
374{
375 half hueCentered = hue - centerH;
376 if (hueCentered < -180.0) hueCentered = hueCentered + 360.0;
377 else if (hueCentered > 180.0) hueCentered = hueCentered - 360.0;
378 return hueCentered;
379}
380
381half sigmoid_shaper(half x)
382{
383 // Sigmoid function in the range 0 to 1 spanning -2 to +2.
384
385 half t = max(1.0 - abs(x / 2.0), 0.0);
386 half y = 1.0 + half(FastSign(x)) * (1.0 - t * t);
387
388 return y / 2.0;
389}
390
391half glow_fwd(half ycIn, half glowGainIn, half glowMid)
392{
393 half glowGainOut;
394
395 if (ycIn <= 2.0 / 3.0 * glowMid)
396 glowGainOut = glowGainIn;
397 else if (ycIn >= 2.0 * glowMid)
398 glowGainOut = 0.0;
399 else
400 glowGainOut = glowGainIn * (glowMid / ycIn - 1.0 / 2.0);
401
402 return glowGainOut;
403}
404
405/*
406half cubic_basis_shaper
407(
408 half x,
409 half w // full base width of the shaper function (in degrees)
410)
411{
412 half M[4][4] = {
413 { -1.0 / 6, 3.0 / 6, -3.0 / 6, 1.0 / 6 },
414 { 3.0 / 6, -6.0 / 6, 3.0 / 6, 0.0 / 6 },
415 { -3.0 / 6, 0.0 / 6, 3.0 / 6, 0.0 / 6 },
416 { 1.0 / 6, 4.0 / 6, 1.0 / 6, 0.0 / 6 }
417 };
418
419 half knots[5] = {
420 -w / 2.0,
421 -w / 4.0,
422 0.0,
423 w / 4.0,
424 w / 2.0
425 };
426
427 half y = 0.0;
428 if ((x > knots[0]) && (x < knots[4]))
429 {
430 half knot_coord = (x - knots[0]) * 4.0 / w;
431 int j = knot_coord;
432 half t = knot_coord - j;
433
434 half monomials[4] = { t*t*t, t*t, t, 1.0 };
435
436 // (if/else structure required for compatibility with CTL < v1.5.)
437 if (j == 3)
438 {
439 y = monomials[0] * M[0][0] + monomials[1] * M[1][0] +
440 monomials[2] * M[2][0] + monomials[3] * M[3][0];
441 }
442 else if (j == 2)
443 {
444 y = monomials[0] * M[0][1] + monomials[1] * M[1][1] +
445 monomials[2] * M[2][1] + monomials[3] * M[3][1];
446 }
447 else if (j == 1)
448 {
449 y = monomials[0] * M[0][2] + monomials[1] * M[1][2] +
450 monomials[2] * M[2][2] + monomials[3] * M[3][2];
451 }
452 else if (j == 0)
453 {
454 y = monomials[0] * M[0][3] + monomials[1] * M[1][3] +
455 monomials[2] * M[2][3] + monomials[3] * M[3][3];
456 }
457 else
458 {
459 y = 0.0;
460 }
461 }
462
463 return y * 3.0 / 2.0;
464}
465*/
466
467static const half3x3 M = {
468 0.5, -1.0, 0.5,
469 -1.0, 1.0, 0.0,
470 0.5, 0.5, 0.0
471};
472
473half segmented_spline_c5_fwd(half x)
474{
475 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)
476 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)
477 const half2 minPoint = half2(0.18 * exp2(-15.0), 0.0001); // {luminance, luminance} linear extension below this
478 const half2 midPoint = half2(0.18, 0.48); // {luminance, luminance}
479 const half2 maxPoint = half2(0.18 * exp2(18.0), 10000.0); // {luminance, luminance} linear extension above this
480 const half slopeLow = 0.0; // log-log slope of low linear extension
481 const half slopeHigh = 0.0; // log-log slope of high linear extension
482
483 const int N_KNOTS_LOW = 4;
484 const int N_KNOTS_HIGH = 4;
485
486 // Check for negatives or zero before taking the log. If negative or zero,
487 // set to ACESMIN.1
488 half xCheck = x;
489 if (xCheck <= 0.0) xCheck = HALF_MIN;
490
491 half logx = log10(xCheck);
492 half logy;
493
494 if (logx <= log10(minPoint.x))
495 {
496 logy = logx * slopeLow + (log10(minPoint.y) - slopeLow * log10(minPoint.x));
497 }
498 else if ((logx > log10(minPoint.x)) && (logx < log10(midPoint.x)))
499 {
500 half knot_coord = half(N_KNOTS_LOW - 1) * (logx - log10(minPoint.x)) / (log10(midPoint.x) - log10(minPoint.x));
501 int j = knot_coord;
502 half t = knot_coord - half(j);
503
504 half3 cf = half3(coefsLow[j], coefsLow[j + 1], coefsLow[j + 2]);
505 half3 monomials = half3(t * t, t, 1.0);
506 logy = dot(monomials, mul(M, cf));
507 }
508 else if ((logx >= log10(midPoint.x)) && (logx < log10(maxPoint.x)))
509 {
510 half knot_coord = half(N_KNOTS_HIGH - 1) * (logx - log10(midPoint.x)) / (log10(maxPoint.x) - log10(midPoint.x));
511 int j = knot_coord;
512 half t = knot_coord - half(j);
513
514 half3 cf = half3(coefsHigh[j], coefsHigh[j + 1], coefsHigh[j + 2]);
515 half3 monomials = half3(t * t, t, 1.0);
516 logy = dot(monomials, mul(M, cf));
517 }
518 else
519 { //if (logIn >= log10(maxPoint.x)) {
520 logy = logx * slopeHigh + (log10(maxPoint.y) - slopeHigh * log10(maxPoint.x));
521 }
522
523 return pow(10.0, logy);
524}
525
526struct SegmentedSplineParams_c9
527{
528 float coefsLow[10]; // coefs for B-spline between minPoint and midPoint (units of log luminance)
529 float coefsHigh[10]; // coefs for B-spline between midPoint and maxPoint (units of log luminance)
530 half2 minPoint; // {luminance, luminance} linear extension below this
531 half2 midPoint; // {luminance, luminance}
532 half2 maxPoint; // {luminance, luminance} linear extension above this
533 float slopeLow; // log-log slope of low linear extension
534 float slopeHigh; // log-log slope of high linear extension
535};
536
537half segmented_spline_c9_fwd(half x, SegmentedSplineParams_c9 params)
538{
539 const int N_KNOTS_LOW = 8;
540 const int N_KNOTS_HIGH = 8;
541
542 // Check for negatives or zero before taking the log. If negative or zero,
543 // set to OCESMIN.
544 half xCheck = x;
545 if (xCheck <= 0.0) xCheck = 1e-4;
546
547 half logx = log10(xCheck);
548 half logy;
549
550 if (logx <= log10(params.minPoint.x))
551 {
552 logy = logx * half(params.slopeLow) + (log10(params.minPoint.y) - half(params.slopeLow) * log10(params.minPoint.x));
553 }
554 else if ((logx > log10(params.minPoint.x)) && (logx < log10(params.midPoint.x)))
555 {
556 half knot_coord = half(N_KNOTS_LOW - 1) * (logx - log10(params.minPoint.x)) / (log10(params.midPoint.x) - log10(params.minPoint.x));
557 int j = knot_coord;
558 half t = knot_coord - half(j);
559
560 half3 cf = half3(params.coefsLow[j], params.coefsLow[j + 1], params.coefsLow[j + 2]);
561 half3 monomials = half3(t * t, t, 1.0);
562 logy = dot(monomials, mul(M, cf));
563 }
564 else if ((logx >= log10(params.midPoint.x)) && (logx < log10(params.maxPoint.x)))
565 {
566 half knot_coord = half(N_KNOTS_HIGH - 1) * (logx - log10(params.midPoint.x)) / (log10(params.maxPoint.x) - log10(params.midPoint.x));
567 int j = knot_coord;
568 half t = knot_coord - half(j);
569
570 half3 cf = half3(params.coefsHigh[j], params.coefsHigh[j + 1], params.coefsHigh[j + 2]);
571 half3 monomials = half3(t * t, t, 1.0);
572 logy = dot(monomials, mul(M, cf));
573 }
574 else
575 { //if (logIn >= log10(maxPoint.x)) {
576 logy = logx * half(params.slopeHigh) + (log10(params.maxPoint.y) - half(params.slopeHigh) * log10(params.maxPoint.x));
577 }
578
579 return pow(10.0, logy);
580}
581
582
583// > 48 Nits from https://github.com/ampas/aces-dev/blob/dev/transforms/ctl/lib/ACESlib.Tonescales.ctl
584SegmentedSplineParams_c9 GetSplineParams_ODT48Nits()
585{
586 const SegmentedSplineParams_c9 ODT_48nits =
587 {
588 // coefsLow[10]
589 { -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414},
590 // coefsHigh[10]
591 { 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 },
592 {segmented_spline_c5_fwd(0.18*pow(2.,-6.5)), 0.02}, // minPoint
593 {segmented_spline_c5_fwd(0.18), 4.8}, // midPoint
594 {segmented_spline_c5_fwd(0.18*pow(2.,6.5)), 48.0}, // maxPoint
595 0.0, // slopeLow
596 0.04 // slopeHigh
597 };
598 return ODT_48nits;
599}
600
601SegmentedSplineParams_c9 GetSplineParams_ODT1000Nits()
602{
603 const SegmentedSplineParams_c9 ODT_1000nits =
604 {
605 // coefsLow[10]
606 { -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
607 // coefsHigh[10]
608 { 0.8089132070, 1.1910867930, 1.5683, 1.9483, 2.3083, 2.6384, 2.8595, 2.9872608805, 3.0127391195, 3.0127391195 },
609 {segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
610 {segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
611 {segmented_spline_c5_fwd(0.18*pow(2.,10.)), 1000.0}, // maxPoint
612 3.0, // slopeLow
613 0.06 // slopeHigh
614 };
615 return ODT_1000nits;
616}
617
618SegmentedSplineParams_c9 GetSplineParams_ODT2000Nits()
619{
620 const SegmentedSplineParams_c9 ODT_2000nits =
621 {
622 // coefsLow[10]
623 { -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
624 // coefsHigh[10]
625 { 0.8019952042, 1.1980047958, 1.5943000000, 1.9973000000, 2.3783000000, 2.7684000000, 3.0515000000, 3.2746293562, 3.3274306351, 3.3274306351 },
626 {segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
627 {segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
628 {segmented_spline_c5_fwd(0.18*pow(2.,11.)), 2000.0}, // maxPoint
629 3.0, // slopeLow
630 0.12 // slopeHigh
631 };
632 return ODT_2000nits;
633}
634
635SegmentedSplineParams_c9 GetSplineParams_ODT4000Nits()
636{
637 const SegmentedSplineParams_c9 ODT_4000nits =
638 {
639 // coefsLow[10]
640 { -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
641 // coefsHigh[10]
642 { 0.7973186613, 1.2026813387, 1.6093000000, 2.0108000000, 2.4148000000, 2.8179000000, 3.1725000000, 3.5344995451, 3.6696204376, 3.6696204376 },
643 {segmented_spline_c5_fwd(0.18*pow(2.,-12.)), 0.0001}, // minPoint
644 {segmented_spline_c5_fwd(0.18), 10.0}, // midPoint
645 {segmented_spline_c5_fwd(0.18*pow(2.,12.)), 4000.0}, // maxPoint
646 3.0, // slopeLow
647 0.3 // slopeHigh
648 };
649 return ODT_4000nits;
650}
651
652
653half segmented_spline_c9_fwd(half x)
654{
655 return segmented_spline_c9_fwd(x, GetSplineParams_ODT48Nits());
656}
657
658static const half RRT_GLOW_GAIN = 0.05;
659static const half RRT_GLOW_MID = 0.08;
660
661static const half RRT_RED_SCALE = 0.82;
662static const half RRT_RED_PIVOT = 0.03;
663static const half RRT_RED_HUE = 0.0;
664static const half RRT_RED_WIDTH = 135.0;
665
666static const half RRT_SAT_FACTOR = 0.96;
667
668half3 RRT(half3 aces)
669{
670 // --- Glow module --- //
671 half saturation = rgb_2_saturation(aces);
672 half ycIn = rgb_2_yc(aces);
673 half s = sigmoid_shaper((saturation - 0.4) / 0.2);
674 half addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
675 aces *= addedGlow;
676
677 // --- Red modifier --- //
678 half hue = rgb_2_hue(aces);
679 half centeredHue = center_hue(hue, RRT_RED_HUE);
680 half hueWeight;
681 {
682 //hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH);
683 hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
684 hueWeight *= hueWeight;
685 }
686
687 aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1.0 - RRT_RED_SCALE);
688
689 // --- ACES to RGB rendering space --- //
690 aces = clamp(aces, 0.0, HALF_MAX); // avoids saturated negative colors from becoming positive in the matrix
691 half3 rgbPre = mul(AP0_2_AP1_MAT, aces);
692 rgbPre = clamp(rgbPre, 0, HALF_MAX);
693
694 // --- Global desaturation --- //
695 //rgbPre = mul(RRT_SAT_MAT, rgbPre);
696 rgbPre = lerp(dot(rgbPre, AP1_RGB2Y).xxx, rgbPre, RRT_SAT_FACTOR.xxx);
697
698 // --- Apply the tonescale independently in rendering-space RGB --- //
699 half3 rgbPost;
700 rgbPost.x = segmented_spline_c5_fwd(rgbPre.x);
701 rgbPost.y = segmented_spline_c5_fwd(rgbPre.y);
702 rgbPost.z = segmented_spline_c5_fwd(rgbPre.z);
703
704 // --- RGB rendering space to OCES --- //
705 half3 outputVal = mul(AP1_2_AP0_MAT, rgbPost);
706
707 return outputVal;
708}
709
710//
711// Output Device Transform
712//
713half3 Y_2_linCV(half3 Y, half Ymax, half Ymin)
714{
715 return (Y - Ymin) / (Ymax - Ymin);
716}
717
718half3 XYZ_2_xyY(half3 XYZ)
719{
720 half divisor = max(dot(XYZ, (1.0).xxx), 1e-4);
721 return half3(XYZ.xy / divisor, XYZ.y);
722}
723
724half3 xyY_2_XYZ(half3 xyY)
725{
726 half m = xyY.z / max(xyY.y, 1e-4);
727 half3 XYZ = half3(xyY.xz, (1.0 - xyY.x - xyY.y));
728 XYZ.xz *= m;
729 return XYZ;
730}
731
732static const half DIM_SURROUND_GAMMA = 0.9811;
733
734half3 darkSurround_to_dimSurround(half3 linearCV)
735{
736 // Extra conversions to float3/half3 are required to avoid floating-point precision issues on some platforms.
737 half3 XYZ = (half3)mul(AP1_2_XYZ_MAT, (float3)linearCV);
738
739 half3 xyY = XYZ_2_xyY(XYZ);
740 xyY.z = clamp(xyY.z, 0.0, HALF_MAX);
741 xyY.z = pow(xyY.z, DIM_SURROUND_GAMMA);
742 XYZ = xyY_2_XYZ(xyY);
743
744 return mul(XYZ_2_AP1_MAT, XYZ);
745}
746
747half moncurve_r(half y, half gamma, half offs)
748{
749 // Reverse monitor curve
750 half x;
751 const half yb = pow(offs * gamma / ((gamma - 1.0) * (1.0 + offs)), gamma);
752 const half rs = pow((gamma - 1.0) / offs, gamma - 1.0) * pow((1.0 + offs) / gamma, gamma);
753 if (y >= yb)
754 x = (1.0 + offs) * pow(y, 1.0 / gamma) - offs;
755 else
756 x = y * rs;
757 return x;
758}
759
760half bt1886_r(half L, half gamma, half Lw, half Lb)
761{
762 // The reference EOTF specified in Rec. ITU-R BT.1886
763 // L = a(max[(V+b),0])^g
764 half a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma);
765 half b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma));
766 half V = pow(max(L / a, 0.0), 1.0 / gamma) - b;
767 return V;
768}
769
770half roll_white_fwd(
771 half x, // color value to adjust (white scaled to around 1.0)
772 half new_wht, // white adjustment (e.g. 0.9 for 10% darkening)
773 half width // adjusted width (e.g. 0.25 for top quarter of the tone scale)
774 )
775{
776 const half x0 = -1.0;
777 const half x1 = x0 + width;
778 const half y0 = -new_wht;
779 const half y1 = x1;
780 const half m1 = (x1 - x0);
781 const half a = y0 - y1 + m1;
782 const half b = 2.0 * (y1 - y0) - m1;
783 const half c = y0;
784 const half t = (-x - x0) / (x1 - x0);
785 half o = 0.0;
786 if (t < 0.0)
787 o = -(t * b + c);
788 else if (t > 1.0)
789 o = x;
790 else
791 o = -((t * a + b) * t + c);
792 return o;
793}
794
795half3 linear_to_bt1886(half3 x, half gamma, half Lw, half Lb)
796{
797 // Good enough approximation for now, may consider using the exact formula instead
798 // TODO: Experiment
799 return pow(max(x, 0.0), 1.0 / 2.4);
800
801 // Correct implementation (Reference EOTF specified in Rec. ITU-R BT.1886) :
802 // L = a(max[(V+b),0])^g
803 half invgamma = 1.0 / gamma;
804 half p_Lw = pow(Lw, invgamma);
805 half p_Lb = pow(Lb, invgamma);
806 half3 a = pow(p_Lw - p_Lb, gamma).xxx;
807 half3 b = (p_Lb / p_Lw - p_Lb).xxx;
808 half3 V = pow(max(x / a, 0.0), invgamma.xxx) - b;
809 return V;
810}
811
812static const half CINEMA_WHITE = 48.0;
813static const half CINEMA_BLACK = CINEMA_WHITE / 2400.0;
814static const half ODT_SAT_FACTOR = 0.93;
815
816// <ACEStransformID>ODT.Academy.RGBmonitor_100nits_dim.a1.0.3</ACEStransformID>
817// <ACESuserName>ACES 1.0 Output - sRGB</ACESuserName>
818
819//
820// Output Device Transform - RGB computer monitor
821//
822
823//
824// Summary :
825// This transform is intended for mapping OCES onto a desktop computer monitor
826// typical of those used in motion picture visual effects production. These
827// monitors may occasionally be referred to as "sRGB" displays, however, the
828// monitor for which this transform is designed does not exactly match the
829// specifications in IEC 61966-2-1:1999.
830//
831// The assumed observer adapted white is D65, and the viewing environment is
832// that of a dim surround.
833//
834// The monitor specified is intended to be more typical of those found in
835// visual effects production.
836//
837// Device Primaries :
838// Primaries are those specified in Rec. ITU-R BT.709
839// CIE 1931 chromaticities: x y Y
840// Red: 0.64 0.33
841// Green: 0.3 0.6
842// Blue: 0.15 0.06
843// White: 0.3127 0.329 100 cd/m^2
844//
845// Display EOTF :
846// The reference electro-optical transfer function specified in
847// IEC 61966-2-1:1999.
848//
849// Signal Range:
850// This transform outputs full range code values.
851//
852// Assumed observer adapted white point:
853// CIE 1931 chromaticities: x y
854// 0.3127 0.329
855//
856// Viewing Environment:
857// This ODT has a compensation for viewing environment variables more typical
858// of those associated with video mastering.
859//
860half3 ODT_RGBmonitor_100nits_dim(half3 oces)
861{
862 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
863
864 // OCES to RGB rendering space
865 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
866
867 // Apply the tonescale independently in rendering-space RGB
868 half3 rgbPost;
869 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
870 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
871 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
872
873 // Scale luminance to linear code value
874 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
875
876 // Apply gamma adjustment to compensate for dim surround
877 linearCV = darkSurround_to_dimSurround(linearCV);
878
879 // Apply desaturation to compensate for luminance difference
880 //linearCV = mul(ODT_SAT_MAT, linearCV);
881 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
882
883 // Convert to display primary encoding
884 // Rendering space RGB to XYZ
885 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
886
887 // Apply CAT from ACES white point to assumed observer adapted white point
888 XYZ = mul(D60_2_D65_CAT, XYZ);
889
890 // CIE XYZ to display primaries
891 linearCV = mul(XYZ_2_REC709_MAT, XYZ);
892
893 // Handle out-of-gamut values
894 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
895 linearCV = saturate(linearCV);
896
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);
908
909 outputCV = linear_to_sRGB(linearCV);
910 */
911
912 // Unity already draws to a sRGB target
913 return linearCV;
914}
915
916// <ACEStransformID>ODT.Academy.RGBmonitor_D60sim_100nits_dim.a1.0.3</ACEStransformID>
917// <ACESuserName>ACES 1.0 Output - sRGB (D60 sim.)</ACESuserName>
918
919//
920// Output Device Transform - RGB computer monitor (D60 simulation)
921//
922
923//
924// Summary :
925// This transform is intended for mapping OCES onto a desktop computer monitor
926// typical of those used in motion picture visual effects production. These
927// monitors may occasionally be referred to as "sRGB" displays, however, the
928// monitor for which this transform is designed does not exactly match the
929// specifications in IEC 61966-2-1:1999.
930//
931// The assumed observer adapted white is D60, and the viewing environment is
932// that of a dim surround.
933//
934// The monitor specified is intended to be more typical of those found in
935// visual effects production.
936//
937// Device Primaries :
938// Primaries are those specified in Rec. ITU-R BT.709
939// CIE 1931 chromaticities: x y Y
940// Red: 0.64 0.33
941// Green: 0.3 0.6
942// Blue: 0.15 0.06
943// White: 0.3127 0.329 100 cd/m^2
944//
945// Display EOTF :
946// The reference electro-optical transfer function specified in
947// IEC 61966-2-1:1999.
948//
949// Signal Range:
950// This transform outputs full range code values.
951//
952// Assumed observer adapted white point:
953// CIE 1931 chromaticities: x y
954// 0.32168 0.33767
955//
956// Viewing Environment:
957// This ODT has a compensation for viewing environment variables more typical
958// of those associated with video mastering.
959//
960half3 ODT_RGBmonitor_D60sim_100nits_dim(half3 oces)
961{
962 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
963
964 // OCES to RGB rendering space
965 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
966
967 // Apply the tonescale independently in rendering-space RGB
968 half3 rgbPost;
969 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
970 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
971 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
972
973 // Scale luminance to linear code value
974 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
975
976 // --- Compensate for different white point being darker --- //
977 // This adjustment is to correct an issue that exists in ODTs where the device
978 // is calibrated to a white chromaticity other than D60. In order to simulate
979 // D60 on such devices, unequal code values are sent to the display to achieve
980 // neutrals at D60. In order to produce D60 on a device calibrated to the DCI
981 // white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
982 // 0.351) the red channel is higher than green and blue to compensate for the
983 // "greenish" DCI white. This is the correct behavior but it means that as
984 // highlight increase, the red channel will hit the device maximum first and
985 // clip, resulting in a chromaticity shift as the green and blue channels
986 // continue to increase.
987 // To avoid this clipping error, a slight scale factor is applied to allow the
988 // ODTs to simulate D60 within the D65 calibration white point.
989
990 // Scale and clamp white to avoid casted highlights due to D60 simulation
991 const half SCALE = 0.955;
992 linearCV = min(linearCV, 1.0) * SCALE;
993
994 // Apply gamma adjustment to compensate for dim surround
995 linearCV = darkSurround_to_dimSurround(linearCV);
996
997 // Apply desaturation to compensate for luminance difference
998 //linearCV = mul(ODT_SAT_MAT, linearCV);
999 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1000
1001 // Convert to display primary encoding
1002 // Rendering space RGB to XYZ
1003 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1004
1005 // CIE XYZ to display primaries
1006 linearCV = mul(XYZ_2_REC709_MAT, XYZ);
1007
1008 // Handle out-of-gamut values
1009 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1010 linearCV = saturate(linearCV);
1011
1012 // TODO: Revisit when it is possible to deactivate Unity default framebuffer encoding
1013 // with sRGB opto-electrical transfer function (OETF).
1014 /*
1015 // Encode linear code values with transfer function
1016 half3 outputCV;
1017 // moncurve_r with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB)
1018 const half DISPGAMMA = 2.4;
1019 const half OFFSET = 0.055;
1020 outputCV.x = moncurve_r(linearCV.x, DISPGAMMA, OFFSET);
1021 outputCV.y = moncurve_r(linearCV.y, DISPGAMMA, OFFSET);
1022 outputCV.z = moncurve_r(linearCV.z, DISPGAMMA, OFFSET);
1023
1024 outputCV = linear_to_sRGB(linearCV);
1025 */
1026
1027 // Unity already draws to a sRGB target
1028 return linearCV;
1029}
1030
1031// <ACEStransformID>ODT.Academy.Rec709_100nits_dim.a1.0.3</ACEStransformID>
1032// <ACESuserName>ACES 1.0 Output - Rec.709</ACESuserName>
1033
1034//
1035// Output Device Transform - Rec709
1036//
1037
1038//
1039// Summary :
1040// This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
1041// that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
1042// adapted white is D65, and the viewing environment is a dim surround.
1043//
1044// A possible use case for this transform would be HDTV/video mastering.
1045//
1046// Device Primaries :
1047// Primaries are those specified in Rec. ITU-R BT.709
1048// CIE 1931 chromaticities: x y Y
1049// Red: 0.64 0.33
1050// Green: 0.3 0.6
1051// Blue: 0.15 0.06
1052// White: 0.3127 0.329 100 cd/m^2
1053//
1054// Display EOTF :
1055// The reference electro-optical transfer function specified in
1056// Rec. ITU-R BT.1886.
1057//
1058// Signal Range:
1059// By default, this transform outputs full range code values. If instead a
1060// SMPTE "legal" signal is desired, there is a runtime flag to output
1061// SMPTE legal signal. In ctlrender, this can be achieved by appending
1062// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
1063//
1064// Assumed observer adapted white point:
1065// CIE 1931 chromaticities: x y
1066// 0.3127 0.329
1067//
1068// Viewing Environment:
1069// This ODT has a compensation for viewing environment variables more typical
1070// of those associated with video mastering.
1071//
1072half3 ODT_Rec709_100nits_dim(half3 oces)
1073{
1074 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
1075
1076 // OCES to RGB rendering space
1077 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1078
1079 // Apply the tonescale independently in rendering-space RGB
1080 half3 rgbPost;
1081 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
1082 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
1083 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
1084
1085 // Scale luminance to linear code value
1086 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1087
1088 // Apply gamma adjustment to compensate for dim surround
1089 linearCV = darkSurround_to_dimSurround(linearCV);
1090
1091 // Apply desaturation to compensate for luminance difference
1092 //linearCV = mul(ODT_SAT_MAT, linearCV);
1093 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1094
1095 // Convert to display primary encoding
1096 // Rendering space RGB to XYZ
1097 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1098
1099 // Apply CAT from ACES white point to assumed observer adapted white point
1100 XYZ = mul(D60_2_D65_CAT, XYZ);
1101
1102 // CIE XYZ to display primaries
1103 linearCV = mul(XYZ_2_REC709_MAT, XYZ);
1104
1105 // Handle out-of-gamut values
1106 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1107 linearCV = saturate(linearCV);
1108
1109 // Encode linear code values with transfer function
1110 const half DISPGAMMA = 2.4;
1111 const half L_W = 1.0;
1112 const half L_B = 0.0;
1113 half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
1114
1115 // TODO: Implement support for legal range.
1116
1117 // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1118 // by default which will result in double perceptual encoding, thus for now if one want to use
1119 // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1120 // compensate for Unity default behaviour.
1121
1122 return outputCV;
1123}
1124
1125// <ACEStransformID>ODT.Academy.Rec709_D60sim_100nits_dim.a1.0.3</ACEStransformID>
1126// <ACESuserName>ACES 1.0 Output - Rec.709 (D60 sim.)</ACESuserName>
1127
1128//
1129// Output Device Transform - Rec709 (D60 simulation)
1130//
1131
1132//
1133// Summary :
1134// This transform is intended for mapping OCES onto a Rec.709 broadcast monitor
1135// that is calibrated to a D65 white point at 100 cd/m^2. The assumed observer
1136// adapted white is D60, and the viewing environment is a dim surround.
1137//
1138// A possible use case for this transform would be cinema "soft-proofing".
1139//
1140// Device Primaries :
1141// Primaries are those specified in Rec. ITU-R BT.709
1142// CIE 1931 chromaticities: x y Y
1143// Red: 0.64 0.33
1144// Green: 0.3 0.6
1145// Blue: 0.15 0.06
1146// White: 0.3127 0.329 100 cd/m^2
1147//
1148// Display EOTF :
1149// The reference electro-optical transfer function specified in
1150// Rec. ITU-R BT.1886.
1151//
1152// Signal Range:
1153// By default, this transform outputs full range code values. If instead a
1154// SMPTE "legal" signal is desired, there is a runtime flag to output
1155// SMPTE legal signal. In ctlrender, this can be achieved by appending
1156// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
1157//
1158// Assumed observer adapted white point:
1159// CIE 1931 chromaticities: x y
1160// 0.32168 0.33767
1161//
1162// Viewing Environment:
1163// This ODT has a compensation for viewing environment variables more typical
1164// of those associated with video mastering.
1165//
1166half3 ODT_Rec709_D60sim_100nits_dim(half3 oces)
1167{
1168 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
1169
1170 // OCES to RGB rendering space
1171 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1172
1173 // Apply the tonescale independently in rendering-space RGB
1174 half3 rgbPost;
1175 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
1176 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
1177 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
1178
1179 // Scale luminance to linear code value
1180 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1181
1182 // --- Compensate for different white point being darker --- //
1183 // This adjustment is to correct an issue that exists in ODTs where the device
1184 // is calibrated to a white chromaticity other than D60. In order to simulate
1185 // D60 on such devices, unequal code values must be sent to the display to achieve
1186 // the chromaticities of D60. More specifically, in order to produce D60 on a device
1187 // calibrated to a D65 white point (i.e. equal code values yield CIE x,y
1188 // chromaticities of 0.3127, 0.329) the red channel must be slightly higher than
1189 // that of green and blue in order to compensate for the relatively more "blue-ish"
1190 // D65 white. This unequalness of color channels is the correct behavior but it
1191 // means that as neutral highlights increase, the red channel will hit the
1192 // device maximum first and clip, resulting in a small chromaticity shift as the
1193 // green and blue channels continue to increase to their maximums.
1194 // To avoid this clipping error, a slight scale factor is applied to allow the
1195 // ODTs to simulate D60 within the D65 calibration white point.
1196
1197 // Scale and clamp white to avoid casted highlights due to D60 simulation
1198 const half SCALE = 0.955;
1199 linearCV = min(linearCV, 1.0) * SCALE;
1200
1201 // Apply gamma adjustment to compensate for dim surround
1202 linearCV = darkSurround_to_dimSurround(linearCV);
1203
1204 // Apply desaturation to compensate for luminance difference
1205 //linearCV = mul(ODT_SAT_MAT, linearCV);
1206 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1207
1208 // Convert to display primary encoding
1209 // Rendering space RGB to XYZ
1210 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1211
1212 // CIE XYZ to display primaries
1213 linearCV = mul(XYZ_2_REC709_MAT, XYZ);
1214
1215 // Handle out-of-gamut values
1216 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1217 linearCV = saturate(linearCV);
1218
1219 // Encode linear code values with transfer function
1220 const half DISPGAMMA = 2.4;
1221 const half L_W = 1.0;
1222 const half L_B = 0.0;
1223 half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
1224
1225 // TODO: Implement support for legal range.
1226
1227 // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1228 // by default which will result in double perceptual encoding, thus for now if one want to use
1229 // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1230 // compensate for Unity default behaviour.
1231
1232 return outputCV;
1233}
1234
1235// <ACEStransformID>ODT.Academy.Rec2020_100nits_dim.a1.0.3</ACEStransformID>
1236// <ACESuserName>ACES 1.0 Output - Rec.2020</ACESuserName>
1237
1238//
1239// Output Device Transform - Rec2020
1240//
1241
1242//
1243// Summary :
1244// This transform is intended for mapping OCES onto a Rec.2020 broadcast
1245// monitor that is calibrated to a D65 white point at 100 cd/m^2. The assumed
1246// observer adapted white is D65, and the viewing environment is that of a dim
1247// surround.
1248//
1249// A possible use case for this transform would be UHDTV/video mastering.
1250//
1251// Device Primaries :
1252// Primaries are those specified in Rec. ITU-R BT.2020
1253// CIE 1931 chromaticities: x y Y
1254// Red: 0.708 0.292
1255// Green: 0.17 0.797
1256// Blue: 0.131 0.046
1257// White: 0.3127 0.329 100 cd/m^2
1258//
1259// Display EOTF :
1260// The reference electro-optical transfer function specified in
1261// Rec. ITU-R BT.1886.
1262//
1263// Signal Range:
1264// By default, this transform outputs full range code values. If instead a
1265// SMPTE "legal" signal is desired, there is a runtime flag to output
1266// SMPTE legal signal. In ctlrender, this can be achieved by appending
1267// '-param1 legalRange 1' after the '-ctl odt.ctl' string.
1268//
1269// Assumed observer adapted white point:
1270// CIE 1931 chromaticities: x y
1271// 0.3127 0.329
1272//
1273// Viewing Environment:
1274// This ODT has a compensation for viewing environment variables more typical
1275// of those associated with video mastering.
1276//
1277
1278half3 ODT_Rec2020_100nits_dim(half3 oces)
1279{
1280 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
1281
1282 // OCES to RGB rendering space
1283 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1284
1285 // Apply the tonescale independently in rendering-space RGB
1286 half3 rgbPost;
1287 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
1288 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
1289 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
1290
1291 // Scale luminance to linear code value
1292 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1293
1294 // Apply gamma adjustment to compensate for dim surround
1295 linearCV = darkSurround_to_dimSurround(linearCV);
1296
1297 // Apply desaturation to compensate for luminance difference
1298 //linearCV = mul(ODT_SAT_MAT, linearCV);
1299 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1300
1301 // Convert to display primary encoding
1302 // Rendering space RGB to XYZ
1303 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1304
1305 // Apply CAT from ACES white point to assumed observer adapted white point
1306 XYZ = mul(D60_2_D65_CAT, XYZ);
1307
1308 // CIE XYZ to display primaries
1309 linearCV = mul(XYZ_2_REC2020_MAT, XYZ);
1310
1311 // Handle out-of-gamut values
1312 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1313 linearCV = saturate(linearCV);
1314
1315 // Encode linear code values with transfer function
1316 const half DISPGAMMA = 2.4;
1317 const half L_W = 1.0;
1318 const half L_B = 0.0;
1319 half3 outputCV = linear_to_bt1886(linearCV, DISPGAMMA, L_W, L_B);
1320
1321 // TODO: Implement support for legal range.
1322
1323 // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1324 // by default which will result in double perceptual encoding, thus for now if one want to use
1325 // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1326 // compensate for Unity default behaviour.
1327
1328 return outputCV;
1329}
1330
1331// <ACEStransformID>ODT.Academy.P3DCI_48nits.a1.0.3</ACEStransformID>
1332// <ACESuserName>ACES 1.0 Output - P3-DCI</ACESuserName>
1333
1334//
1335// Output Device Transform - P3DCI (D60 Simulation)
1336//
1337
1338//
1339// Summary :
1340// This transform is intended for mapping OCES onto a P3 digital cinema
1341// projector that is calibrated to a DCI white point at 48 cd/m^2. The assumed
1342// observer adapted white is D60, and the viewing environment is that of a dark
1343// theater.
1344//
1345// Device Primaries :
1346// CIE 1931 chromaticities: x y Y
1347// Red: 0.68 0.32
1348// Green: 0.265 0.69
1349// Blue: 0.15 0.06
1350// White: 0.314 0.351 48 cd/m^2
1351//
1352// Display EOTF :
1353// Gamma: 2.6
1354//
1355// Assumed observer adapted white point:
1356// CIE 1931 chromaticities: x y
1357// 0.32168 0.33767
1358//
1359// Viewing Environment:
1360// Environment specified in SMPTE RP 431-2-2007
1361//
1362half3 ODT_P3DCI_48nits(half3 oces)
1363{
1364 const SegmentedSplineParams_c9 ODT_48nits = GetSplineParams_ODT48Nits();
1365
1366 // OCES to RGB rendering space
1367 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1368
1369 // Apply the tonescale independently in rendering-space RGB
1370 half3 rgbPost;
1371 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_48nits);
1372 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_48nits);
1373 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_48nits);
1374
1375 // Scale luminance to linear code value
1376 half3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
1377
1378 // --- Compensate for different white point being darker --- //
1379 // This adjustment is to correct an issue that exists in ODTs where the device
1380 // is calibrated to a white chromaticity other than D60. In order to simulate
1381 // D60 on such devices, unequal code values are sent to the display to achieve
1382 // neutrals at D60. In order to produce D60 on a device calibrated to the DCI
1383 // white point (i.e. equal code values yield CIE x,y chromaticities of 0.314,
1384 // 0.351) the red channel is higher than green and blue to compensate for the
1385 // "greenish" DCI white. This is the correct behavior but it means that as
1386 // highlight increase, the red channel will hit the device maximum first and
1387 // clip, resulting in a chromaticity shift as the green and blue channels
1388 // continue to increase.
1389 // To avoid this clipping error, a slight scale factor is applied to allow the
1390 // ODTs to simulate D60 within the D65 calibration white point. However, the
1391 // magnitude of the scale factor required for the P3DCI ODT was considered too
1392 // large. Therefore, the scale factor was reduced and the additional required
1393 // compression was achieved via a reshaping of the highlight rolloff in
1394 // conjunction with the scale. The shape of this rolloff was determined
1395 // throught subjective experiments and deemed to best reproduce the
1396 // "character" of the highlights in the P3D60 ODT.
1397
1398 // Roll off highlights to avoid need for as much scaling
1399 const half NEW_WHT = 0.918;
1400 const half ROLL_WIDTH = 0.5;
1401 linearCV.x = roll_white_fwd(linearCV.x, NEW_WHT, ROLL_WIDTH);
1402 linearCV.y = roll_white_fwd(linearCV.y, NEW_WHT, ROLL_WIDTH);
1403 linearCV.z = roll_white_fwd(linearCV.z, NEW_WHT, ROLL_WIDTH);
1404
1405 // Scale and clamp white to avoid casted highlights due to D60 simulation
1406 const half SCALE = 0.96;
1407 linearCV = min(linearCV, NEW_WHT) * SCALE;
1408
1409 // Convert to display primary encoding
1410 // Rendering space RGB to XYZ
1411 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1412
1413 // CIE XYZ to display primaries
1414 linearCV = mul(XYZ_2_DCIP3_MAT, XYZ);
1415
1416 // Handle out-of-gamut values
1417 // Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
1418 linearCV = saturate(linearCV);
1419
1420 // Encode linear code values with transfer function
1421 const half DISPGAMMA = 2.6;
1422 half3 outputCV = pow(linearCV, 1.0 / DISPGAMMA);
1423
1424 // NOTE: Unity framebuffer encoding is encoded with sRGB opto-electrical transfer function (OETF)
1425 // by default which will result in double perceptual encoding, thus for now if one want to use
1426 // this ODT, he needs to decode its output with sRGB electro-optical transfer function (EOTF) to
1427 // compensate for Unity default behaviour.
1428
1429 return outputCV;
1430}
1431
1432
1433// IMPORTANT: This will need transforming to the final output space after unlike the standard ODT.
1434half3 ODT_Rec2020_1000nits_ToLinear(half3 oces)
1435{
1436 const SegmentedSplineParams_c9 ODT_1000nits = GetSplineParams_ODT1000Nits();
1437
1438 // OCES to RGB rendering space
1439 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1440
1441 // Apply the tonescale independently in rendering-space RGB
1442 half3 rgbPost;
1443 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_1000nits);
1444 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_1000nits);
1445 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_1000nits);
1446
1447 // Scale luminance to linear code value
1448 half3 linearCV = Y_2_linCV(rgbPost, ODT_1000nits.maxPoint.y, ODT_1000nits.minPoint.y);
1449
1450 // Apply desaturation to compensate for luminance difference
1451 //linearCV = mul(ODT_SAT_MAT, linearCV);
1452 linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
1453
1454 // Convert to display primary encoding
1455 // Rendering space RGB to XYZ
1456 half3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
1457
1458 // Apply CAT from ACES white point to assumed observer adapted white point
1459 XYZ = mul(D60_2_D65_CAT, XYZ);
1460
1461 // CIE XYZ to display primaries
1462 linearCV = mul(XYZ_2_REC2020_MAT, XYZ);
1463
1464 // Handle out-of-gamut values
1465 linearCV = max(linearCV, 0.);
1466
1467 return linearCV;
1468}
1469
1470half3 ODT_1000nits_ToAP1(half3 oces)
1471{
1472 const SegmentedSplineParams_c9 ODT_1000nits = GetSplineParams_ODT1000Nits();
1473
1474 // OCES to RGB rendering space
1475 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1476
1477 // Apply the tonescale independently in rendering-space RGB
1478 half3 rgbPost;
1479 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_1000nits);
1480 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_1000nits);
1481 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_1000nits);
1482
1483 return rgbPost;
1484}
1485
1486half3 ODT_2000nits_ToAP1(half3 oces)
1487{
1488 const SegmentedSplineParams_c9 ODT_2000nits = GetSplineParams_ODT2000Nits();
1489
1490 // OCES to RGB rendering space
1491 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1492
1493 // Apply the tonescale independently in rendering-space RGB
1494 half3 rgbPost;
1495 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_2000nits);
1496 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_2000nits);
1497 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_2000nits);
1498
1499 return rgbPost;
1500}
1501
1502half3 ODT_4000nits_ToAP1(half3 oces)
1503{
1504 const SegmentedSplineParams_c9 ODT_4000nits = GetSplineParams_ODT4000Nits();
1505
1506 // OCES to RGB rendering space
1507 half3 rgbPre = mul(AP0_2_AP1_MAT, oces);
1508
1509 // Apply the tonescale independently in rendering-space RGB
1510 half3 rgbPost;
1511 rgbPost.x = segmented_spline_c9_fwd(rgbPre.x, ODT_4000nits);
1512 rgbPost.y = segmented_spline_c9_fwd(rgbPre.y, ODT_4000nits);
1513 rgbPost.z = segmented_spline_c9_fwd(rgbPre.z, ODT_4000nits);
1514
1515 return rgbPost;
1516}
1517#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH
1518#pragma warning (enable : 3205) // conversion of larger type to smaller
1519#endif
1520
1521#endif // __ACES__