diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 69d1b936354..e50057855fb 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -802,6 +802,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Increased path tracing BSDFs roughness range from [0.001, 0.999] to [0.00001, 0.99999]. - Changing the default SSGI radius for the all configurations. - Changed the default parameters for quality RTGI to match expected behavior. +- Improved DoF UI/UX (case 1240204) ## [7.1.1] - 2019-09-05 diff --git a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs index b83fe275edf..eae91f55b7e 100644 --- a/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs +++ b/com.unity.render-pipelines.high-definition/Editor/PostProcessing/DepthOfFieldEditor.cs @@ -60,66 +60,182 @@ public override void OnInspectorGUI() int mode = m_FocusMode.value.intValue; if (mode == (int)DepthOfFieldMode.Off) - return; + { + GUI.enabled = false; + } + + // Draw the focus mode controls + HDEditorUtils.BeginIndent(); + DrawFocusSettings(mode); + HDEditorUtils.EndIndent(); + + EditorGUILayout.Space(); + // Draw the quality controls base.OnInspectorGUI(); + HDEditorUtils.BeginIndent(); + GUI.enabled = GUI.enabled && base.overrideState; + DrawQualitySettings(); + HDEditorUtils.EndIndent(); - bool advanced = isInAdvancedMode; + GUI.enabled = true; + } - if (mode == (int)DepthOfFieldMode.UsePhysicalCamera) + void DrawFocusSettings(int mode) + { + if (mode == (int)DepthOfFieldMode.Off) + { + // When DoF is off, display a focus distance at infinity + var val = m_FocusDistance.value.floatValue; + m_FocusDistance.value.floatValue = Mathf.Infinity; + PropertyField(m_FocusDistance); + m_FocusDistance.value.floatValue = val; + } + else if (mode == (int)DepthOfFieldMode.UsePhysicalCamera) { PropertyField(m_FocusDistance); - - if (advanced) - { - GUI.enabled = useCustomValue; - EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel); - PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); - PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); - - EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel); - PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); - PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); - GUI.enabled = true; - } } else if (mode == (int)DepthOfFieldMode.Manual) { - EditorGUILayout.Space(); - - EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel); + EditorGUILayout.LabelField("Near Range", EditorStyles.miniLabel); PropertyField(m_NearFocusStart, EditorGUIUtility.TrTextContent("Start")); PropertyField(m_NearFocusEnd, EditorGUIUtility.TrTextContent("End")); - if (advanced) - { - GUI.enabled = useCustomValue; - PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); - PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); - GUI.enabled = true; - } - - EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel); + EditorGUILayout.LabelField("Far Range", EditorStyles.miniLabel); PropertyField(m_FarFocusStart, EditorGUIUtility.TrTextContent("Start")); PropertyField(m_FarFocusEnd, EditorGUIUtility.TrTextContent("End")); - - if (advanced) - { - GUI.enabled = useCustomValue; - PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); - PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); - GUI.enabled = true; - } } + } + + void DrawQualitySettings() + { + object oldSettings = SaveCustomQualitySettingsAsObject(); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.LabelField("Near Blur", EditorStyles.miniLabel); + PropertyField(m_NearSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); + PropertyField(m_NearMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); + + EditorGUILayout.LabelField("Far Blur", EditorStyles.miniLabel); + PropertyField(m_FarSampleCount, EditorGUIUtility.TrTextContent("Sample Count")); + PropertyField(m_FarMaxBlur, EditorGUIUtility.TrTextContent("Max Radius")); - if (advanced) + if (isInAdvancedMode) { - GUI.enabled = useCustomValue; EditorGUILayout.LabelField("Advanced Tweaks", EditorStyles.miniLabel); PropertyField(m_Resolution); PropertyField(m_HighQualityFiltering); - GUI.enabled = true; } + + if (EditorGUI.EndChangeCheck()) + { + object newSettings = SaveCustomQualitySettingsAsObject(); + + if (!QualitySettingsBlob.IsEqual(oldSettings as QualitySettingsBlob, newSettings as QualitySettingsBlob)) + QualitySettingsWereChanged(); + } + } + + /// An opaque binary blob storing preset settings (used to remember what were the last custom settings that were used). + /// For the functionality to save and restore the settings + class QualitySettingsBlob + { + public int nearSampleCount; + public float nearMaxBlur; + public int farSampleCount; + public float farMaxBlur; + public DepthOfFieldResolution resolution; + public bool hqFiltering; + + public bool[] overrideState = new bool[6]; + + public static bool IsEqual(QualitySettingsBlob left, QualitySettingsBlob right) + { + if ((right == null && left != null) || (right != null && left == null)) + { + return false; + } + + if (right == null && left == null) + { + return true; + } + + for (int i=0; i < left.overrideState.Length; ++i) + { + if (left.overrideState[i] != right.overrideState[i]) + { + return false; + } + } + + return left.nearSampleCount == right.nearSampleCount + && left.nearMaxBlur == right.nearMaxBlur + && left.farSampleCount == right.farSampleCount + && left.farMaxBlur == right.farMaxBlur + && left.resolution == right.resolution + && left.hqFiltering == right.hqFiltering; + } + } + + public override void LoadSettingsFromObject(object settings) + { + QualitySettingsBlob qualitySettings = settings as QualitySettingsBlob; + + m_NearSampleCount.value.intValue = qualitySettings.nearSampleCount; + m_NearMaxBlur.value.floatValue = qualitySettings.nearMaxBlur; + m_FarSampleCount.value.intValue = qualitySettings.farSampleCount; + m_FarMaxBlur.value.floatValue = qualitySettings.farMaxBlur; + m_Resolution.value.intValue = (int) qualitySettings.resolution; + m_HighQualityFiltering.value.boolValue = qualitySettings.hqFiltering; + + m_NearSampleCount.overrideState.boolValue = qualitySettings.overrideState[0]; + m_NearMaxBlur.overrideState.boolValue = qualitySettings.overrideState[1]; + m_FarSampleCount.overrideState.boolValue = qualitySettings.overrideState[2]; + m_FarMaxBlur.overrideState.boolValue = qualitySettings.overrideState[3]; + m_Resolution.overrideState.boolValue = qualitySettings.overrideState[4]; + m_HighQualityFiltering.overrideState.boolValue = qualitySettings.overrideState[5]; + } + + public override void LoadSettingsFromQualityPreset(RenderPipelineSettings settings, int level) + { + m_NearSampleCount.value.intValue = settings.postProcessQualitySettings.NearBlurSampleCount[level]; + m_NearMaxBlur.value.floatValue = settings.postProcessQualitySettings.NearBlurMaxRadius[level]; + + m_FarSampleCount.value.intValue = settings.postProcessQualitySettings.FarBlurSampleCount[level]; + m_FarMaxBlur.value.floatValue = settings.postProcessQualitySettings.FarBlurMaxRadius[level]; + + m_Resolution.value.intValue = (int) settings.postProcessQualitySettings.DoFResolution[level]; + m_HighQualityFiltering.value.boolValue = settings.postProcessQualitySettings.DoFHighQualityFiltering[level]; + + // set all quality override states to true, to indicate that these values are actually used + m_NearSampleCount.overrideState.boolValue = true; + m_NearMaxBlur.overrideState.boolValue = true; + m_FarSampleCount.overrideState.boolValue = true; + m_FarMaxBlur.overrideState.boolValue = true; + m_Resolution.overrideState.boolValue = true; + m_HighQualityFiltering.overrideState.boolValue = true; + + } + + public override object SaveCustomQualitySettingsAsObject(object history = null) + { + QualitySettingsBlob qualitySettings = (history != null) ? history as QualitySettingsBlob : new QualitySettingsBlob(); + + qualitySettings.nearSampleCount = m_NearSampleCount.value.intValue; + qualitySettings.nearMaxBlur = m_NearMaxBlur.value.floatValue; + qualitySettings.farSampleCount = m_FarSampleCount.value.intValue; + qualitySettings.farMaxBlur = m_FarMaxBlur.value.floatValue; + qualitySettings.resolution = (DepthOfFieldResolution) m_Resolution.value.intValue; + qualitySettings.hqFiltering = m_HighQualityFiltering.value.boolValue; + + qualitySettings.overrideState[0] = m_NearSampleCount.overrideState.boolValue; + qualitySettings.overrideState[1] = m_NearMaxBlur.overrideState.boolValue; + qualitySettings.overrideState[2] = m_FarSampleCount.overrideState.boolValue; + qualitySettings.overrideState[3] = m_FarMaxBlur.overrideState.boolValue; + qualitySettings.overrideState[4] = m_Resolution.overrideState.boolValue; + qualitySettings.overrideState[5] = m_HighQualityFiltering.overrideState.boolValue; + + return qualitySettings; } } } diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs index 29c7745ea9b..3007a4a775f 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDEditorUtils.cs @@ -277,6 +277,30 @@ internal static void HandlePrefixLabelWithIndent(Rect totalPosition, Rect labelP labelPosition.x += EditorGUI.indentLevel * 15; EditorGUI.HandlePrefixLabel(totalPosition, labelPosition, label); } + + /// + /// Like EditorGUI.indentLevel++ but this one will also indent the override checkboxes + /// + internal static void BeginIndent() + { + // When using EditorGUI.indentLevel++, the clicking on the checkboxes does not work properly due to some issues on the C++ side. + // This function is a work-around for this issue. + const float offset = 15f; + GUILayout.BeginHorizontal(); + EditorGUILayout.Space(offset, false); + GUILayout.BeginVertical(); + EditorGUIUtility.labelWidth -= offset; + } + + /// + /// To be used for resetting the indentation after calling BeginIndent + /// + internal static void EndIndent() + { + EditorGUIUtility.labelWidth = 0f; + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } } internal static partial class SerializedPropertyExtension diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs index c790e1c7918..3d4cbb9e06e 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/VolumeComponentWithQualityEditor.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; +using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; namespace UnityEditor.Rendering.HighDefinition @@ -7,15 +9,106 @@ internal abstract class VolumeComponentWithQualityEditor : VolumeComponentEditor // Quality settings SerializedDataParameter m_QualitySetting; + // Note: Editors are refreshed on gui changes by the volume system, so any state that we want to store here needs to be a static (or in a serialized variable) + // We use ConditionalWeakTable instead of a Dictionary of InstanceIDs to get automatic clean-up of dead entries in the table + static ConditionalWeakTable s_CustomSettingsHistory = new ConditionalWeakTable(); + + static readonly int k_CustomQuality = ScalableSettingLevelParameter.LevelCount; + public override void OnEnable() { var o = new PropertyFetcher(serializedObject); m_QualitySetting = Unpack(o.Find(x => x.quality)); } - public override void OnInspectorGUI() =>PropertyField(m_QualitySetting); + public override void OnInspectorGUI() + { + int prevQualityLevel = m_QualitySetting.value.intValue; + + EditorGUI.BeginChangeCheck(); + PropertyField(m_QualitySetting); + + // When a quality preset changes, we want to detect and reflect the settings in the UI. PropertyFields mirror the contents of one memory loccation, so + // the idea is that we copy the presets to that location. This logic is optional, if volume components don't override the helper functions at the end, + // they will continue to work, but the preset settings will not be reflected in the UI. + if (EditorGUI.EndChangeCheck()) + { + // The ScalableSettingLevelParameterEditor updates the value of the referenced object directly (and not the reflected one), + // so this is what we have to do to get the new value selected by the user: + var o = m_QualitySetting.GetObjectRef(); + var (preset, custom) = o.levelAndOverride; + int newQualityLevel = custom ? 3 : preset; + m_QualitySetting.value.intValue = newQualityLevel; + + if (newQualityLevel == k_CustomQuality) + { + // If we have switched to custom quality from a preset, then load the last custom quality settings the user has used in this volume + if (prevQualityLevel != k_CustomQuality) + { + object history = null; + s_CustomSettingsHistory.TryGetValue(serializedObject.targetObject, out history); + if (history != null) + { + LoadSettingsFromObject(history); + } + } + } + else + { + // If we are going to use a quality preset, then load the preset values so they are reflected in the UI + var pipeline = (HDRenderPipeline)RenderPipelineManager.currentPipeline; + if (pipeline != null) + { + // If we switch from a custom quality level, then save these values so we can re-use them if teh user switches back + if (prevQualityLevel == k_CustomQuality) + { + int key = serializedObject.targetObject.GetInstanceID(); + object history = null; + s_CustomSettingsHistory.TryGetValue(serializedObject.targetObject, out history); + if (history != null) + { + SaveCustomQualitySettingsAsObject(history); + } + else + { + // Only keep track of custom settings for components that implement the new interface (and return not null) + history = SaveCustomQualitySettingsAsObject(); + if (history != null) + { + s_CustomSettingsHistory.Add(serializedObject.targetObject, history); + } + + } + } + LoadSettingsFromQualityPreset(pipeline.currentPlatformRenderPipelineSettings, newQualityLevel); + } + } + } + } + + protected bool useCustomValue => m_QualitySetting.value.intValue == k_CustomQuality; + protected bool overrideState => m_QualitySetting.overrideState.boolValue; + + /// + /// This should be called after the user manually edits a quality setting that appears in a preset. After calling this function, the quality preset will change to Custom. + /// + public void QualitySettingsWereChanged() { m_QualitySetting.value.intValue = k_CustomQuality; } + + /// + /// This function should be overriden by a volume component to load preset settings from RenderPipelineSettings + /// + public virtual void LoadSettingsFromQualityPreset(RenderPipelineSettings settings, int level) { } + + /// + /// This function should be overriden by a volume component to return an opaque object (binary blob) with the custom quality settings currently in use. + /// + public virtual object SaveCustomQualitySettingsAsObject(object history = null) { return null; } + + /// + /// This function should be overriden by a volume component to load a custom preset setting from an opaque binary blob (as returned from SaveCustomQualitySettingsAsObject) + /// + public virtual void LoadSettingsFromObject(object settings) { } - protected bool useCustomValue => m_QualitySetting.value.intValue == ScalableSettingLevelParameter.LevelCount; } }