using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEditorInternal; using RuntimeSRPPreferences = UnityEngine.Rendering.CoreRenderPipelinePreferences; namespace UnityEditor.Rendering { using CED = CoreEditorDrawer; internal class ProbeAdjustmentColorPreferences { internal static Func GetColorPrefProbeVolumeGizmoColor; internal static Color s_ProbeAdjustmentVolumeGizmoColorDefault = new Color32(222, 132, 144, 45); static ProbeAdjustmentColorPreferences() { GetColorPrefProbeVolumeGizmoColor = RuntimeSRPPreferences.RegisterPreferenceColor("Adaptive Probe Volumes/Probe Adjustment Volume Gizmo", s_ProbeAdjustmentVolumeGizmoColorDefault); } } [CanEditMultipleObjects] [CustomEditor(typeof(ProbeAdjustmentVolume))] internal class ProbeAdjustmentVolumeEditor : Editor { internal static class Styles { internal static readonly GUIContent s_VORotateTool = EditorGUIUtility.TrIconContent("RotateTool", "The virtual offset direction for probes falling in this volume."); internal static readonly GUIContent s_SORotateTool = EditorGUIUtility.TrIconContent("RotateTool", "The direction used to sample the ambient probe for probes falling in this volume."); internal static readonly GUIContent s_VolumeHeader = EditorGUIUtility.TrTextContent("Influence Volume"); internal static readonly GUIContent s_AdjustmentHeader = EditorGUIUtility.TrTextContent("Probe Volume Overrides"); internal static readonly GUIContent s_Mode = new GUIContent("Mode", "Choose which type of adjustment to apply to probes covered by this volume."); internal static readonly GUIContent s_DilationThreshold = new GUIContent("Dilation Validity Threshold", "Override the Dilation Validity Threshold for probes covered by this Probe Adjustment Volume. Higher values increase the chance of probes being considered invalid."); internal static readonly GUIContent virtualOffsetThreshold = new GUIContent("Validity Threshold", "Override the Virtual Offset Validity Threshold for probes covered by this Probe Adjustment Volume. Higher values increase the chance of probes being considered invalid."); internal static readonly GUIContent s_VODirection = new GUIContent("Rotation", "Rotate the axis along which probes will be pushed when applying Virtual Offset."); internal static readonly GUIContent s_VODistance = new GUIContent("Distance", "Determines how far probes are pushed in the direction of the Virtual Offset."); internal static readonly GUIContent renderingLayerMaskOperation = new GUIContent("Operation", "The operation to combine the Rendering Layer Mask set by this adjustment volume with the Rendering Layer Mask of the probes covered by this volume."); internal static readonly GUIContent renderingLayerMask = new GUIContent("Rendering Layer Mask", "Sets the Rendering Layer Mask to be combined with the Rendering Layer Mask of the probes covered by this volume."); internal static readonly GUIContent s_PreviewLighting = new GUIContent("Preview Probe Adjustments", "Quickly preview the effect of adjustments on probes covered by this volume."); internal static readonly GUIContent skyOcclusionSampleCount = new GUIContent("Sample Count", "Controls the number of samples per probe for sky occlusion baking."); internal static readonly GUIContent skyOcclusionMaxBounces = new GUIContent("Max Bounces", "Controls the number of bounces per light path for sky occlusion baking."); internal static readonly string s_AdjustmentVolumeChangedMessage = "This Adjustment Volume has never been baked, or has changed since the last bake. Re-bake Probe Volumes to ensure lighting data is valid."; internal static readonly EditMode.SceneViewEditMode VirtualOffsetEditMode = (EditMode.SceneViewEditMode)110; internal static readonly EditMode.SceneViewEditMode SkyDirectionEditMode = (EditMode.SceneViewEditMode)110; internal static readonly Color k_GizmoColorBase = ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault; internal static readonly Color[] k_BaseHandlesColor = new Color[] { ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault, ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault, ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault, ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault, ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault, ProbeAdjustmentColorPreferences.s_ProbeAdjustmentVolumeGizmoColorDefault }; } static class ProbeAdjustmentVolumeUI { public static readonly CED.IDrawer Inspector = null; enum AdditionalProperties { Adjustments = 1 << 0, } enum Expandable { Volume = 1 << 0, Adjustments = 1 << 1, } readonly static ExpandedState k_ExpandedState = new ExpandedState(Expandable.Volume | Expandable.Adjustments); readonly static AdditionalPropertiesState k_AdditionalPropertiesState = new AdditionalPropertiesState(0); public static void DrawVolumeContent(SerializedProbeAdjustmentVolume serialized, Editor owner) { EditorGUILayout.PropertyField(serialized.shape); EditorGUILayout.PropertyField(serialized.shape.intValue == 0 ? serialized.size : serialized.radius); } static T[] RemoveAt(T[] values, int index) { var list = new List(values); list.RemoveAt(index); return list.ToArray(); } static GUIContent[] CastArray(string[] values) { var result = new GUIContent[values.Length]; for (int i = 0; i < values.Length; i++) result[i] = new GUIContent(ObjectNames.NicifyVariableName(values[i])); return result; } public static void DrawAdjustmentContent(SerializedProbeAdjustmentVolume serialized, Editor owner) { ProbeAdjustmentVolume ptv = (owner.target as ProbeAdjustmentVolume); var bakingSet = ProbeVolumeBakingSet.GetBakingSetForScene(ptv.gameObject.scene); bool useVirtualOffset = bakingSet != null ? bakingSet.settings.virtualOffsetSettings.useVirtualOffset : false; bool useSkyOcclusion = bakingSet != null ? bakingSet.skyOcclusion : false; var hiddenMode = (int)ProbeAdjustmentVolume.Mode.IntensityScale; var availableValues = (int[])Enum.GetValues(typeof(ProbeAdjustmentVolume.Mode)); var availableModes = CastArray(Enum.GetNames(typeof(ProbeAdjustmentVolume.Mode))); if (!k_AdditionalPropertiesState[AdditionalProperties.Adjustments] && serialized.mode.intValue != hiddenMode) { int idx = Array.IndexOf(availableValues, hiddenMode); availableValues = RemoveAt(availableValues, idx); availableModes = RemoveAt(availableModes, idx); } EditorGUI.BeginChangeCheck(); int newValue = EditorGUILayout.IntPopup(Styles.s_Mode, serialized.mode.intValue, availableModes, availableValues); if (EditorGUI.EndChangeCheck()) serialized.mode.intValue = newValue; if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.OverrideValidityThreshold) { EditorGUILayout.PropertyField(serialized.overriddenDilationThreshold, Styles.s_DilationThreshold); } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.ApplyVirtualOffset) { EditorGUI.BeginDisabledGroup(!useVirtualOffset); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(serialized.virtualOffsetRotation, Styles.s_VODirection); var editMode = Styles.VirtualOffsetEditMode; EditorGUI.BeginChangeCheck(); GUILayout.Toggle(editMode == EditMode.editMode, Styles.s_VORotateTool, EditorStyles.miniButton, GUILayout.Width(28f)); if (EditorGUI.EndChangeCheck()) { EditMode.SceneViewEditMode targetMode = EditMode.editMode == editMode ? EditMode.SceneViewEditMode.None : editMode; EditMode.ChangeEditMode(targetMode, GetBounds(serialized, owner), owner); } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(serialized.virtualOffsetDistance, Styles.s_VODistance); EditorGUI.EndDisabledGroup(); if (!useVirtualOffset) { CoreEditorUtils.DrawFixMeBox("Apply Virtual Offset can be used only if Virtual Offset is enabled for the Baking Set.", MessageType.Warning, "Open", () => { ProbeVolumeLightingTab.OpenBakingSet(bakingSet); }); } } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.OverrideVirtualOffsetSettings) { EditorGUI.BeginDisabledGroup(!useVirtualOffset); EditorGUILayout.PropertyField(serialized.virtualOffsetThreshold, Styles.virtualOffsetThreshold); EditorGUILayout.PropertyField(serialized.geometryBias); EditorGUILayout.PropertyField(serialized.rayOriginBias); EditorGUI.EndDisabledGroup(); if (!useVirtualOffset) { CoreEditorUtils.DrawFixMeBox("Override Virtual Offset can be used only if Virtual Offset is enabled for the Baking Set.", MessageType.Warning, "Open", () => { ProbeVolumeLightingTab.OpenBakingSet(bakingSet); }); } } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.OverrideSampleCount) { EditorGUILayout.LabelField("Probes", EditorStyles.miniBoldLabel); using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.PropertyField(serialized.directSampleCount); EditorGUILayout.PropertyField(serialized.indirectSampleCount); EditorGUILayout.PropertyField(serialized.sampleCountMultiplier); EditorGUILayout.PropertyField(serialized.maxBounces); } EditorGUILayout.Space(); EditorGUILayout.LabelField("Sky Occlusion", EditorStyles.miniBoldLabel); using (new EditorGUI.DisabledGroupScope(bakingSet != null && !bakingSet.skyOcclusion)) using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.PropertyField(serialized.skyOcclusionSampleCount, Styles.skyOcclusionSampleCount); EditorGUILayout.PropertyField(serialized.skyOcclusionMaxBounces, Styles.skyOcclusionMaxBounces); } } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.IntensityScale) { EditorGUILayout.PropertyField(serialized.intensityScale); EditorGUILayout.HelpBox("Overriding the intensity of probes can break the physical plausibility of lighting. This may result in unwanted visual inconsistencies.", MessageType.Info, wide: true); } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.OverrideSkyDirection) { if (!SupportedRenderingFeatures.active.skyOcclusion) { EditorGUILayout.HelpBox("Sky Occlusion is not supported by this Render Pipeline.", MessageType.Warning); return; } var editMode = Styles.SkyDirectionEditMode; EditorGUILayout.BeginHorizontal(); using (new EditorGUI.DisabledScope(!SupportedRenderingFeatures.active.skyOcclusion)) { using (new EditorGUI.DisabledScope(editMode == EditMode.editMode)) EditorGUILayout.PropertyField(serialized.skyDirection); EditorGUI.BeginChangeCheck(); GUILayout.Toggle(editMode == EditMode.editMode, Styles.s_SORotateTool, EditorStyles.miniButton, GUILayout.Width(28f)); if (EditorGUI.EndChangeCheck()) { EditMode.SceneViewEditMode targetMode = EditMode.editMode == editMode ? EditMode.SceneViewEditMode.None : editMode; EditMode.ChangeEditMode(targetMode, GetBounds(serialized, owner), owner); } } EditorGUILayout.EndHorizontal(); if (!useSkyOcclusion) { CoreEditorUtils.DrawFixMeBox("Overriding Sky Direction can be used only if Sky Occlusion is enabled for the Baking Set.", MessageType.Warning, "Open", () => { ProbeVolumeLightingTab.OpenBakingSet(bakingSet); }); } } else if (serialized.mode.intValue == (int)ProbeAdjustmentVolume.Mode.OverrideRenderingLayerMask) { if (bakingSet != null && !bakingSet.useRenderingLayers) { CoreEditorUtils.DrawFixMeBox("Override Rendering Layer can be used only if Rendering Layers are enabled for the Baking Set.", MessageType.Warning, "Open", () => { ProbeVolumeLightingTab.OpenBakingSet(bakingSet); }); } else { EditorGUILayout.PropertyField(serialized.renderingLayerMaskOperation, Styles.renderingLayerMaskOperation); string[] options; if (bakingSet != null) { options = new string[bakingSet.renderingLayerMasks.Length]; for (int i = 0; i < bakingSet.renderingLayerMasks.Length; i++) options[i] = bakingSet.renderingLayerMasks[i].name; } else { options = new string[APVDefinitions.probeMaxRegionCount]; for (int i = 0; i < APVDefinitions.probeMaxRegionCount; i++) options[i] = "Mask " + (i + 1); } if (options.Length == 0) { using (new EditorGUI.DisabledScope(true)) EditorGUILayout.MaskField(Styles.renderingLayerMask, 0, new[] { "Nothing" }); } else { EditorGUI.BeginChangeCheck(); int newMask = EditorGUILayout.MaskField(Styles.renderingLayerMask, serialized.renderingLayerMask.intValue, options); if (EditorGUI.EndChangeCheck()) serialized.renderingLayerMask.uintValue = (uint)newMask; } } } } static void DrawBakingHelpers(SerializedProbeAdjustmentVolume p, Editor owner) { ProbeAdjustmentVolume ptv = owner.target as ProbeAdjustmentVolume; var bakingSet = ProbeVolumeBakingSet.GetBakingSetForScene(ptv.gameObject.scene); if (owner.targets.Length == 1) { EditorGUILayout.Space(); using (new EditorGUI.DisabledScope(Lightmapping.isRunning || bakingSet == null)) { using (new EditorGUI.DisabledScope(AdaptiveProbeVolumes.isRunning)) if (GUILayout.Button(Styles.s_PreviewLighting)) AdaptiveProbeVolumes.BakeAdjustmentVolume(bakingSet, ptv); ProbeVolumeLightingTab.BakeAPVButton(); } } if (ptv.cachedHashCode != ptv.GetHashCode()) { EditorGUILayout.Space(); EditorGUILayout.HelpBox(Styles.s_AdjustmentVolumeChangedMessage, MessageType.Warning); } } internal static Bounds GetBounds(SerializedProbeAdjustmentVolume serialized, Editor owner) { var position = ((Component)owner.target).transform.position; if (serialized.shape.intValue == (int)ProbeAdjustmentVolume.Shape.Box) return new Bounds(position, serialized.size.vector3Value); if (serialized.shape.intValue == (int)ProbeAdjustmentVolume.Shape.Box) return new Bounds(position, serialized.radius.floatValue * Vector3.up); return default; } public static void DrawAdditionalContent(SerializedProbeAdjustmentVolume serialized, Editor owner) { } static ProbeAdjustmentVolumeUI() { Inspector = CED.Group( CED.FoldoutGroup(Styles.s_VolumeHeader, Expandable.Volume, k_ExpandedState, (serialized, owner) => DrawVolumeContent(serialized, owner)), CED.AdditionalPropertiesFoldoutGroup(Styles.s_AdjustmentHeader, Expandable.Adjustments, k_ExpandedState, AdditionalProperties.Adjustments, k_AdditionalPropertiesState, CED.Group((serialized, owner) => DrawAdjustmentContent(serialized, owner)), DrawAdditionalContent), CED.Group(null, GroupOption.None, DrawBakingHelpers) ); } } SerializedProbeAdjustmentVolume m_SerializedAdjustmentVolume; internal const EditMode.SceneViewEditMode k_EditShape = EditMode.SceneViewEditMode.ReflectionProbeBox; static HierarchicalBox _ShapeBox; static HierarchicalBox s_ShapeBox { get { if (_ShapeBox == null) _ShapeBox = new HierarchicalBox(Styles.k_GizmoColorBase, Styles.k_BaseHandlesColor); return _ShapeBox; } } static HierarchicalSphere _ShapeSphere; static HierarchicalSphere s_ShapeSphere { get { if (_ShapeSphere == null) _ShapeSphere = new HierarchicalSphere(Styles.k_GizmoColorBase); return _ShapeSphere; } } protected void OnEnable() { m_SerializedAdjustmentVolume = new SerializedProbeAdjustmentVolume(serializedObject); ProbeVolumeDebug.s_ActiveAdjustmentVolumes++; } protected void OnDisable() { ProbeVolumeDebug.s_ActiveAdjustmentVolumes--; } public override void OnInspectorGUI() { if (!ProbeReferenceVolume.instance.isInitialized || !ProbeReferenceVolume.instance.enabledBySRP) { ProbeVolumeEditor.APVDisabledHelpBox(); return; } ProbeVolumeEditor.FrameSettingDisabledHelpBox(); serializedObject.Update(); ProbeAdjustmentVolumeUI.Inspector.Draw(m_SerializedAdjustmentVolume, this); serializedObject.ApplyModifiedProperties(); } [DrawGizmo(GizmoType.InSelectionHierarchy)] static void DrawGizmosSelected(ProbeAdjustmentVolume adjustmentVolume, GizmoType gizmoType) { using (new Handles.DrawingScope(Matrix4x4.TRS(adjustmentVolume.transform.position, adjustmentVolume.transform.rotation, Vector3.one))) { if (adjustmentVolume.shape == ProbeAdjustmentVolume.Shape.Box) { s_ShapeBox.center = Vector3.zero; s_ShapeBox.size = adjustmentVolume.size; s_ShapeBox.SetBaseColor(ProbeAdjustmentColorPreferences.GetColorPrefProbeVolumeGizmoColor()); s_ShapeBox.DrawHull(true); } else if (adjustmentVolume.shape == ProbeAdjustmentVolume.Shape.Sphere) { s_ShapeSphere.center = Vector3.zero; s_ShapeSphere.radius = adjustmentVolume.radius; s_ShapeSphere.DrawHull(true); } if (adjustmentVolume.mode == ProbeAdjustmentVolume.Mode.ApplyVirtualOffset) { ArrowHandle(0, Quaternion.Euler(adjustmentVolume.virtualOffsetRotation), adjustmentVolume.virtualOffsetDistance); } } using (new Handles.DrawingScope(Matrix4x4.TRS(adjustmentVolume.transform.position, Quaternion.identity, Vector3.one))) { if (adjustmentVolume.mode == ProbeAdjustmentVolume.Mode.OverrideSkyDirection) { var editMode = Styles.SkyDirectionEditMode; if (editMode != EditMode.editMode) { var quat = Quaternion.FromToRotation(Vector3.forward, adjustmentVolume.skyDirection); adjustmentVolume.skyShadingDirectionRotation = quat.eulerAngles; } ArrowHandle(0, Quaternion.Euler(adjustmentVolume.skyShadingDirectionRotation), 1.0f); } } } static void ArrowHandle(int controlID, Quaternion rotation, float distance) { Handles.matrix *= Matrix4x4.Rotate(rotation); float thickness = Handles.lineThickness; float coneSize = .05f; var linePos = (distance - coneSize) * Vector3.forward; var conePos = (distance - coneSize * 0.5f) * Vector3.forward; if (distance < coneSize) { conePos = coneSize * 0.5f * Vector3.forward; Handles.matrix *= Matrix4x4.Scale(new Vector3(1, 1, distance / coneSize)); } Handles.DrawLine(Vector3.zero, linePos, thickness); Handles.ConeHandleCap(controlID, conePos, Quaternion.identity, coneSize, EventType.Repaint); } protected void OnSceneGUI() { ProbeAdjustmentVolume adjustmentVolume = target as ProbeAdjustmentVolume; var position = Quaternion.Inverse(adjustmentVolume.transform.rotation) * adjustmentVolume.transform.position; //important: if the origin of the handle's space move along the handle, //handles displacement will appears as moving two time faster. using (new Handles.DrawingScope(Matrix4x4.TRS(Vector3.zero, adjustmentVolume.transform.rotation, Vector3.one))) { if (adjustmentVolume.shape == ProbeAdjustmentVolume.Shape.Box) { //contained must be initialized in all case s_ShapeBox.center = position; s_ShapeBox.size = adjustmentVolume.size; s_ShapeBox.monoHandle = false; EditorGUI.BeginChangeCheck(); s_ShapeBox.DrawHandle(); if (EditorGUI.EndChangeCheck()) { Undo.RecordObjects(new UnityEngine.Object[] { adjustmentVolume, adjustmentVolume.transform }, "Change Adjustment Volume Bounding Box"); adjustmentVolume.size = s_ShapeBox.size; Vector3 delta = adjustmentVolume.transform.rotation * s_ShapeBox.center - adjustmentVolume.transform.position; adjustmentVolume.transform.position += delta; ; } } else if (adjustmentVolume.shape == ProbeAdjustmentVolume.Shape.Sphere) { s_ShapeSphere.center = position; s_ShapeSphere.radius = adjustmentVolume.radius; EditorGUI.BeginChangeCheck(); s_ShapeSphere.DrawHandle(); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(adjustmentVolume, "Change Adjustment Volume Radius"); adjustmentVolume.radius = s_ShapeSphere.radius; } } if (adjustmentVolume.mode == ProbeAdjustmentVolume.Mode.ApplyVirtualOffset && EditMode.editMode == Styles.VirtualOffsetEditMode) { EditorGUI.BeginChangeCheck(); Quaternion rotation = Handles.RotationHandle(Quaternion.Euler(adjustmentVolume.virtualOffsetRotation), position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(adjustmentVolume, "Change Virtual Offset Direction"); adjustmentVolume.virtualOffsetRotation = rotation.eulerAngles; } } } if (adjustmentVolume.mode == ProbeAdjustmentVolume.Mode.OverrideSkyDirection && EditMode.editMode == Styles.SkyDirectionEditMode) { EditorGUI.BeginChangeCheck(); Quaternion rotation = Handles.RotationHandle(Quaternion.Euler(adjustmentVolume.skyShadingDirectionRotation), adjustmentVolume.transform.position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(adjustmentVolume, "Change Sky Shading Direction"); adjustmentVolume.skyShadingDirectionRotation = rotation.eulerAngles; adjustmentVolume.skyDirection = rotation * Vector3.forward; adjustmentVolume.skyDirection.Normalize(); } } } [MenuItem("CONTEXT/ProbeAdjustmentVolume/Rendering Debugger...")] internal static void AddAdjustmentVolumeContextMenu() { ProbeVolumeLightingTab.OpenProbeVolumeDebugPanel(null, null, 0); } } }