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.

290 lines
7.0 KiB

  1. #ifndef __COLOR_GRADING__
  2. #define __COLOR_GRADING__
  3. #include "ACES.cginc"
  4. #include "Common.cginc"
  5. // Set to 1 to use more precise but more expensive log/linear conversions. I haven't found a proper
  6. // use case for the high precision version yet so I'm leaving this to 0.
  7. #define COLOR_GRADING_PRECISE_LOG 0
  8. //
  9. // Alexa LogC converters (El 1000)
  10. // See http://www.vocas.nl/webfm_send/964
  11. // It's a good fit to store HDR values in log as the range is pretty wide (1 maps to ~58.85666) and
  12. // is quick enough to compute.
  13. //
  14. struct ParamsLogC
  15. {
  16. half cut;
  17. half a, b, c, d, e, f;
  18. };
  19. static const ParamsLogC LogC =
  20. {
  21. 0.011361, // cut
  22. 5.555556, // a
  23. 0.047996, // b
  24. 0.244161, // c
  25. 0.386036, // d
  26. 5.301883, // e
  27. 0.092819 // f
  28. };
  29. half LinearToLogC_Precise(half x)
  30. {
  31. half o;
  32. if (x > LogC.cut)
  33. o = LogC.c * log10(LogC.a * x + LogC.b) + LogC.d;
  34. else
  35. o = LogC.e * x + LogC.f;
  36. return o;
  37. }
  38. half3 LinearToLogC(half3 x)
  39. {
  40. #if COLOR_GRADING_PRECISE_LOG
  41. return half3(
  42. LinearToLogC_Precise(x.x),
  43. LinearToLogC_Precise(x.y),
  44. LinearToLogC_Precise(x.z)
  45. );
  46. #else
  47. return LogC.c * log10(LogC.a * x + LogC.b) + LogC.d;
  48. #endif
  49. }
  50. half LogCToLinear_Precise(half x)
  51. {
  52. half o;
  53. if (x > LogC.e * LogC.cut + LogC.f)
  54. o = (pow(10.0, (x - LogC.d) / LogC.c) - LogC.b) / LogC.a;
  55. else
  56. o = (x - LogC.f) / LogC.e;
  57. return o;
  58. }
  59. half3 LogCToLinear(half3 x)
  60. {
  61. #if COLOR_GRADING_PRECISE_LOG
  62. return half3(
  63. LogCToLinear_Precise(x.x),
  64. LogCToLinear_Precise(x.y),
  65. LogCToLinear_Precise(x.z)
  66. );
  67. #else
  68. return (pow(10.0, (x - LogC.d) / LogC.c) - LogC.b) / LogC.a;
  69. #endif
  70. }
  71. //
  72. // White balance
  73. // Recommended workspace: ACEScg (linear)
  74. //
  75. static const half3x3 LIN_2_LMS_MAT = {
  76. 3.90405e-1, 5.49941e-1, 8.92632e-3,
  77. 7.08416e-2, 9.63172e-1, 1.35775e-3,
  78. 2.31082e-2, 1.28021e-1, 9.36245e-1
  79. };
  80. static const half3x3 LMS_2_LIN_MAT = {
  81. 2.85847e+0, -1.62879e+0, -2.48910e-2,
  82. -2.10182e-1, 1.15820e+0, 3.24281e-4,
  83. -4.18120e-2, -1.18169e-1, 1.06867e+0
  84. };
  85. half3 WhiteBalance(half3 c, half3 balance)
  86. {
  87. half3 lms = mul(LIN_2_LMS_MAT, c);
  88. lms *= balance;
  89. return mul(LMS_2_LIN_MAT, lms);
  90. }
  91. //
  92. // Luminance (Rec.709 primaries according to ACES specs)
  93. //
  94. half AcesLuminance(half3 c)
  95. {
  96. return dot(c, half3(0.2126, 0.7152, 0.0722));
  97. }
  98. //
  99. // Offset, Power, Slope (ASC-CDL)
  100. // Works in Log & Linear. Results will be different but still correct.
  101. //
  102. half3 OffsetPowerSlope(half3 c, half3 offset, half3 power, half3 slope)
  103. {
  104. half3 so = c * slope + offset;
  105. so = so > (0.0).xxx ? pow(so, power) : so;
  106. return so;
  107. }
  108. //
  109. // Lift, Gamma (pre-inverted), Gain
  110. // Recommended workspace: ACEScg (linear)
  111. //
  112. half3 LiftGammaGain(half3 c, half3 lift, half3 invgamma, half3 gain)
  113. {
  114. //return gain * (lift * (1.0 - c) + pow(max(c, kEpsilon), invgamma));
  115. //return pow(gain * (c + lift * (1.0 - c)), invgamma);
  116. half3 power = invgamma;
  117. half3 offset = lift * gain;
  118. half3 slope = ((1.0).xxx - lift) * gain;
  119. return OffsetPowerSlope(c, offset, power, slope);
  120. }
  121. //
  122. // Saturation (should be used after offset/power/slope)
  123. // Recommended workspace: ACEScc (log)
  124. // Optimal range: [0.0, 2.0]
  125. //
  126. half3 Saturation(half3 c, half sat)
  127. {
  128. half luma = AcesLuminance(c);
  129. return luma.xxx + sat * (c - luma.xxx);
  130. }
  131. //
  132. // Basic contrast curve
  133. // Recommended workspace: ACEScc (log)
  134. // Optimal range: [0.0, 2.0]
  135. //
  136. half3 ContrastLog(half3 c, half con)
  137. {
  138. return (c - ACEScc_MIDGRAY) * con + ACEScc_MIDGRAY;
  139. }
  140. //
  141. // Hue, Saturation, Value
  142. // Ranges:
  143. // Hue [0.0, 1.0]
  144. // Sat [0.0, 1.0]
  145. // Lum [0.0, HALF_MAX]
  146. //
  147. half3 RgbToHsv(half3 c)
  148. {
  149. half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
  150. half4 p = lerp(half4(c.bg, K.wz), half4(c.gb, K.xy), step(c.b, c.g));
  151. half4 q = lerp(half4(p.xyw, c.r), half4(c.r, p.yzx), step(p.x, c.r));
  152. half d = q.x - min(q.w, q.y);
  153. half e = EPSILON;
  154. return half3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
  155. }
  156. half3 HsvToRgb(half3 c)
  157. {
  158. half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
  159. half3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
  160. return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
  161. }
  162. half RotateHue(half value, half low, half hi)
  163. {
  164. return (value < low)
  165. ? value + hi
  166. : (value > hi)
  167. ? value - hi
  168. : value;
  169. }
  170. //
  171. // Remaps Y/R/G/B values
  172. //
  173. half3 YrgbCurve(half3 c, sampler2D curveTex)
  174. {
  175. const float kHalfPixel = (1.0 / 128.0) / 2.0;
  176. // Y
  177. c += kHalfPixel.xxx;
  178. float mr = tex2D(curveTex, float2(c.r, 0.75)).a;
  179. float mg = tex2D(curveTex, float2(c.g, 0.75)).a;
  180. float mb = tex2D(curveTex, float2(c.b, 0.75)).a;
  181. c = saturate(float3(mr, mg, mb));
  182. // RGB
  183. c += kHalfPixel.xxx;
  184. float r = tex2D(curveTex, float2(c.r, 0.75)).r;
  185. float g = tex2D(curveTex, float2(c.g, 0.75)).g;
  186. float b = tex2D(curveTex, float2(c.b, 0.75)).b;
  187. return saturate(half3(r, g, b));
  188. }
  189. //
  190. // (X) Hue VS Hue - Remaps hue on a curve according to the current hue
  191. // Input is Hue [0.0, 1.0]
  192. // Output is Hue [0.0, 1.0]
  193. //
  194. half SecondaryHueHue(half hue, sampler2D curveTex)
  195. {
  196. half offset = saturate(tex2D(curveTex, half2(hue, 0.25)).x) - 0.5;
  197. hue += offset;
  198. hue = RotateHue(hue, 0.0, 1.0);
  199. return hue;
  200. }
  201. //
  202. // (Y) Hue VS Saturation - Remaps saturation on a curve according to the current hue
  203. // Input is Hue [0.0, 1.0]
  204. // Output is Saturation multiplier [0.0, 2.0]
  205. //
  206. half SecondaryHueSat(half hue, sampler2D curveTex)
  207. {
  208. return saturate(tex2D(curveTex, half2(hue, 0.25)).y) * 2.0;
  209. }
  210. //
  211. // (Z) Saturation VS Saturation - Remaps saturation on a curve according to the current saturation
  212. // Input is Saturation [0.0, 1.0]
  213. // Output is Saturation multiplier [0.0, 2.0]
  214. //
  215. half SecondarySatSat(half sat, sampler2D curveTex)
  216. {
  217. return saturate(tex2D(curveTex, half2(sat, 0.25)).z) * 2.0;
  218. }
  219. //
  220. // (W) Luminance VS Saturation - Remaps saturation on a curve according to the current luminance
  221. // Input is Luminance [0.0, 1.0]
  222. // Output is Saturation multiplier [0.0, 2.0]
  223. //
  224. half SecondaryLumSat(half lum, sampler2D curveTex)
  225. {
  226. return saturate(tex2D(curveTex, half2(lum, 0.25)).w) * 2.0;
  227. }
  228. //
  229. // Channel mixing (same as Photoshop's and DaVinci's Resolve)
  230. // Recommended workspace: ACEScg (linear)
  231. // Input mixers should be in range [-2.0;2.0]
  232. //
  233. half3 ChannelMixer(half3 c, half3 red, half3 green, half3 blue)
  234. {
  235. return half3(
  236. dot(c, red),
  237. dot(c, green),
  238. dot(c, blue)
  239. );
  240. }
  241. //
  242. // LUT grading
  243. // scaleOffset = (1 / lut_width, 1 / lut_height, lut_height - 1)
  244. //
  245. half3 ApplyLut2d(sampler2D tex, half3 uvw, half3 scaleOffset)
  246. {
  247. // Strip format where `height = sqrt(width)`
  248. uvw.z *= scaleOffset.z;
  249. half shift = floor(uvw.z);
  250. uvw.xy = uvw.xy * scaleOffset.z * scaleOffset.xy + scaleOffset.xy * 0.5;
  251. uvw.x += shift * scaleOffset.y;
  252. uvw.xyz = lerp(tex2D(tex, uvw.xy).rgb, tex2D(tex, uvw.xy + half2(scaleOffset.y, 0)).rgb, uvw.z - shift);
  253. return uvw;
  254. }
  255. half3 ApplyLut3d(sampler3D tex, half3 uvw)
  256. {
  257. return tex3D(tex, uvw).rgb;
  258. }
  259. #endif // __COLOR_GRADING__