| | |
| | | namespace UnityEngine.PostProcessing |
| | | { |
| | | using DebugMode = BuiltinDebugViewsModel.Mode; |
| | | |
| | | public sealed class ColorGradingComponent : PostProcessingComponentRenderTexture<ColorGradingModel> |
| | | { |
| | | static class Uniforms |
| | | { |
| | | internal static readonly int _LutParams = Shader.PropertyToID("_LutParams"); |
| | | internal static readonly int _NeutralTonemapperParams1 = Shader.PropertyToID("_NeutralTonemapperParams1"); |
| | | internal static readonly int _NeutralTonemapperParams2 = Shader.PropertyToID("_NeutralTonemapperParams2"); |
| | | internal static readonly int _HueShift = Shader.PropertyToID("_HueShift"); |
| | | internal static readonly int _Saturation = Shader.PropertyToID("_Saturation"); |
| | | internal static readonly int _Contrast = Shader.PropertyToID("_Contrast"); |
| | | internal static readonly int _Balance = Shader.PropertyToID("_Balance"); |
| | | internal static readonly int _Lift = Shader.PropertyToID("_Lift"); |
| | | internal static readonly int _InvGamma = Shader.PropertyToID("_InvGamma"); |
| | | internal static readonly int _Gain = Shader.PropertyToID("_Gain"); |
| | | internal static readonly int _Slope = Shader.PropertyToID("_Slope"); |
| | | internal static readonly int _Power = Shader.PropertyToID("_Power"); |
| | | internal static readonly int _Offset = Shader.PropertyToID("_Offset"); |
| | | internal static readonly int _ChannelMixerRed = Shader.PropertyToID("_ChannelMixerRed"); |
| | | internal static readonly int _ChannelMixerGreen = Shader.PropertyToID("_ChannelMixerGreen"); |
| | | internal static readonly int _ChannelMixerBlue = Shader.PropertyToID("_ChannelMixerBlue"); |
| | | internal static readonly int _Curves = Shader.PropertyToID("_Curves"); |
| | | internal static readonly int _LogLut = Shader.PropertyToID("_LogLut"); |
| | | internal static readonly int _LogLut_Params = Shader.PropertyToID("_LogLut_Params"); |
| | | internal static readonly int _ExposureEV = Shader.PropertyToID("_ExposureEV"); |
| | | } |
| | | |
| | | const int k_InternalLogLutSize = 32; |
| | | const int k_CurvePrecision = 128; |
| | | const float k_CurveStep = 1f / k_CurvePrecision; |
| | | |
| | | Texture2D m_GradingCurves; |
| | | Color[] m_pixels = new Color[k_CurvePrecision * 2]; |
| | | |
| | | public override bool active |
| | | { |
| | | get |
| | | { |
| | | return model != null && model.enabled |
| | | && !context.interrupted; |
| | | } |
| | | } |
| | | |
| | | // An analytical model of chromaticity of the standard illuminant, by Judd et al. |
| | | // http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D |
| | | // Slightly modifed to adjust it with the D65 white point (x=0.31271, y=0.32902). |
| | | float StandardIlluminantY(float x) |
| | | { |
| | | return 2.87f * x - 3f * x * x - 0.27509507f; |
| | | } |
| | | |
| | | // CIE xy chromaticity to CAT02 LMS. |
| | | // http://en.wikipedia.org/wiki/LMS_color_space#CAT02 |
| | | Vector3 CIExyToLMS(float x, float y) |
| | | { |
| | | float Y = 1f; |
| | | float X = Y * x / y; |
| | | float Z = Y * (1f - x - y) / y; |
| | | |
| | | float L = 0.7328f * X + 0.4296f * Y - 0.1624f * Z; |
| | | float M = -0.7036f * X + 1.6975f * Y + 0.0061f * Z; |
| | | float S = 0.0030f * X + 0.0136f * Y + 0.9834f * Z; |
| | | |
| | | return new Vector3(L, M, S); |
| | | } |
| | | |
| | | Vector3 CalculateColorBalance(float temperature, float tint) |
| | | { |
| | | // Range ~[-1.8;1.8] ; using higher ranges is unsafe |
| | | float t1 = temperature / 55f; |
| | | float t2 = tint / 55f; |
| | | |
| | | // Get the CIE xy chromaticity of the reference white point. |
| | | // Note: 0.31271 = x value on the D65 white point |
| | | float x = 0.31271f - t1 * (t1 < 0f ? 0.1f : 0.05f); |
| | | float y = StandardIlluminantY(x) + t2 * 0.05f; |
| | | |
| | | // Calculate the coefficients in the LMS space. |
| | | var w1 = new Vector3(0.949237f, 1.03542f, 1.08728f); // D65 white point |
| | | var w2 = CIExyToLMS(x, y); |
| | | return new Vector3(w1.x / w2.x, w1.y / w2.y, w1.z / w2.z); |
| | | } |
| | | |
| | | static Color NormalizeColor(Color c) |
| | | { |
| | | float sum = (c.r + c.g + c.b) / 3f; |
| | | |
| | | if (Mathf.Approximately(sum, 0f)) |
| | | return new Color(1f, 1f, 1f, c.a); |
| | | |
| | | return new Color |
| | | { |
| | | r = c.r / sum, |
| | | g = c.g / sum, |
| | | b = c.b / sum, |
| | | a = c.a |
| | | }; |
| | | } |
| | | |
| | | static Vector3 ClampVector(Vector3 v, float min, float max) |
| | | { |
| | | return new Vector3( |
| | | Mathf.Clamp(v.x, min, max), |
| | | Mathf.Clamp(v.y, min, max), |
| | | Mathf.Clamp(v.z, min, max) |
| | | ); |
| | | } |
| | | |
| | | public static Vector3 GetLiftValue(Color lift) |
| | | { |
| | | const float kLiftScale = 0.1f; |
| | | |
| | | var nLift = NormalizeColor(lift); |
| | | float avgLift = (nLift.r + nLift.g + nLift.b) / 3f; |
| | | |
| | | // Getting some artifacts when going into the negatives using a very low offset (lift.a) with non ACES-tonemapping |
| | | float liftR = (nLift.r - avgLift) * kLiftScale + lift.a; |
| | | float liftG = (nLift.g - avgLift) * kLiftScale + lift.a; |
| | | float liftB = (nLift.b - avgLift) * kLiftScale + lift.a; |
| | | |
| | | return ClampVector(new Vector3(liftR, liftG, liftB), -1f, 1f); |
| | | } |
| | | |
| | | public static Vector3 GetGammaValue(Color gamma) |
| | | { |
| | | const float kGammaScale = 0.5f; |
| | | const float kMinGamma = 0.01f; |
| | | |
| | | var nGamma = NormalizeColor(gamma); |
| | | float avgGamma = (nGamma.r + nGamma.g + nGamma.b) / 3f; |
| | | |
| | | gamma.a *= gamma.a < 0f ? 0.8f : 5f; |
| | | float gammaR = Mathf.Pow(2f, (nGamma.r - avgGamma) * kGammaScale) + gamma.a; |
| | | float gammaG = Mathf.Pow(2f, (nGamma.g - avgGamma) * kGammaScale) + gamma.a; |
| | | float gammaB = Mathf.Pow(2f, (nGamma.b - avgGamma) * kGammaScale) + gamma.a; |
| | | |
| | | float invGammaR = 1f / Mathf.Max(kMinGamma, gammaR); |
| | | float invGammaG = 1f / Mathf.Max(kMinGamma, gammaG); |
| | | float invGammaB = 1f / Mathf.Max(kMinGamma, gammaB); |
| | | |
| | | return ClampVector(new Vector3(invGammaR, invGammaG, invGammaB), 0f, 5f); |
| | | } |
| | | |
| | | public static Vector3 GetGainValue(Color gain) |
| | | { |
| | | const float kGainScale = 0.5f; |
| | | |
| | | var nGain = NormalizeColor(gain); |
| | | float avgGain = (nGain.r + nGain.g + nGain.b) / 3f; |
| | | |
| | | gain.a *= gain.a > 0f ? 3f : 1f; |
| | | float gainR = Mathf.Pow(2f, (nGain.r - avgGain) * kGainScale) + gain.a; |
| | | float gainG = Mathf.Pow(2f, (nGain.g - avgGain) * kGainScale) + gain.a; |
| | | float gainB = Mathf.Pow(2f, (nGain.b - avgGain) * kGainScale) + gain.a; |
| | | |
| | | return ClampVector(new Vector3(gainR, gainG, gainB), 0f, 4f); |
| | | } |
| | | |
| | | public static void CalculateLiftGammaGain(Color lift, Color gamma, Color gain, out Vector3 outLift, out Vector3 outGamma, out Vector3 outGain) |
| | | { |
| | | outLift = GetLiftValue(lift); |
| | | outGamma = GetGammaValue(gamma); |
| | | outGain = GetGainValue(gain); |
| | | } |
| | | |
| | | public static Vector3 GetSlopeValue(Color slope) |
| | | { |
| | | const float kSlopeScale = 0.1f; |
| | | |
| | | var nSlope = NormalizeColor(slope); |
| | | float avgSlope = (nSlope.r + nSlope.g + nSlope.b) / 3f; |
| | | |
| | | slope.a *= 0.5f; |
| | | float slopeR = (nSlope.r - avgSlope) * kSlopeScale + slope.a + 1f; |
| | | float slopeG = (nSlope.g - avgSlope) * kSlopeScale + slope.a + 1f; |
| | | float slopeB = (nSlope.b - avgSlope) * kSlopeScale + slope.a + 1f; |
| | | |
| | | return ClampVector(new Vector3(slopeR, slopeG, slopeB), 0f, 2f); |
| | | } |
| | | |
| | | public static Vector3 GetPowerValue(Color power) |
| | | { |
| | | const float kPowerScale = 0.1f; |
| | | const float minPower = 0.01f; |
| | | |
| | | var nPower = NormalizeColor(power); |
| | | float avgPower = (nPower.r + nPower.g + nPower.b) / 3f; |
| | | |
| | | power.a *= 0.5f; |
| | | float powerR = (nPower.r - avgPower) * kPowerScale + power.a + 1f; |
| | | float powerG = (nPower.g - avgPower) * kPowerScale + power.a + 1f; |
| | | float powerB = (nPower.b - avgPower) * kPowerScale + power.a + 1f; |
| | | |
| | | float invPowerR = 1f / Mathf.Max(minPower, powerR); |
| | | float invPowerG = 1f / Mathf.Max(minPower, powerG); |
| | | float invPowerB = 1f / Mathf.Max(minPower, powerB); |
| | | |
| | | return ClampVector(new Vector3(invPowerR, invPowerG, invPowerB), 0.5f, 2.5f); |
| | | } |
| | | |
| | | public static Vector3 GetOffsetValue(Color offset) |
| | | { |
| | | const float kOffsetScale = 0.05f; |
| | | |
| | | var nOffset = NormalizeColor(offset); |
| | | float avgOffset = (nOffset.r + nOffset.g + nOffset.b) / 3f; |
| | | |
| | | offset.a *= 0.5f; |
| | | float offsetR = (nOffset.r - avgOffset) * kOffsetScale + offset.a; |
| | | float offsetG = (nOffset.g - avgOffset) * kOffsetScale + offset.a; |
| | | float offsetB = (nOffset.b - avgOffset) * kOffsetScale + offset.a; |
| | | |
| | | return ClampVector(new Vector3(offsetR, offsetG, offsetB), -0.8f, 0.8f); |
| | | } |
| | | |
| | | public static void CalculateSlopePowerOffset(Color slope, Color power, Color offset, out Vector3 outSlope, out Vector3 outPower, out Vector3 outOffset) |
| | | { |
| | | outSlope = GetSlopeValue(slope); |
| | | outPower = GetPowerValue(power); |
| | | outOffset = GetOffsetValue(offset); |
| | | } |
| | | |
| | | TextureFormat GetCurveFormat() |
| | | { |
| | | if (SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf)) |
| | | return TextureFormat.RGBAHalf; |
| | | |
| | | return TextureFormat.RGBA32; |
| | | } |
| | | |
| | | Texture2D GetCurveTexture() |
| | | { |
| | | if (m_GradingCurves == null) |
| | | { |
| | | m_GradingCurves = new Texture2D(k_CurvePrecision, 2, GetCurveFormat(), false, true) |
| | | { |
| | | name = "Internal Curves Texture", |
| | | hideFlags = HideFlags.DontSave, |
| | | anisoLevel = 0, |
| | | wrapMode = TextureWrapMode.Clamp, |
| | | filterMode = FilterMode.Bilinear |
| | | }; |
| | | } |
| | | |
| | | var curves = model.settings.curves; |
| | | curves.hueVShue.Cache(); |
| | | curves.hueVSsat.Cache(); |
| | | |
| | | for (int i = 0; i < k_CurvePrecision; i++) |
| | | { |
| | | float t = i * k_CurveStep; |
| | | |
| | | // HSL |
| | | float x = curves.hueVShue.Evaluate(t); |
| | | float y = curves.hueVSsat.Evaluate(t); |
| | | float z = curves.satVSsat.Evaluate(t); |
| | | float w = curves.lumVSsat.Evaluate(t); |
| | | m_pixels[i] = new Color(x, y, z, w); |
| | | |
| | | // YRGB |
| | | float m = curves.master.Evaluate(t); |
| | | float r = curves.red.Evaluate(t); |
| | | float g = curves.green.Evaluate(t); |
| | | float b = curves.blue.Evaluate(t); |
| | | m_pixels[i + k_CurvePrecision] = new Color(r, g, b, m); |
| | | } |
| | | |
| | | m_GradingCurves.SetPixels(m_pixels); |
| | | m_GradingCurves.Apply(false, false); |
| | | |
| | | return m_GradingCurves; |
| | | } |
| | | |
| | | bool IsLogLutValid(RenderTexture lut) |
| | | { |
| | | return lut != null && lut.IsCreated() && lut.height == k_InternalLogLutSize; |
| | | } |
| | | |
| | | RenderTextureFormat GetLutFormat() |
| | | { |
| | | if (SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGBHalf)) |
| | | return RenderTextureFormat.ARGBHalf; |
| | | |
| | | return RenderTextureFormat.ARGB32; |
| | | } |
| | | |
| | | void GenerateLut() |
| | | { |
| | | var settings = model.settings; |
| | | |
| | | if (!IsLogLutValid(model.bakedLut)) |
| | | { |
| | | GraphicsUtils.Destroy(model.bakedLut); |
| | | |
| | | model.bakedLut = new RenderTexture(k_InternalLogLutSize * k_InternalLogLutSize, k_InternalLogLutSize, 0, GetLutFormat()) |
| | | { |
| | | name = "Color Grading Log LUT", |
| | | hideFlags = HideFlags.DontSave, |
| | | filterMode = FilterMode.Bilinear, |
| | | wrapMode = TextureWrapMode.Clamp, |
| | | anisoLevel = 0 |
| | | }; |
| | | } |
| | | |
| | | var lutMaterial = context.materialFactory.Get("Hidden/Post FX/Lut Generator"); |
| | | lutMaterial.SetVector(Uniforms._LutParams, new Vector4( |
| | | k_InternalLogLutSize, |
| | | 0.5f / (k_InternalLogLutSize * k_InternalLogLutSize), |
| | | 0.5f / k_InternalLogLutSize, |
| | | k_InternalLogLutSize / (k_InternalLogLutSize - 1f)) |
| | | ); |
| | | |
| | | // Tonemapping |
| | | lutMaterial.shaderKeywords = null; |
| | | |
| | | var tonemapping = settings.tonemapping; |
| | | switch (tonemapping.tonemapper) |
| | | { |
| | | case ColorGradingModel.Tonemapper.Neutral: |
| | | { |
| | | lutMaterial.EnableKeyword("TONEMAPPING_NEUTRAL"); |
| | | |
| | | const float scaleFactor = 20f; |
| | | const float scaleFactorHalf = scaleFactor * 0.5f; |
| | | |
| | | float inBlack = tonemapping.neutralBlackIn * scaleFactor + 1f; |
| | | float outBlack = tonemapping.neutralBlackOut * scaleFactorHalf + 1f; |
| | | float inWhite = tonemapping.neutralWhiteIn / scaleFactor; |
| | | float outWhite = 1f - tonemapping.neutralWhiteOut / scaleFactor; |
| | | float blackRatio = inBlack / outBlack; |
| | | float whiteRatio = inWhite / outWhite; |
| | | |
| | | const float a = 0.2f; |
| | | float b = Mathf.Max(0f, Mathf.LerpUnclamped(0.57f, 0.37f, blackRatio)); |
| | | float c = Mathf.LerpUnclamped(0.01f, 0.24f, whiteRatio); |
| | | float d = Mathf.Max(0f, Mathf.LerpUnclamped(0.02f, 0.20f, blackRatio)); |
| | | const float e = 0.02f; |
| | | const float f = 0.30f; |
| | | |
| | | lutMaterial.SetVector(Uniforms._NeutralTonemapperParams1, new Vector4(a, b, c, d)); |
| | | lutMaterial.SetVector(Uniforms._NeutralTonemapperParams2, new Vector4(e, f, tonemapping.neutralWhiteLevel, tonemapping.neutralWhiteClip / scaleFactorHalf)); |
| | | break; |
| | | } |
| | | |
| | | case ColorGradingModel.Tonemapper.ACES: |
| | | { |
| | | lutMaterial.EnableKeyword("TONEMAPPING_FILMIC"); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // Color balance & basic grading settings |
| | | lutMaterial.SetFloat(Uniforms._HueShift, settings.basic.hueShift / 360f); |
| | | lutMaterial.SetFloat(Uniforms._Saturation, settings.basic.saturation); |
| | | lutMaterial.SetFloat(Uniforms._Contrast, settings.basic.contrast); |
| | | lutMaterial.SetVector(Uniforms._Balance, CalculateColorBalance(settings.basic.temperature, settings.basic.tint)); |
| | | |
| | | // Lift / Gamma / Gain |
| | | Vector3 lift, gamma, gain; |
| | | CalculateLiftGammaGain( |
| | | settings.colorWheels.linear.lift, |
| | | settings.colorWheels.linear.gamma, |
| | | settings.colorWheels.linear.gain, |
| | | out lift, out gamma, out gain |
| | | ); |
| | | |
| | | lutMaterial.SetVector(Uniforms._Lift, lift); |
| | | lutMaterial.SetVector(Uniforms._InvGamma, gamma); |
| | | lutMaterial.SetVector(Uniforms._Gain, gain); |
| | | |
| | | // Slope / Power / Offset |
| | | Vector3 slope, power, offset; |
| | | CalculateSlopePowerOffset( |
| | | settings.colorWheels.log.slope, |
| | | settings.colorWheels.log.power, |
| | | settings.colorWheels.log.offset, |
| | | out slope, out power, out offset |
| | | ); |
| | | |
| | | lutMaterial.SetVector(Uniforms._Slope, slope); |
| | | lutMaterial.SetVector(Uniforms._Power, power); |
| | | lutMaterial.SetVector(Uniforms._Offset, offset); |
| | | |
| | | // Channel mixer |
| | | lutMaterial.SetVector(Uniforms._ChannelMixerRed, settings.channelMixer.red); |
| | | lutMaterial.SetVector(Uniforms._ChannelMixerGreen, settings.channelMixer.green); |
| | | lutMaterial.SetVector(Uniforms._ChannelMixerBlue, settings.channelMixer.blue); |
| | | |
| | | // Selective grading & YRGB curves |
| | | lutMaterial.SetTexture(Uniforms._Curves, GetCurveTexture()); |
| | | |
| | | // Generate the lut |
| | | Graphics.Blit(null, model.bakedLut, lutMaterial, 0); |
| | | } |
| | | |
| | | public override void Prepare(Material uberMaterial) |
| | | { |
| | | if (model.isDirty || !IsLogLutValid(model.bakedLut)) |
| | | { |
| | | GenerateLut(); |
| | | model.isDirty = false; |
| | | } |
| | | |
| | | uberMaterial.EnableKeyword( |
| | | context.profile.debugViews.IsModeActive(DebugMode.PreGradingLog) |
| | | ? "COLOR_GRADING_LOG_VIEW" |
| | | : "COLOR_GRADING" |
| | | ); |
| | | |
| | | var bakedLut = model.bakedLut; |
| | | uberMaterial.SetTexture(Uniforms._LogLut, bakedLut); |
| | | uberMaterial.SetVector(Uniforms._LogLut_Params, new Vector3(1f / bakedLut.width, 1f / bakedLut.height, bakedLut.height - 1f)); |
| | | |
| | | float ev = Mathf.Exp(model.settings.basic.postExposure * 0.69314718055994530941723212145818f); |
| | | uberMaterial.SetFloat(Uniforms._ExposureEV, ev); |
| | | } |
| | | |
| | | public void OnGUI() |
| | | { |
| | | var bakedLut = model.bakedLut; |
| | | var rect = new Rect(context.viewport.x * Screen.width + 8f, 8f, bakedLut.width, bakedLut.height); |
| | | GUI.DrawTexture(rect, bakedLut); |
| | | } |
| | | |
| | | public override void OnDisable() |
| | | { |
| | | GraphicsUtils.Destroy(m_GradingCurves); |
| | | GraphicsUtils.Destroy(model.bakedLut); |
| | | m_GradingCurves = null; |
| | | model.bakedLut = null; |
| | | } |
| | | } |
| | | } |
| | | namespace UnityEngine.PostProcessing
|
| | | {
|
| | | using DebugMode = BuiltinDebugViewsModel.Mode;
|
| | |
|
| | | public sealed class ColorGradingComponent : PostProcessingComponentRenderTexture<ColorGradingModel>
|
| | | {
|
| | | static class Uniforms
|
| | | {
|
| | | internal static readonly int _LutParams = Shader.PropertyToID("_LutParams");
|
| | | internal static readonly int _NeutralTonemapperParams1 = Shader.PropertyToID("_NeutralTonemapperParams1");
|
| | | internal static readonly int _NeutralTonemapperParams2 = Shader.PropertyToID("_NeutralTonemapperParams2");
|
| | | internal static readonly int _HueShift = Shader.PropertyToID("_HueShift");
|
| | | internal static readonly int _Saturation = Shader.PropertyToID("_Saturation");
|
| | | internal static readonly int _Contrast = Shader.PropertyToID("_Contrast");
|
| | | internal static readonly int _Balance = Shader.PropertyToID("_Balance");
|
| | | internal static readonly int _Lift = Shader.PropertyToID("_Lift");
|
| | | internal static readonly int _InvGamma = Shader.PropertyToID("_InvGamma");
|
| | | internal static readonly int _Gain = Shader.PropertyToID("_Gain");
|
| | | internal static readonly int _Slope = Shader.PropertyToID("_Slope");
|
| | | internal static readonly int _Power = Shader.PropertyToID("_Power");
|
| | | internal static readonly int _Offset = Shader.PropertyToID("_Offset");
|
| | | internal static readonly int _ChannelMixerRed = Shader.PropertyToID("_ChannelMixerRed");
|
| | | internal static readonly int _ChannelMixerGreen = Shader.PropertyToID("_ChannelMixerGreen");
|
| | | internal static readonly int _ChannelMixerBlue = Shader.PropertyToID("_ChannelMixerBlue");
|
| | | internal static readonly int _Curves = Shader.PropertyToID("_Curves");
|
| | | internal static readonly int _LogLut = Shader.PropertyToID("_LogLut");
|
| | | internal static readonly int _LogLut_Params = Shader.PropertyToID("_LogLut_Params");
|
| | | internal static readonly int _ExposureEV = Shader.PropertyToID("_ExposureEV");
|
| | | }
|
| | |
|
| | | const int k_InternalLogLutSize = 32;
|
| | | const int k_CurvePrecision = 128;
|
| | | const float k_CurveStep = 1f / k_CurvePrecision;
|
| | |
|
| | | Texture2D m_GradingCurves;
|
| | | Color[] m_pixels = new Color[k_CurvePrecision * 2];
|
| | |
|
| | | public override bool active
|
| | | {
|
| | | get
|
| | | {
|
| | | return model != null && model.enabled
|
| | | && !context.interrupted;
|
| | | }
|
| | | }
|
| | |
|
| | | // An analytical model of chromaticity of the standard illuminant, by Judd et al.
|
| | | // http://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
|
| | | // Slightly modifed to adjust it with the D65 white point (x=0.31271, y=0.32902).
|
| | | float StandardIlluminantY(float x)
|
| | | {
|
| | | return 2.87f * x - 3f * x * x - 0.27509507f;
|
| | | }
|
| | |
|
| | | // CIE xy chromaticity to CAT02 LMS.
|
| | | // http://en.wikipedia.org/wiki/LMS_color_space#CAT02
|
| | | Vector3 CIExyToLMS(float x, float y)
|
| | | {
|
| | | float Y = 1f;
|
| | | float X = Y * x / y;
|
| | | float Z = Y * (1f - x - y) / y;
|
| | |
|
| | | float L = 0.7328f * X + 0.4296f * Y - 0.1624f * Z;
|
| | | float M = -0.7036f * X + 1.6975f * Y + 0.0061f * Z;
|
| | | float S = 0.0030f * X + 0.0136f * Y + 0.9834f * Z;
|
| | |
|
| | | return new Vector3(L, M, S);
|
| | | }
|
| | |
|
| | | Vector3 CalculateColorBalance(float temperature, float tint)
|
| | | {
|
| | | // Range ~[-1.8;1.8] ; using higher ranges is unsafe
|
| | | float t1 = temperature / 55f;
|
| | | float t2 = tint / 55f;
|
| | |
|
| | | // Get the CIE xy chromaticity of the reference white point.
|
| | | // Note: 0.31271 = x value on the D65 white point
|
| | | float x = 0.31271f - t1 * (t1 < 0f ? 0.1f : 0.05f);
|
| | | float y = StandardIlluminantY(x) + t2 * 0.05f;
|
| | |
|
| | | // Calculate the coefficients in the LMS space.
|
| | | var w1 = new Vector3(0.949237f, 1.03542f, 1.08728f); // D65 white point
|
| | | var w2 = CIExyToLMS(x, y);
|
| | | return new Vector3(w1.x / w2.x, w1.y / w2.y, w1.z / w2.z);
|
| | | }
|
| | |
|
| | | static Color NormalizeColor(Color c)
|
| | | {
|
| | | float sum = (c.r + c.g + c.b) / 3f;
|
| | |
|
| | | if (Mathf.Approximately(sum, 0f))
|
| | | return new Color(1f, 1f, 1f, c.a);
|
| | |
|
| | | return new Color
|
| | | {
|
| | | r = c.r / sum,
|
| | | g = c.g / sum,
|
| | | b = c.b / sum,
|
| | | a = c.a
|
| | | };
|
| | | }
|
| | |
|
| | | static Vector3 ClampVector(Vector3 v, float min, float max)
|
| | | {
|
| | | return new Vector3(
|
| | | Mathf.Clamp(v.x, min, max),
|
| | | Mathf.Clamp(v.y, min, max),
|
| | | Mathf.Clamp(v.z, min, max)
|
| | | );
|
| | | }
|
| | |
|
| | | public static Vector3 GetLiftValue(Color lift)
|
| | | {
|
| | | const float kLiftScale = 0.1f;
|
| | |
|
| | | var nLift = NormalizeColor(lift);
|
| | | float avgLift = (nLift.r + nLift.g + nLift.b) / 3f;
|
| | |
|
| | | // Getting some artifacts when going into the negatives using a very low offset (lift.a) with non ACES-tonemapping
|
| | | float liftR = (nLift.r - avgLift) * kLiftScale + lift.a;
|
| | | float liftG = (nLift.g - avgLift) * kLiftScale + lift.a;
|
| | | float liftB = (nLift.b - avgLift) * kLiftScale + lift.a;
|
| | |
|
| | | return ClampVector(new Vector3(liftR, liftG, liftB), -1f, 1f);
|
| | | }
|
| | |
|
| | | public static Vector3 GetGammaValue(Color gamma)
|
| | | {
|
| | | const float kGammaScale = 0.5f;
|
| | | const float kMinGamma = 0.01f;
|
| | |
|
| | | var nGamma = NormalizeColor(gamma);
|
| | | float avgGamma = (nGamma.r + nGamma.g + nGamma.b) / 3f;
|
| | |
|
| | | gamma.a *= gamma.a < 0f ? 0.8f : 5f;
|
| | | float gammaR = Mathf.Pow(2f, (nGamma.r - avgGamma) * kGammaScale) + gamma.a;
|
| | | float gammaG = Mathf.Pow(2f, (nGamma.g - avgGamma) * kGammaScale) + gamma.a;
|
| | | float gammaB = Mathf.Pow(2f, (nGamma.b - avgGamma) * kGammaScale) + gamma.a;
|
| | |
|
| | | float invGammaR = 1f / Mathf.Max(kMinGamma, gammaR);
|
| | | float invGammaG = 1f / Mathf.Max(kMinGamma, gammaG);
|
| | | float invGammaB = 1f / Mathf.Max(kMinGamma, gammaB);
|
| | |
|
| | | return ClampVector(new Vector3(invGammaR, invGammaG, invGammaB), 0f, 5f);
|
| | | }
|
| | |
|
| | | public static Vector3 GetGainValue(Color gain)
|
| | | {
|
| | | const float kGainScale = 0.5f;
|
| | |
|
| | | var nGain = NormalizeColor(gain);
|
| | | float avgGain = (nGain.r + nGain.g + nGain.b) / 3f;
|
| | |
|
| | | gain.a *= gain.a > 0f ? 3f : 1f;
|
| | | float gainR = Mathf.Pow(2f, (nGain.r - avgGain) * kGainScale) + gain.a;
|
| | | float gainG = Mathf.Pow(2f, (nGain.g - avgGain) * kGainScale) + gain.a;
|
| | | float gainB = Mathf.Pow(2f, (nGain.b - avgGain) * kGainScale) + gain.a;
|
| | |
|
| | | return ClampVector(new Vector3(gainR, gainG, gainB), 0f, 4f);
|
| | | }
|
| | |
|
| | | public static void CalculateLiftGammaGain(Color lift, Color gamma, Color gain, out Vector3 outLift, out Vector3 outGamma, out Vector3 outGain)
|
| | | {
|
| | | outLift = GetLiftValue(lift);
|
| | | outGamma = GetGammaValue(gamma);
|
| | | outGain = GetGainValue(gain);
|
| | | }
|
| | |
|
| | | public static Vector3 GetSlopeValue(Color slope)
|
| | | {
|
| | | const float kSlopeScale = 0.1f;
|
| | |
|
| | | var nSlope = NormalizeColor(slope);
|
| | | float avgSlope = (nSlope.r + nSlope.g + nSlope.b) / 3f;
|
| | |
|
| | | slope.a *= 0.5f;
|
| | | float slopeR = (nSlope.r - avgSlope) * kSlopeScale + slope.a + 1f;
|
| | | float slopeG = (nSlope.g - avgSlope) * kSlopeScale + slope.a + 1f;
|
| | | float slopeB = (nSlope.b - avgSlope) * kSlopeScale + slope.a + 1f;
|
| | |
|
| | | return ClampVector(new Vector3(slopeR, slopeG, slopeB), 0f, 2f);
|
| | | }
|
| | |
|
| | | public static Vector3 GetPowerValue(Color power)
|
| | | {
|
| | | const float kPowerScale = 0.1f;
|
| | | const float minPower = 0.01f;
|
| | |
|
| | | var nPower = NormalizeColor(power);
|
| | | float avgPower = (nPower.r + nPower.g + nPower.b) / 3f;
|
| | |
|
| | | power.a *= 0.5f;
|
| | | float powerR = (nPower.r - avgPower) * kPowerScale + power.a + 1f;
|
| | | float powerG = (nPower.g - avgPower) * kPowerScale + power.a + 1f;
|
| | | float powerB = (nPower.b - avgPower) * kPowerScale + power.a + 1f;
|
| | |
|
| | | float invPowerR = 1f / Mathf.Max(minPower, powerR);
|
| | | float invPowerG = 1f / Mathf.Max(minPower, powerG);
|
| | | float invPowerB = 1f / Mathf.Max(minPower, powerB);
|
| | |
|
| | | return ClampVector(new Vector3(invPowerR, invPowerG, invPowerB), 0.5f, 2.5f);
|
| | | }
|
| | |
|
| | | public static Vector3 GetOffsetValue(Color offset)
|
| | | {
|
| | | const float kOffsetScale = 0.05f;
|
| | |
|
| | | var nOffset = NormalizeColor(offset);
|
| | | float avgOffset = (nOffset.r + nOffset.g + nOffset.b) / 3f;
|
| | |
|
| | | offset.a *= 0.5f;
|
| | | float offsetR = (nOffset.r - avgOffset) * kOffsetScale + offset.a;
|
| | | float offsetG = (nOffset.g - avgOffset) * kOffsetScale + offset.a;
|
| | | float offsetB = (nOffset.b - avgOffset) * kOffsetScale + offset.a;
|
| | |
|
| | | return ClampVector(new Vector3(offsetR, offsetG, offsetB), -0.8f, 0.8f);
|
| | | }
|
| | |
|
| | | public static void CalculateSlopePowerOffset(Color slope, Color power, Color offset, out Vector3 outSlope, out Vector3 outPower, out Vector3 outOffset)
|
| | | {
|
| | | outSlope = GetSlopeValue(slope);
|
| | | outPower = GetPowerValue(power);
|
| | | outOffset = GetOffsetValue(offset);
|
| | | }
|
| | |
|
| | | TextureFormat GetCurveFormat()
|
| | | {
|
| | | if (SystemInfo.SupportsTextureFormat(TextureFormat.RGBAHalf))
|
| | | return TextureFormat.RGBAHalf;
|
| | |
|
| | | return TextureFormat.RGBA32;
|
| | | }
|
| | |
|
| | | Texture2D GetCurveTexture()
|
| | | {
|
| | | if (m_GradingCurves == null)
|
| | | {
|
| | | m_GradingCurves = new Texture2D(k_CurvePrecision, 2, GetCurveFormat(), false, true)
|
| | | {
|
| | | name = "Internal Curves Texture",
|
| | | hideFlags = HideFlags.DontSave,
|
| | | anisoLevel = 0,
|
| | | wrapMode = TextureWrapMode.Clamp,
|
| | | filterMode = FilterMode.Bilinear
|
| | | };
|
| | | }
|
| | |
|
| | | var curves = model.settings.curves;
|
| | | curves.hueVShue.Cache();
|
| | | curves.hueVSsat.Cache();
|
| | |
|
| | | for (int i = 0; i < k_CurvePrecision; i++)
|
| | | {
|
| | | float t = i * k_CurveStep;
|
| | |
|
| | | // HSL
|
| | | float x = curves.hueVShue.Evaluate(t);
|
| | | float y = curves.hueVSsat.Evaluate(t);
|
| | | float z = curves.satVSsat.Evaluate(t);
|
| | | float w = curves.lumVSsat.Evaluate(t);
|
| | | m_pixels[i] = new Color(x, y, z, w);
|
| | |
|
| | | // YRGB
|
| | | float m = curves.master.Evaluate(t);
|
| | | float r = curves.red.Evaluate(t);
|
| | | float g = curves.green.Evaluate(t);
|
| | | float b = curves.blue.Evaluate(t);
|
| | | m_pixels[i + k_CurvePrecision] = new Color(r, g, b, m);
|
| | | }
|
| | |
|
| | | m_GradingCurves.SetPixels(m_pixels);
|
| | | m_GradingCurves.Apply(false, false);
|
| | |
|
| | | return m_GradingCurves;
|
| | | }
|
| | |
|
| | | bool IsLogLutValid(RenderTexture lut)
|
| | | {
|
| | | return lut != null && lut.IsCreated() && lut.height == k_InternalLogLutSize;
|
| | | }
|
| | |
|
| | | RenderTextureFormat GetLutFormat()
|
| | | {
|
| | | if (SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGBHalf))
|
| | | return RenderTextureFormat.ARGBHalf;
|
| | |
|
| | | return RenderTextureFormat.ARGB32;
|
| | | }
|
| | |
|
| | | void GenerateLut()
|
| | | {
|
| | | var settings = model.settings;
|
| | |
|
| | | if (!IsLogLutValid(model.bakedLut))
|
| | | {
|
| | | GraphicsUtils.Destroy(model.bakedLut);
|
| | |
|
| | | model.bakedLut = new RenderTexture(k_InternalLogLutSize * k_InternalLogLutSize, k_InternalLogLutSize, 0, GetLutFormat())
|
| | | {
|
| | | name = "Color Grading Log LUT",
|
| | | hideFlags = HideFlags.DontSave,
|
| | | filterMode = FilterMode.Bilinear,
|
| | | wrapMode = TextureWrapMode.Clamp,
|
| | | anisoLevel = 0
|
| | | };
|
| | | }
|
| | |
|
| | | var lutMaterial = context.materialFactory.Get("Hidden/Post FX/Lut Generator");
|
| | | lutMaterial.SetVector(Uniforms._LutParams, new Vector4(
|
| | | k_InternalLogLutSize,
|
| | | 0.5f / (k_InternalLogLutSize * k_InternalLogLutSize),
|
| | | 0.5f / k_InternalLogLutSize,
|
| | | k_InternalLogLutSize / (k_InternalLogLutSize - 1f))
|
| | | );
|
| | |
|
| | | // Tonemapping
|
| | | lutMaterial.shaderKeywords = null;
|
| | |
|
| | | var tonemapping = settings.tonemapping;
|
| | | switch (tonemapping.tonemapper)
|
| | | {
|
| | | case ColorGradingModel.Tonemapper.Neutral:
|
| | | {
|
| | | lutMaterial.EnableKeyword("TONEMAPPING_NEUTRAL");
|
| | |
|
| | | const float scaleFactor = 20f;
|
| | | const float scaleFactorHalf = scaleFactor * 0.5f;
|
| | |
|
| | | float inBlack = tonemapping.neutralBlackIn * scaleFactor + 1f;
|
| | | float outBlack = tonemapping.neutralBlackOut * scaleFactorHalf + 1f;
|
| | | float inWhite = tonemapping.neutralWhiteIn / scaleFactor;
|
| | | float outWhite = 1f - tonemapping.neutralWhiteOut / scaleFactor;
|
| | | float blackRatio = inBlack / outBlack;
|
| | | float whiteRatio = inWhite / outWhite;
|
| | |
|
| | | const float a = 0.2f;
|
| | | float b = Mathf.Max(0f, Mathf.LerpUnclamped(0.57f, 0.37f, blackRatio));
|
| | | float c = Mathf.LerpUnclamped(0.01f, 0.24f, whiteRatio);
|
| | | float d = Mathf.Max(0f, Mathf.LerpUnclamped(0.02f, 0.20f, blackRatio));
|
| | | const float e = 0.02f;
|
| | | const float f = 0.30f;
|
| | |
|
| | | lutMaterial.SetVector(Uniforms._NeutralTonemapperParams1, new Vector4(a, b, c, d));
|
| | | lutMaterial.SetVector(Uniforms._NeutralTonemapperParams2, new Vector4(e, f, tonemapping.neutralWhiteLevel, tonemapping.neutralWhiteClip / scaleFactorHalf));
|
| | | break;
|
| | | }
|
| | |
|
| | | case ColorGradingModel.Tonemapper.ACES:
|
| | | {
|
| | | lutMaterial.EnableKeyword("TONEMAPPING_FILMIC");
|
| | | break;
|
| | | }
|
| | | }
|
| | |
|
| | | // Color balance & basic grading settings
|
| | | lutMaterial.SetFloat(Uniforms._HueShift, settings.basic.hueShift / 360f);
|
| | | lutMaterial.SetFloat(Uniforms._Saturation, settings.basic.saturation);
|
| | | lutMaterial.SetFloat(Uniforms._Contrast, settings.basic.contrast);
|
| | | lutMaterial.SetVector(Uniforms._Balance, CalculateColorBalance(settings.basic.temperature, settings.basic.tint));
|
| | |
|
| | | // Lift / Gamma / Gain
|
| | | Vector3 lift, gamma, gain;
|
| | | CalculateLiftGammaGain(
|
| | | settings.colorWheels.linear.lift,
|
| | | settings.colorWheels.linear.gamma,
|
| | | settings.colorWheels.linear.gain,
|
| | | out lift, out gamma, out gain
|
| | | );
|
| | |
|
| | | lutMaterial.SetVector(Uniforms._Lift, lift);
|
| | | lutMaterial.SetVector(Uniforms._InvGamma, gamma);
|
| | | lutMaterial.SetVector(Uniforms._Gain, gain);
|
| | |
|
| | | // Slope / Power / Offset
|
| | | Vector3 slope, power, offset;
|
| | | CalculateSlopePowerOffset(
|
| | | settings.colorWheels.log.slope,
|
| | | settings.colorWheels.log.power,
|
| | | settings.colorWheels.log.offset,
|
| | | out slope, out power, out offset
|
| | | );
|
| | |
|
| | | lutMaterial.SetVector(Uniforms._Slope, slope);
|
| | | lutMaterial.SetVector(Uniforms._Power, power);
|
| | | lutMaterial.SetVector(Uniforms._Offset, offset);
|
| | |
|
| | | // Channel mixer
|
| | | lutMaterial.SetVector(Uniforms._ChannelMixerRed, settings.channelMixer.red);
|
| | | lutMaterial.SetVector(Uniforms._ChannelMixerGreen, settings.channelMixer.green);
|
| | | lutMaterial.SetVector(Uniforms._ChannelMixerBlue, settings.channelMixer.blue);
|
| | |
|
| | | // Selective grading & YRGB curves
|
| | | lutMaterial.SetTexture(Uniforms._Curves, GetCurveTexture());
|
| | |
|
| | | // Generate the lut
|
| | | Graphics.Blit(null, model.bakedLut, lutMaterial, 0);
|
| | | }
|
| | |
|
| | | public override void Prepare(Material uberMaterial)
|
| | | {
|
| | | if (model.isDirty || !IsLogLutValid(model.bakedLut))
|
| | | {
|
| | | GenerateLut();
|
| | | model.isDirty = false;
|
| | | }
|
| | |
|
| | | uberMaterial.EnableKeyword(
|
| | | context.profile.debugViews.IsModeActive(DebugMode.PreGradingLog)
|
| | | ? "COLOR_GRADING_LOG_VIEW"
|
| | | : "COLOR_GRADING"
|
| | | );
|
| | |
|
| | | var bakedLut = model.bakedLut;
|
| | | uberMaterial.SetTexture(Uniforms._LogLut, bakedLut);
|
| | | uberMaterial.SetVector(Uniforms._LogLut_Params, new Vector3(1f / bakedLut.width, 1f / bakedLut.height, bakedLut.height - 1f));
|
| | |
|
| | | float ev = Mathf.Exp(model.settings.basic.postExposure * 0.69314718055994530941723212145818f);
|
| | | uberMaterial.SetFloat(Uniforms._ExposureEV, ev);
|
| | | }
|
| | |
|
| | | public void OnGUI()
|
| | | {
|
| | | var bakedLut = model.bakedLut;
|
| | | var rect = new Rect(context.viewport.x * Screen.width + 8f, 8f, bakedLut.width, bakedLut.height);
|
| | | GUI.DrawTexture(rect, bakedLut);
|
| | | }
|
| | |
|
| | | public override void OnDisable()
|
| | | {
|
| | | GraphicsUtils.Destroy(m_GradingCurves);
|
| | | GraphicsUtils.Destroy(model.bakedLut);
|
| | | m_GradingCurves = null;
|
| | | model.bakedLut = null;
|
| | | }
|
| | | }
|
| | | }
|