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__