From c714e16588c0ed29ac3fe09c70edbdbce587277a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20V=C3=A1zquez?= Date: Thu, 19 Aug 2021 10:48:47 +0200 Subject: [PATCH] XPipeline - Split LightUnit classes. --- .../Editor/Lighting/HDLightUI.cs | 2 +- .../LightUnit/HDPiecewiseLightUnitSlider.cs | 131 +++++++ .../HDPiecewiseLightUnitSlider.cs.meta | 11 + .../LightUnit/HDPunctualLightUnitSlider.cs | 99 +++++ .../HDPunctualLightUnitSlider.cs.meta | 11 + .../Lighting/LightUnit/LightUnitSlider.cs | 355 +----------------- .../Lighting/LightUnit/TemperatureSlider.cs | 144 +++++++ .../LightUnit/TemperatureSlider.cs.meta | 11 + 8 files changed, 414 insertions(+), 350 deletions(-) create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs.meta create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs.meta create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs create mode 100644 com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs.meta diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs index 7209768671c..495de3313a3 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs @@ -671,7 +671,7 @@ static void DrawEmissionContentFiltered(SerializedHDLight serialized, Editor own var temperatureSliderRect = lineRect; temperatureSliderRect.x += EditorGUIUtility.labelWidth + k_ValueUnitSeparator; temperatureSliderRect.width -= EditorGUIUtility.labelWidth + k_ValueUnitSeparator; - k_LightUnitSliderUIDrawer.DrawTemperatureSlider(serialized.settings, serialized.settings.colorTemperature, temperatureSliderRect); + TemperatureSliderUIDrawer.Draw(serialized.settings, serialized.serializedObject, serialized.settings.colorTemperature, temperatureSliderRect); // Value and unit label // Match const defined in EditorGUI.cs diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs new file mode 100644 index 00000000000..fd64df7927c --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering.HighDefinition; +using UnityEngine.Experimental.Rendering; + +namespace UnityEditor.Rendering.HighDefinition +{ + /// + /// Formats the provided descriptor into a piece-wise linear slider with contextual slider markers, tooltips, and icons. + /// + class HDPiecewiseLightUnitSlider : LightUnitSlider + { + struct Piece + { + public Vector2 domain; + public Vector2 range; + + public float directM; + public float directB; + public float inverseM; + public float inverseB; + } + + // Piecewise function indexed by value ranges. + private readonly Dictionary m_PiecewiseFunctionMap = new Dictionary(); + + static void ComputeTransformationParameters(float x0, float x1, float y0, float y1, out float m, out float b) + { + m = (y0 - y1) / (x0 - x1); + b = (m * -x0) + y0; + } + + static float DoTransformation(in float x, in float m, in float b) => (m * x) + b; + + // Ensure clamping to (0,1) as sometimes the function evaluates to slightly below 0 (breaking the handle). + static float ValueToSlider(Piece p, float x) => Mathf.Clamp01(DoTransformation(x, p.inverseM, p.inverseB)); + static float SliderToValue(Piece p, float x) => DoTransformation(x, p.directM, p.directB); + + // Ideally we want a continuous, monotonically increasing function, but this is useful as we can easily fit a + // distribution to a set of (huge) value ranges onto a slider. + public HDPiecewiseLightUnitSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) + { + // Sort the ranges into ascending order + var sortedRanges = m_Descriptor.valueRanges.OrderBy(x => x.value.x).ToArray(); + var sliderDistribution = m_Descriptor.sliderDistribution; + + // Compute the transformation for each value range. + for (int i = 0; i < sortedRanges.Length; i++) + { + var r = sortedRanges[i].value; + + var x0 = sliderDistribution[i + 0]; + var x1 = sliderDistribution[i + 1]; + var y0 = r.x; + var y1 = r.y; + + Piece piece; + piece.domain = new Vector2(x0, x1); + piece.range = new Vector2(y0, y1); + + ComputeTransformationParameters(x0, x1, y0, y1, out piece.directM, out piece.directB); + + // Compute the inverse + ComputeTransformationParameters(y0, y1, x0, x1, out piece.inverseM, out piece.inverseB); + + m_PiecewiseFunctionMap.Add(sortedRanges[i].value, piece); + } + } + + protected override float GetPositionOnSlider(float value, Vector2 valueRange) + { + if (!m_PiecewiseFunctionMap.TryGetValue(valueRange, out var piecewise)) + return -1f; + + return ValueToSlider(piecewise, value); + } + + // Search for the corresponding piece-wise function to a value on the domain and update the input piece to it. + // Returns true if search was successful and an update was made, false otherwise. + bool UpdatePiece(ref Piece piece, float x) + { + foreach (var pair in m_PiecewiseFunctionMap) + { + var p = pair.Value; + + if (x >= p.domain.x && x <= p.domain.y) + { + piece = p; + + return true; + } + } + + return false; + } + + void SliderOutOfBounds(Rect rect, ref float value) + { + EditorGUI.BeginChangeCheck(); + var internalValue = GUI.HorizontalSlider(rect, value, 0f, 1f); + if (EditorGUI.EndChangeCheck()) + { + Piece p = new Piece(); + UpdatePiece(ref p, internalValue); + value = SliderToValue(p, internalValue); + } + } + + protected override void DoSlider(Rect rect, ref float value, Vector2 sliderRange, Vector2 valueRange) + { + // Map the internal slider value to the current piecewise function + if (!m_PiecewiseFunctionMap.TryGetValue(valueRange, out var piece)) + { + // Assume that if the piece is not found, that means the unit value is out of bounds. + SliderOutOfBounds(rect, ref value); + return; + } + + // Maintain an internal value to support a single linear continuous function + EditorGUI.BeginChangeCheck(); + var internalValue = GUI.HorizontalSlider(rect, ValueToSlider(piece, value), 0f, 1f); + if (EditorGUI.EndChangeCheck()) + { + // Ensure that the current function piece is being used to transform the value + UpdatePiece(ref piece, internalValue); + value = SliderToValue(piece, internalValue); + } + } + } +} diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs.meta b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs.meta new file mode 100644 index 00000000000..8dbac8e9cd8 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPiecewiseLightUnitSlider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f01918813898d7b4382f640c4a0e1046 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs new file mode 100644 index 00000000000..a2a9af4f072 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering.HighDefinition; +using UnityEngine.Experimental.Rendering; + +namespace UnityEditor.Rendering.HighDefinition +{ + /// + /// Formats the provided descriptor into a punctual light unit slider with contextual slider markers, tooltips, and icons. + /// + class HDPunctualLightUnitSlider : HDPiecewiseLightUnitSlider + { + public HDPunctualLightUnitSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) { } + + private SerializedHDLight m_Light; + private Editor m_Editor; + private LightUnit m_Unit; + private bool m_SpotReflectorEnabled; + + // Note: these should be in sync with LightUnit + private static string[] k_UnitNames = + { + "Lumen", + "Candela", + "Lux", + "Nits", + "EV", + }; + + public void Setup(LightUnit unit, SerializedHDLight light, Editor owner) + { + m_Unit = unit; + m_Light = light; + m_Editor = owner; + + // Cache the spot reflector state as we will need to revert back to it after treating the slider as point light. + m_SpotReflectorEnabled = light.enableSpotReflector.boolValue; + } + + public override void Draw(Rect rect, SerializedProperty value, ref float floatValue) + { + // Convert the incoming unit value into Lumen as the punctual slider is always in these terms (internally) + float convertedValue = UnitToLumen(floatValue); + + EditorGUI.BeginChangeCheck(); + base.Draw(rect, value, ref convertedValue); + if (EditorGUI.EndChangeCheck()) + floatValue = LumenToUnit(convertedValue); + } + + protected override GUIContent GetLightUnitTooltip(string baseTooltip, float value, string unit) + { + // Convert the internal lumens into the actual light unit value + value = LumenToUnit(value); + unit = k_UnitNames[(int)m_Unit]; + + return base.GetLightUnitTooltip(baseTooltip, value, unit); + } + + float UnitToLumen(float value) + { + if (m_Unit == LightUnit.Lumen) + return value; + + // Punctual slider currently does not have any regard for spot shape/reflector. + // Conversions need to happen as if light is a point, and this is the only setting that influences that. + m_Light.enableSpotReflector.boolValue = false; + + return HDLightUI.ConvertLightIntensity(m_Unit, LightUnit.Lumen, m_Light, m_Editor, value); + } + + float LumenToUnit(float value) + { + if (m_Unit == LightUnit.Lumen) + return value; + + // Once again temporarily disable reflector in case we called this for tooltip or context menu preset. + m_Light.enableSpotReflector.boolValue = false; + + value = HDLightUI.ConvertLightIntensity(LightUnit.Lumen, m_Unit, m_Light, m_Editor, value); + + // Restore the state of spot reflector on the light. + m_Light.enableSpotReflector.boolValue = m_SpotReflectorEnabled; + + return value; + } + + protected override void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset) + { + m_Light?.Update(); + + // Convert to the actual unit value. + value.floatValue = LumenToUnit(preset.presetValue); + + m_Light?.Apply(); + } + } +} diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs.meta b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs.meta new file mode 100644 index 00000000000..7706e6b2609 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/HDPunctualLightUnitSlider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76975b3cce013684cb3d854af3d22d46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/LightUnitSlider.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/LightUnitSlider.cs index 76da41d46c8..84fe914d9f8 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/LightUnitSlider.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/LightUnitSlider.cs @@ -254,351 +254,22 @@ protected virtual float GetPositionOnSlider(float value) } } - /// - /// Formats the provided descriptor into a piece-wise linear slider with contextual slider markers, tooltips, and icons. - /// - class PiecewiseLightUnitSlider : LightUnitSlider - { - struct Piece - { - public Vector2 domain; - public Vector2 range; - - public float directM; - public float directB; - public float inverseM; - public float inverseB; - } - - // Piecewise function indexed by value ranges. - private readonly Dictionary m_PiecewiseFunctionMap = new Dictionary(); - - static void ComputeTransformationParameters(float x0, float x1, float y0, float y1, out float m, out float b) - { - m = (y0 - y1) / (x0 - x1); - b = (m * -x0) + y0; - } - - static float DoTransformation(in float x, in float m, in float b) => (m * x) + b; - - // Ensure clamping to (0,1) as sometimes the function evaluates to slightly below 0 (breaking the handle). - static float ValueToSlider(Piece p, float x) => Mathf.Clamp01(DoTransformation(x, p.inverseM, p.inverseB)); - static float SliderToValue(Piece p, float x) => DoTransformation(x, p.directM, p.directB); - - // Ideally we want a continuous, monotonically increasing function, but this is useful as we can easily fit a - // distribution to a set of (huge) value ranges onto a slider. - public PiecewiseLightUnitSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) - { - // Sort the ranges into ascending order - var sortedRanges = m_Descriptor.valueRanges.OrderBy(x => x.value.x).ToArray(); - var sliderDistribution = m_Descriptor.sliderDistribution; - - // Compute the transformation for each value range. - for (int i = 0; i < sortedRanges.Length; i++) - { - var r = sortedRanges[i].value; - - var x0 = sliderDistribution[i + 0]; - var x1 = sliderDistribution[i + 1]; - var y0 = r.x; - var y1 = r.y; - - Piece piece; - piece.domain = new Vector2(x0, x1); - piece.range = new Vector2(y0, y1); - - ComputeTransformationParameters(x0, x1, y0, y1, out piece.directM, out piece.directB); - - // Compute the inverse - ComputeTransformationParameters(y0, y1, x0, x1, out piece.inverseM, out piece.inverseB); - - m_PiecewiseFunctionMap.Add(sortedRanges[i].value, piece); - } - } - - protected override float GetPositionOnSlider(float value, Vector2 valueRange) - { - if (!m_PiecewiseFunctionMap.TryGetValue(valueRange, out var piecewise)) - return -1f; - - return ValueToSlider(piecewise, value); - } - - // Search for the corresponding piece-wise function to a value on the domain and update the input piece to it. - // Returns true if search was successful and an update was made, false otherwise. - bool UpdatePiece(ref Piece piece, float x) - { - foreach (var pair in m_PiecewiseFunctionMap) - { - var p = pair.Value; - - if (x >= p.domain.x && x <= p.domain.y) - { - piece = p; - - return true; - } - } - - return false; - } - - void SliderOutOfBounds(Rect rect, ref float value) - { - EditorGUI.BeginChangeCheck(); - var internalValue = GUI.HorizontalSlider(rect, value, 0f, 1f); - if (EditorGUI.EndChangeCheck()) - { - Piece p = new Piece(); - UpdatePiece(ref p, internalValue); - value = SliderToValue(p, internalValue); - } - } - - protected override void DoSlider(Rect rect, ref float value, Vector2 sliderRange, Vector2 valueRange) - { - // Map the internal slider value to the current piecewise function - if (!m_PiecewiseFunctionMap.TryGetValue(valueRange, out var piece)) - { - // Assume that if the piece is not found, that means the unit value is out of bounds. - SliderOutOfBounds(rect, ref value); - return; - } - - // Maintain an internal value to support a single linear continuous function - EditorGUI.BeginChangeCheck(); - var internalValue = GUI.HorizontalSlider(rect, ValueToSlider(piece, value), 0f, 1f); - if (EditorGUI.EndChangeCheck()) - { - // Ensure that the current function piece is being used to transform the value - UpdatePiece(ref piece, internalValue); - value = SliderToValue(piece, internalValue); - } - } - } - - /// - /// Formats the provided descriptor into a punctual light unit slider with contextual slider markers, tooltips, and icons. - /// - class PunctualLightUnitSlider : PiecewiseLightUnitSlider - { - public PunctualLightUnitSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) { } - - private SerializedHDLight m_Light; - private Editor m_Editor; - private LightUnit m_Unit; - private bool m_SpotReflectorEnabled; - - // Note: these should be in sync with LightUnit - private static string[] k_UnitNames = - { - "Lumen", - "Candela", - "Lux", - "Nits", - "EV", - }; - - public void Setup(LightUnit unit, SerializedHDLight light, Editor owner) - { - m_Unit = unit; - m_Light = light; - m_Editor = owner; - - // Cache the spot reflector state as we will need to revert back to it after treating the slider as point light. - m_SpotReflectorEnabled = light.enableSpotReflector.boolValue; - } - - public override void Draw(Rect rect, SerializedProperty value, ref float floatValue) - { - // Convert the incoming unit value into Lumen as the punctual slider is always in these terms (internally) - float convertedValue = UnitToLumen(floatValue); - - EditorGUI.BeginChangeCheck(); - base.Draw(rect, value, ref convertedValue); - if (EditorGUI.EndChangeCheck()) - floatValue = LumenToUnit(convertedValue); - } - - protected override GUIContent GetLightUnitTooltip(string baseTooltip, float value, string unit) - { - // Convert the internal lumens into the actual light unit value - value = LumenToUnit(value); - unit = k_UnitNames[(int)m_Unit]; - - return base.GetLightUnitTooltip(baseTooltip, value, unit); - } - - float UnitToLumen(float value) - { - if (m_Unit == LightUnit.Lumen) - return value; - - // Punctual slider currently does not have any regard for spot shape/reflector. - // Conversions need to happen as if light is a point, and this is the only setting that influences that. - m_Light.enableSpotReflector.boolValue = false; - - return HDLightUI.ConvertLightIntensity(m_Unit, LightUnit.Lumen, m_Light, m_Editor, value); - } - - float LumenToUnit(float value) - { - if (m_Unit == LightUnit.Lumen) - return value; - - // Once again temporarily disable reflector in case we called this for tooltip or context menu preset. - m_Light.enableSpotReflector.boolValue = false; - - value = HDLightUI.ConvertLightIntensity(LightUnit.Lumen, m_Unit, m_Light, m_Editor, value); - - // Restore the state of spot reflector on the light. - m_Light.enableSpotReflector.boolValue = m_SpotReflectorEnabled; - - return value; - } - - protected override void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset) - { - m_Light?.Update(); - - // Convert to the actual unit value. - value.floatValue = LumenToUnit(preset.presetValue); - - m_Light?.Apply(); - } - } - - /// - /// Formats the provided descriptor into a temperature unit slider with contextual slider markers, tooltips, and icons. - /// - class TemperatureSlider : LightUnitSlider - { - private Vector3 m_ExponentialConstraints; - - private LightEditor.Settings m_Settings; - - private static Texture2D s_KelvinGradientTexture; - - /// - /// Exponential slider modeled to set a f(0.5) value. - /// ref: https://stackoverflow.com/a/17102320 - /// - void PrepareExponentialConstraints(float lo, float mi, float hi) - { - // float x = lo; - // float y = mi; - // float z = hi; - // - // // https://www.desmos.com/calculator/yx2yf4huia - // m_ExponentialConstraints.x = ((x * z) - (y * y)) / (x - (2 * y) + z); - // m_ExponentialConstraints.y = ((y - x) * (y - x)) / (x - (2 * y) + z); - // m_ExponentialConstraints.z = 2 * Mathf.Log((z - y) / (y - x)); - - // Warning: These are the coefficients for a system of equation fit for a continuous, monotonic curve that fits a f(0.44) value. - // f(0.44) is required instead of f(0.5) due to the location of the white in the temperature gradient texture. - // The equation is solved to get the coefficient for the following constraint for low, mid, hi: - // f(0) = 1500 - // f(0.44) = 6500 - // f(1.0) = 20000 - // If for any reason the constraints are changed, then the function must be refit and the new coefficients found. - // Note that we can't re-use the original PowerSlider instead due to how it forces a text field, which we don't want in this case. - m_ExponentialConstraints.x = -3935.53965427f; - m_ExponentialConstraints.y = 5435.53965427f; - m_ExponentialConstraints.z = 1.48240556f; - } - - protected float ValueToSlider(float x) => Mathf.Log((x - m_ExponentialConstraints.x) / m_ExponentialConstraints.y) / m_ExponentialConstraints.z; - protected float SliderToValue(float x) => m_ExponentialConstraints.x + m_ExponentialConstraints.y * Mathf.Exp(m_ExponentialConstraints.z * x); - - protected override float GetPositionOnSlider(float value, Vector2 valueRange) - { - return ValueToSlider(value); - } - - static Texture2D GetKelvinGradientTexture(LightEditor.Settings settings) - { - if (s_KelvinGradientTexture == null) - { - var kelvinTexture = (Texture2D)typeof(LightEditor.Settings).GetField("m_KelvinGradientTexture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(settings); - - // This seems to be the only way to gamma-correct the internal gradient tex (aside from drawing it manually). - var kelvinTextureLinear = new Texture2D(kelvinTexture.width, kelvinTexture.height, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.MipChain); - kelvinTextureLinear.SetPixels(kelvinTexture.GetPixels()); - kelvinTextureLinear.Apply(); - - s_KelvinGradientTexture = kelvinTextureLinear; - } - - return s_KelvinGradientTexture; - } - - public TemperatureSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) - { - var halfValue = 6500; - PrepareExponentialConstraints(m_Descriptor.sliderRange.x, halfValue, m_Descriptor.sliderRange.y); - } - - public void Setup(LightEditor.Settings settings) - { - m_Settings = settings; - } - - // The serialized property for color temperature is stored in the build-in light editor, and we need to use this object to apply the update. - protected override void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset) - { - m_Settings.Update(); - - base.SetValueToPreset(value, preset); - - m_Settings.ApplyModifiedProperties(); - } - - protected override void DoSlider(Rect rect, ref float value, Vector2 sliderRange) - { - SliderWithTextureNoTextField(rect, ref value, sliderRange, m_Settings); - } - - // Note: We could use the internal SliderWithTexture, however: the internal slider func forces a text-field (and no ability to opt-out of it). - void SliderWithTextureNoTextField(Rect rect, ref float value, Vector2 range, LightEditor.Settings settings) - { - GUI.DrawTexture(rect, GetKelvinGradientTexture(settings)); - - EditorGUI.BeginChangeCheck(); - - // Draw the exponential slider that fits 6500K to the white point on the gradient texture. - var internalValue = GUI.HorizontalSlider(rect, ValueToSlider(value), 0f, 1f, SliderStyles.k_TemperatureBorder, SliderStyles.k_TemperatureThumb); - - // Round to nearest since so much precision is not necessary for kelvin while sliding. - if (EditorGUI.EndChangeCheck()) - { - // Map the value back into kelvin. - value = SliderToValue(internalValue); - - value = Mathf.Round(value); - } - } - } - internal class LightUnitSliderUIDrawer { - static PiecewiseLightUnitSlider k_DirectionalLightUnitSlider; - static PunctualLightUnitSlider k_PunctualLightUnitSlider; - static PiecewiseLightUnitSlider k_ExposureSlider; - static TemperatureSlider k_TemperatureSlider; + static HDPiecewiseLightUnitSlider k_DirectionalLightUnitSlider; + static HDPunctualLightUnitSlider k_PunctualLightUnitSlider; + static HDPiecewiseLightUnitSlider k_ExposureSlider; static LightUnitSliderUIDrawer() { // Maintain a unique slider for directional/lux. - k_DirectionalLightUnitSlider = new PiecewiseLightUnitSlider(LightUnitSliderDescriptors.LuxDescriptor); + k_DirectionalLightUnitSlider = new HDPiecewiseLightUnitSlider(LightUnitSliderDescriptors.LuxDescriptor); // Internally, slider is always in terms of lumens, so that the slider is uniform for all light units. - k_PunctualLightUnitSlider = new PunctualLightUnitSlider(LightUnitSliderDescriptors.LumenDescriptor); + k_PunctualLightUnitSlider = new HDPunctualLightUnitSlider(LightUnitSliderDescriptors.LumenDescriptor); // Exposure is in EV100, but we load a separate due to the different icon set. - k_ExposureSlider = new PiecewiseLightUnitSlider(LightUnitSliderDescriptors.ExposureDescriptor); - - // Kelvin is not classified internally as a light unit so we handle it independently as well. - k_TemperatureSlider = new TemperatureSlider(LightUnitSliderDescriptors.TemperatureDescriptor); + k_ExposureSlider = new HDPiecewiseLightUnitSlider(LightUnitSliderDescriptors.ExposureDescriptor); } // Need to cache the serialized object on the slider, to add support for the preset selection context menu (need to apply changes to serialized) @@ -608,7 +279,6 @@ public void SetSerializedObject(SerializedObject serializedObject) k_DirectionalLightUnitSlider.SetSerializedObject(serializedObject); k_PunctualLightUnitSlider.SetSerializedObject(serializedObject); k_ExposureSlider.SetSerializedObject(serializedObject); - k_TemperatureSlider.SetSerializedObject(serializedObject); } public void Draw(HDLightType type, LightUnit lightUnit, SerializedProperty value, Rect rect, SerializedHDLight light, Editor owner) @@ -650,18 +320,5 @@ public void DrawExposureSlider(SerializedProperty value, Rect rect) value.floatValue = val; } } - - public void DrawTemperatureSlider(LightEditor.Settings settings, SerializedProperty value, Rect rect) - { - using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) - { - k_TemperatureSlider.Setup(settings); - - float val = value.floatValue; - k_TemperatureSlider.Draw(rect, value, ref val); - if (val != value.floatValue) - value.floatValue = val; - } - } } } diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs new file mode 100644 index 00000000000..1f1997643fc --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs @@ -0,0 +1,144 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering.HighDefinition; +using UnityEngine.Experimental.Rendering; + +namespace UnityEditor.Rendering.HighDefinition +{ + /// + /// Formats the provided descriptor into a temperature unit slider with contextual slider markers, tooltips, and icons. + /// + class TemperatureSlider : LightUnitSlider + { + private Vector3 m_ExponentialConstraints; + + private LightEditor.Settings m_Settings; + + private static Texture2D s_KelvinGradientTexture; + + /// + /// Exponential slider modeled to set a f(0.5) value. + /// ref: https://stackoverflow.com/a/17102320 + /// + void PrepareExponentialConstraints(float lo, float mi, float hi) + { + // float x = lo; + // float y = mi; + // float z = hi; + // + // // https://www.desmos.com/calculator/yx2yf4huia + // m_ExponentialConstraints.x = ((x * z) - (y * y)) / (x - (2 * y) + z); + // m_ExponentialConstraints.y = ((y - x) * (y - x)) / (x - (2 * y) + z); + // m_ExponentialConstraints.z = 2 * Mathf.Log((z - y) / (y - x)); + + // Warning: These are the coefficients for a system of equation fit for a continuous, monotonic curve that fits a f(0.44) value. + // f(0.44) is required instead of f(0.5) due to the location of the white in the temperature gradient texture. + // The equation is solved to get the coefficient for the following constraint for low, mid, hi: + // f(0) = 1500 + // f(0.44) = 6500 + // f(1.0) = 20000 + // If for any reason the constraints are changed, then the function must be refit and the new coefficients found. + // Note that we can't re-use the original PowerSlider instead due to how it forces a text field, which we don't want in this case. + m_ExponentialConstraints.x = -3935.53965427f; + m_ExponentialConstraints.y = 5435.53965427f; + m_ExponentialConstraints.z = 1.48240556f; + } + + protected float ValueToSlider(float x) => Mathf.Log((x - m_ExponentialConstraints.x) / m_ExponentialConstraints.y) / m_ExponentialConstraints.z; + protected float SliderToValue(float x) => m_ExponentialConstraints.x + m_ExponentialConstraints.y * Mathf.Exp(m_ExponentialConstraints.z * x); + + protected override float GetPositionOnSlider(float value, Vector2 valueRange) + { + return ValueToSlider(value); + } + + static Texture2D GetKelvinGradientTexture(LightEditor.Settings settings) + { + if (s_KelvinGradientTexture == null) + { + var kelvinTexture = (Texture2D)typeof(LightEditor.Settings).GetField("m_KelvinGradientTexture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(settings); + + // This seems to be the only way to gamma-correct the internal gradient tex (aside from drawing it manually). + var kelvinTextureLinear = new Texture2D(kelvinTexture.width, kelvinTexture.height, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.MipChain); + kelvinTextureLinear.SetPixels(kelvinTexture.GetPixels()); + kelvinTextureLinear.Apply(); + + s_KelvinGradientTexture = kelvinTextureLinear; + } + + return s_KelvinGradientTexture; + } + + public TemperatureSlider(LightUnitSliderUIDescriptor descriptor) : base(descriptor) + { + var halfValue = 6500; + PrepareExponentialConstraints(m_Descriptor.sliderRange.x, halfValue, m_Descriptor.sliderRange.y); + } + + public void Setup(LightEditor.Settings settings) + { + m_Settings = settings; + } + + // The serialized property for color temperature is stored in the build-in light editor, and we need to use this object to apply the update. + protected override void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset) + { + m_Settings.Update(); + + base.SetValueToPreset(value, preset); + + m_Settings.ApplyModifiedProperties(); + } + + protected override void DoSlider(Rect rect, ref float value, Vector2 sliderRange) + { + SliderWithTextureNoTextField(rect, ref value, sliderRange, m_Settings); + } + + // Note: We could use the internal SliderWithTexture, however: the internal slider func forces a text-field (and no ability to opt-out of it). + void SliderWithTextureNoTextField(Rect rect, ref float value, Vector2 range, LightEditor.Settings settings) + { + GUI.DrawTexture(rect, GetKelvinGradientTexture(settings)); + + EditorGUI.BeginChangeCheck(); + + // Draw the exponential slider that fits 6500K to the white point on the gradient texture. + var internalValue = GUI.HorizontalSlider(rect, ValueToSlider(value), 0f, 1f, SliderStyles.k_TemperatureBorder, SliderStyles.k_TemperatureThumb); + + // Round to nearest since so much precision is not necessary for kelvin while sliding. + if (EditorGUI.EndChangeCheck()) + { + // Map the value back into kelvin. + value = SliderToValue(internalValue); + + value = Mathf.Round(value); + } + } + } + + internal class TemperatureSliderUIDrawer + { + static TemperatureSlider k_TemperatureSlider; + + static TemperatureSliderUIDrawer() + { + // Kelvin is not classified internally as a light unit so we handle it independently as well. + k_TemperatureSlider = new TemperatureSlider(LightUnitSliderDescriptors.TemperatureDescriptor); + } + + public static void Draw(LightEditor.Settings settings, SerializedObject serializedObject, SerializedProperty value, Rect rect) + { + k_TemperatureSlider.SetSerializedObject(serializedObject); + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) + { + k_TemperatureSlider.Setup(settings); + + float val = value.floatValue; + k_TemperatureSlider.Draw(rect, value, ref val); + if (val != value.floatValue) + value.floatValue = val; + } + } + } +} diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs.meta b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs.meta new file mode 100644 index 00000000000..63fd33f4d99 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/LightUnit/TemperatureSlider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33d6ef6a49896aa44a810a84e3cd394f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: