You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1333 lines
46 KiB

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