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.

436 lines
18 KiB

  1. namespace UnityEngine.PostProcessing
  2. {
  3. using DebugMode = BuiltinDebugViewsModel.Mode;
  4. public sealed class ColorGradingComponent : PostProcessingComponentRenderTexture<ColorGradingModel>
  5. {
  6. static class Uniforms
  7. {
  8. internal static readonly int _LutParams = Shader.PropertyToID("_LutParams");
  9. internal static readonly int _NeutralTonemapperParams1 = Shader.PropertyToID("_NeutralTonemapperParams1");
  10. internal static readonly int _NeutralTonemapperParams2 = Shader.PropertyToID("_NeutralTonemapperParams2");
  11. internal static readonly int _HueShift = Shader.PropertyToID("_HueShift");
  12. internal static readonly int _Saturation = Shader.PropertyToID("_Saturation");
  13. internal static readonly int _Contrast = Shader.PropertyToID("_Contrast");
  14. internal static readonly int _Balance = Shader.PropertyToID("_Balance");
  15. internal static readonly int _Lift = Shader.PropertyToID("_Lift");
  16. internal static readonly int _InvGamma = Shader.PropertyToID("_InvGamma");
  17. internal static readonly int _Gain = Shader.PropertyToID("_Gain");
  18. internal static readonly int _Slope = Shader.PropertyToID("_Slope");
  19. internal static readonly int _Power = Shader.PropertyToID("_Power");
  20. internal static readonly int _Offset = Shader.PropertyToID("_Offset");
  21. internal static readonly int _ChannelMixerRed = Shader.PropertyToID("_ChannelMixerRed");
  22. internal static readonly int _ChannelMixerGreen = Shader.PropertyToID("_ChannelMixerGreen");
  23. internal static readonly int _ChannelMixerBlue = Shader.PropertyToID("_ChannelMixerBlue");
  24. internal static readonly int _Curves = Shader.PropertyToID("_Curves");
  25. internal static readonly int _LogLut = Shader.PropertyToID("_LogLut");
  26. internal static readonly int _LogLut_Params = Shader.PropertyToID("_LogLut_Params");
  27. internal static readonly int _ExposureEV = Shader.PropertyToID("_ExposureEV");
  28. }
  29. const int k_InternalLogLutSize = 32;
  30. const int k_CurvePrecision = 128;
  31. const float k_CurveStep = 1f / k_CurvePrecision;
  32. Texture2D m_GradingCurves;
  33. Color[] m_pixels = new Color[k_CurvePrecision * 2];
  34. public override bool active
  35. {
  36. get
  37. {
  38. return model.enabled
  39. && !context.interrupted;
  40. }
  41. }
  42. // An analytical model of chromaticity of the standard illuminant, by Judd et al.
  43. // http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
  44. // Slightly modifed to adjust it with the D65 white point (x=0.31271, y=0.32902).
  45. float StandardIlluminantY(float x)
  46. {
  47. return 2.87f * x - 3f * x * x - 0.27509507f;
  48. }
  49. // CIE xy chromaticity to CAT02 LMS.
  50. // http://en.wikipedia.org/wiki/LMS_color_space#CAT02
  51. Vector3 CIExyToLMS(float x, float y)
  52. {
  53. float Y = 1f;
  54. float X = Y * x / y;
  55. float Z = Y * (1f - x - y) / y;
  56. float L = 0.7328f * X + 0.4296f * Y - 0.1624f * Z;
  57. float M = -0.7036f * X + 1.6975f * Y + 0.0061f * Z;
  58. float S = 0.0030f * X + 0.0136f * Y + 0.9834f * Z;
  59. return new Vector3(L, M, S);
  60. }
  61. Vector3 CalculateColorBalance(float temperature, float tint)
  62. {
  63. // Range ~[-1.8;1.8] ; using higher ranges is unsafe
  64. float t1 = temperature / 55f;
  65. float t2 = tint / 55f;
  66. // Get the CIE xy chromaticity of the reference white point.
  67. // Note: 0.31271 = x value on the D65 white point
  68. float x = 0.31271f - t1 * (t1 < 0f ? 0.1f : 0.05f);
  69. float y = StandardIlluminantY(x) + t2 * 0.05f;
  70. // Calculate the coefficients in the LMS space.
  71. var w1 = new Vector3(0.949237f, 1.03542f, 1.08728f); // D65 white point
  72. var w2 = CIExyToLMS(x, y);
  73. return new Vector3(w1.x / w2.x, w1.y / w2.y, w1.z / w2.z);
  74. }
  75. static Color NormalizeColor(Color c)
  76. {
  77. float sum = (c.r + c.g + c.b) / 3f;
  78. if (Mathf.Approximately(sum, 0f))
  79. return new Color(1f, 1f, 1f, c.a);
  80. return new Color
  81. {
  82. r = c.r / sum,
  83. g = c.g / sum,
  84. b = c.b / sum,
  85. a = c.a
  86. };
  87. }
  88. static Vector3 ClampVector(Vector3 v, float min, float max)
  89. {
  90. return new Vector3(
  91. Mathf.Clamp(v.x, min, max),
  92. Mathf.Clamp(v.y, min, max),
  93. Mathf.Clamp(v.z, min, max)
  94. );
  95. }
  96. public static Vector3 GetLiftValue(Color lift)
  97. {
  98. const float kLiftScale = 0.1f;
  99. var nLift = NormalizeColor(lift);
  100. float avgLift = (nLift.r + nLift.g + nLift.b) / 3f;
  101. // Getting some artifacts when going into the negatives using a very low offset (lift.a) with non ACES-tonemapping
  102. float liftR = (nLift.r - avgLift) * kLiftScale + lift.a;
  103. float liftG = (nLift.g - avgLift) * kLiftScale + lift.a;
  104. float liftB = (nLift.b - avgLift) * kLiftScale + lift.a;
  105. return ClampVector(new Vector3(liftR, liftG, liftB), -1f, 1f);
  106. }
  107. public static Vector3 GetGammaValue(Color gamma)
  108. {
  109. const float kGammaScale = 0.5f;
  110. const float kMinGamma = 0.01f;
  111. var nGamma = NormalizeColor(gamma);
  112. float avgGamma = (nGamma.r + nGamma.g + nGamma.b) / 3f;
  113. gamma.a *= gamma.a < 0f ? 0.8f : 5f;
  114. float gammaR = Mathf.Pow(2f, (nGamma.r - avgGamma) * kGammaScale) + gamma.a;
  115. float gammaG = Mathf.Pow(2f, (nGamma.g - avgGamma) * kGammaScale) + gamma.a;
  116. float gammaB = Mathf.Pow(2f, (nGamma.b - avgGamma) * kGammaScale) + gamma.a;
  117. float invGammaR = 1f / Mathf.Max(kMinGamma, gammaR);
  118. float invGammaG = 1f / Mathf.Max(kMinGamma, gammaG);
  119. float invGammaB = 1f / Mathf.Max(kMinGamma, gammaB);
  120. return ClampVector(new Vector3(invGammaR, invGammaG, invGammaB), 0f, 5f);
  121. }
  122. public static Vector3 GetGainValue(Color gain)
  123. {
  124. const float kGainScale = 0.5f;
  125. var nGain = NormalizeColor(gain);
  126. float avgGain = (nGain.r + nGain.g + nGain.b) / 3f;
  127. gain.a *= gain.a > 0f ? 3f : 1f;
  128. float gainR = Mathf.Pow(2f, (nGain.r - avgGain) * kGainScale) + gain.a;
  129. float gainG = Mathf.Pow(2f, (nGain.g - avgGain) * kGainScale) + gain.a;
  130. float gainB = Mathf.Pow(2f, (nGain.b - avgGain) * kGainScale) + gain.a;
  131. return ClampVector(new Vector3(gainR, gainG, gainB), 0f, 4f);
  132. }
  133. public static void CalculateLiftGammaGain(Color lift, Color gamma, Color gain, out Vector3 outLift, out Vector3 outGamma, out Vector3 outGain)
  134. {
  135. outLift = GetLiftValue(lift);
  136. outGamma = GetGammaValue(gamma);
  137. outGain = GetGainValue(gain);
  138. }
  139. public static Vector3 GetSlopeValue(Color slope)
  140. {
  141. const float kSlopeScale = 0.1f;
  142. var nSlope = NormalizeColor(slope);
  143. float avgSlope = (nSlope.r + nSlope.g + nSlope.b) / 3f;
  144. slope.a *= 0.5f;
  145. float slopeR = (nSlope.r - avgSlope) * kSlopeScale + slope.a + 1f;
  146. float slopeG = (nSlope.g - avgSlope) * kSlopeScale + slope.a + 1f;
  147. float slopeB = (nSlope.b - avgSlope) * kSlopeScale + slope.a + 1f;
  148. return ClampVector(new Vector3(slopeR, slopeG, slopeB), 0f, 2f);
  149. }
  150. public static Vector3 GetPowerValue(Color power)
  151. {
  152. const float kPowerScale = 0.1f;
  153. const float minPower = 0.01f;
  154. var nPower = NormalizeColor(power);
  155. float avgPower = (nPower.r + nPower.g + nPower.b) / 3f;
  156. power.a *= 0.5f;
  157. float powerR = (nPower.r - avgPower) * kPowerScale + power.a + 1f;
  158. float powerG = (nPower.g - avgPower) * kPowerScale + power.a + 1f;
  159. float powerB = (nPower.b - avgPower) * kPowerScale + power.a + 1f;
  160. float invPowerR = 1f / Mathf.Max(minPower, powerR);
  161. float invPowerG = 1f / Mathf.Max(minPower, powerG);
  162. float invPowerB = 1f / Mathf.Max(minPower, powerB);
  163. return ClampVector(new Vector3(invPowerR, invPowerG, invPowerB), 0.5f, 2.5f);
  164. }
  165. public static Vector3 GetOffsetValue(Color offset)
  166. {
  167. const float kOffsetScale = 0.05f;
  168. var nOffset = NormalizeColor(offset);
  169. float avgOffset = (nOffset.r + nOffset.g + nOffset.b) / 3f;
  170. offset.a *= 0.5f;
  171. float offsetR = (nOffset.r - avgOffset) * kOffsetScale + offset.a;
  172. float offsetG = (nOffset.g - avgOffset) * kOffsetScale + offset.a;
  173. float offsetB = (nOffset.b - avgOffset) * kOffsetScale + offset.a;
  174. return ClampVector(new Vector3(offsetR, offsetG, offsetB), -0.8f, 0.8f);
  175. }
  176. public static void CalculateSlopePowerOffset(Color slope, Color power, Color offset, out Vector3 outSlope, out Vector3 outPower, out Vector3 outOffset)
  177. {
  178. outSlope = GetSlopeValue(slope);
  179. outPower = GetPowerValue(power);
  180. outOffset = GetOffsetValue(offset);
  181. }
  182. TextureFormat GetCurveFormat()
  183. {
  184. if (SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf))
  185. return TextureFormat.RGBAHalf;
  186. return TextureFormat.RGBA32;
  187. }
  188. Texture2D GetCurveTexture()
  189. {
  190. if (m_GradingCurves == null)
  191. {
  192. m_GradingCurves = new Texture2D(k_CurvePrecision, 2, GetCurveFormat(), false, true)
  193. {
  194. name = "Internal Curves Texture",
  195. hideFlags = HideFlags.DontSave,
  196. anisoLevel = 0,
  197. wrapMode = TextureWrapMode.Clamp,
  198. filterMode = FilterMode.Bilinear
  199. };
  200. }
  201. var curves = model.settings.curves;
  202. curves.hueVShue.Cache();
  203. curves.hueVSsat.Cache();
  204. for (int i = 0; i < k_CurvePrecision; i++)
  205. {
  206. float t = i * k_CurveStep;
  207. // HSL
  208. float x = curves.hueVShue.Evaluate(t);
  209. float y = curves.hueVSsat.Evaluate(t);
  210. float z = curves.satVSsat.Evaluate(t);
  211. float w = curves.lumVSsat.Evaluate(t);
  212. m_pixels[i] = new Color(x, y, z, w);
  213. // YRGB
  214. float m = curves.master.Evaluate(t);
  215. float r = curves.red.Evaluate(t);
  216. float g = curves.green.Evaluate(t);
  217. float b = curves.blue.Evaluate(t);
  218. m_pixels[i + k_CurvePrecision] = new Color(r, g, b, m);
  219. }
  220. m_GradingCurves.SetPixels(m_pixels);
  221. m_GradingCurves.Apply(false, false);
  222. return m_GradingCurves;
  223. }
  224. bool IsLogLutValid(RenderTexture lut)
  225. {
  226. return lut != null && lut.IsCreated() && lut.height == k_InternalLogLutSize;
  227. }
  228. RenderTextureFormat GetLutFormat()
  229. {
  230. if (SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGBHalf))
  231. return RenderTextureFormat.ARGBHalf;
  232. return RenderTextureFormat.ARGB32;
  233. }
  234. void GenerateLut()
  235. {
  236. var settings = model.settings;
  237. if (!IsLogLutValid(model.bakedLut))
  238. {
  239. GraphicsUtils.Destroy(model.bakedLut);
  240. model.bakedLut = new RenderTexture(k_InternalLogLutSize * k_InternalLogLutSize, k_InternalLogLutSize, 0, GetLutFormat())
  241. {
  242. name = "Color Grading Log LUT",
  243. hideFlags = HideFlags.DontSave,
  244. filterMode = FilterMode.Bilinear,
  245. wrapMode = TextureWrapMode.Clamp,
  246. anisoLevel = 0
  247. };
  248. }
  249. var lutMaterial = context.materialFactory.Get("Hidden/Post FX/Lut Generator");
  250. lutMaterial.SetVector(Uniforms._LutParams, new Vector4(
  251. k_InternalLogLutSize,
  252. 0.5f / (k_InternalLogLutSize * k_InternalLogLutSize),
  253. 0.5f / k_InternalLogLutSize,
  254. k_InternalLogLutSize / (k_InternalLogLutSize - 1f))
  255. );
  256. // Tonemapping
  257. lutMaterial.shaderKeywords = null;
  258. var tonemapping = settings.tonemapping;
  259. switch (tonemapping.tonemapper)
  260. {
  261. case ColorGradingModel.Tonemapper.Neutral:
  262. {
  263. lutMaterial.EnableKeyword("TONEMAPPING_NEUTRAL");
  264. const float scaleFactor = 20f;
  265. const float scaleFactorHalf = scaleFactor * 0.5f;
  266. float inBlack = tonemapping.neutralBlackIn * scaleFactor + 1f;
  267. float outBlack = tonemapping.neutralBlackOut * scaleFactorHalf + 1f;
  268. float inWhite = tonemapping.neutralWhiteIn / scaleFactor;
  269. float outWhite = 1f - tonemapping.neutralWhiteOut / scaleFactor;
  270. float blackRatio = inBlack / outBlack;
  271. float whiteRatio = inWhite / outWhite;
  272. const float a = 0.2f;
  273. float b = Mathf.Max(0f, Mathf.LerpUnclamped(0.57f, 0.37f, blackRatio));
  274. float c = Mathf.LerpUnclamped(0.01f, 0.24f, whiteRatio);
  275. float d = Mathf.Max(0f, Mathf.LerpUnclamped(0.02f, 0.20f, blackRatio));
  276. const float e = 0.02f;
  277. const float f = 0.30f;
  278. lutMaterial.SetVector(Uniforms._NeutralTonemapperParams1, new Vector4(a, b, c, d));
  279. lutMaterial.SetVector(Uniforms._NeutralTonemapperParams2, new Vector4(e, f, tonemapping.neutralWhiteLevel, tonemapping.neutralWhiteClip / scaleFactorHalf));
  280. break;
  281. }
  282. case ColorGradingModel.Tonemapper.ACES:
  283. {
  284. lutMaterial.EnableKeyword("TONEMAPPING_FILMIC");
  285. break;
  286. }
  287. }
  288. // Color balance & basic grading settings
  289. lutMaterial.SetFloat(Uniforms._HueShift, settings.basic.hueShift / 360f);
  290. lutMaterial.SetFloat(Uniforms._Saturation, settings.basic.saturation);
  291. lutMaterial.SetFloat(Uniforms._Contrast, settings.basic.contrast);
  292. lutMaterial.SetVector(Uniforms._Balance, CalculateColorBalance(settings.basic.temperature, settings.basic.tint));
  293. // Lift / Gamma / Gain
  294. Vector3 lift, gamma, gain;
  295. CalculateLiftGammaGain(
  296. settings.colorWheels.linear.lift,
  297. settings.colorWheels.linear.gamma,
  298. settings.colorWheels.linear.gain,
  299. out lift, out gamma, out gain
  300. );
  301. lutMaterial.SetVector(Uniforms._Lift, lift);
  302. lutMaterial.SetVector(Uniforms._InvGamma, gamma);
  303. lutMaterial.SetVector(Uniforms._Gain, gain);
  304. // Slope / Power / Offset
  305. Vector3 slope, power, offset;
  306. CalculateSlopePowerOffset(
  307. settings.colorWheels.log.slope,
  308. settings.colorWheels.log.power,
  309. settings.colorWheels.log.offset,
  310. out slope, out power, out offset
  311. );
  312. lutMaterial.SetVector(Uniforms._Slope, slope);
  313. lutMaterial.SetVector(Uniforms._Power, power);
  314. lutMaterial.SetVector(Uniforms._Offset, offset);
  315. // Channel mixer
  316. lutMaterial.SetVector(Uniforms._ChannelMixerRed, settings.channelMixer.red);
  317. lutMaterial.SetVector(Uniforms._ChannelMixerGreen, settings.channelMixer.green);
  318. lutMaterial.SetVector(Uniforms._ChannelMixerBlue, settings.channelMixer.blue);
  319. // Selective grading & YRGB curves
  320. lutMaterial.SetTexture(Uniforms._Curves, GetCurveTexture());
  321. // Generate the lut
  322. Graphics.Blit(null, model.bakedLut, lutMaterial, 0);
  323. }
  324. public override void Prepare(Material uberMaterial)
  325. {
  326. if (model.isDirty || !IsLogLutValid(model.bakedLut))
  327. {
  328. GenerateLut();
  329. model.isDirty = false;
  330. }
  331. uberMaterial.EnableKeyword(
  332. context.profile.debugViews.IsModeActive(DebugMode.PreGradingLog)
  333. ? "COLOR_GRADING_LOG_VIEW"
  334. : "COLOR_GRADING"
  335. );
  336. var bakedLut = model.bakedLut;
  337. uberMaterial.SetTexture(Uniforms._LogLut, bakedLut);
  338. uberMaterial.SetVector(Uniforms._LogLut_Params, new Vector3(1f / bakedLut.width, 1f / bakedLut.height, bakedLut.height - 1f));
  339. float ev = Mathf.Exp(model.settings.basic.postExposure * 0.69314718055994530941723212145818f);
  340. uberMaterial.SetFloat(Uniforms._ExposureEV, ev);
  341. }
  342. public void OnGUI()
  343. {
  344. var bakedLut = model.bakedLut;
  345. var rect = new Rect(context.viewport.x * Screen.width + 8f, 8f, bakedLut.width, bakedLut.height);
  346. GUI.DrawTexture(rect, bakedLut);
  347. }
  348. public override void OnDisable()
  349. {
  350. GraphicsUtils.Destroy(m_GradingCurves);
  351. GraphicsUtils.Destroy(model.bakedLut);
  352. m_GradingCurves = null;
  353. model.bakedLut = null;
  354. }
  355. }
  356. }