forked from BilalY/Rasagar
345 lines
16 KiB
C#
345 lines
16 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
using UnityEditorInternal;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.HighDefinition;
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Linq;
|
|
using UnityEditor.Experimental.GraphView;
|
|
|
|
namespace UnityEditor.Rendering.HighDefinition
|
|
{
|
|
[CustomEditor(typeof(CustomPassVolume)), CanEditMultipleObjects]
|
|
sealed class CustomPassVolumeEditor : Editor
|
|
{
|
|
ReorderableList m_CustomPassList;
|
|
string m_ListName;
|
|
CustomPassVolume m_Volume;
|
|
MaterialEditor[] m_MaterialEditors = new MaterialEditor[0];
|
|
int m_CustomPassMaterialsHash;
|
|
bool m_SupportListMultiEditing;
|
|
|
|
const string k_DefaultListName = "Custom Passes";
|
|
|
|
private const int k_GlobalCustomPassMode = 0;
|
|
private const int k_LocalCustomPassMode = 1;
|
|
private const int k_CameraCustomPassMode = 2;
|
|
|
|
static MethodInfo reorderableListInvalidateCacheMethod = typeof(ReorderableList).GetMethod("InvalidateCacheRecursive", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
static class Styles
|
|
{
|
|
public static readonly GUIContent isGlobal = EditorGUIUtility.TrTextContent("Mode", "Global Volumes affect the Camera wherever the Camera is in the Scene and Local Volumes affect the Camera if they encapsulate the Camera within the bounds of their Collider. A camera volume is applied only for the target camera.");
|
|
public static readonly GUIContent fadeRadius = EditorGUIUtility.TrTextContent("Fade Radius", "Radius from where your effect will be rendered, the _FadeValue in shaders will be updated using this radius");
|
|
public static readonly GUIContent injectionPoint = EditorGUIUtility.TrTextContent("Injection Point", "The stage in the render pipeline to execute this pass.");
|
|
public static readonly GUIContent priority = EditorGUIUtility.TrTextContent("Priority", "A value which determines the execution order when there are several Custom Pass Volumes with the same injection point overlaps. Custom Pass Volume with a higher value is rendered first.");
|
|
public static readonly GUIContent targetCamera = EditorGUIUtility.TrTextContent("Target Camera", "Determines on which camera this custom pass volume will be applied. If this property is null and the mode is set to camera, the volume is ignored.");
|
|
public static readonly GUIContent addColliderFixMessage = EditorGUIUtility.TrTextContentWithIcon("Add a Collider to this GameObject to set boundaries for the local Volume.", CoreEditorStyles.iconWarn);
|
|
public static readonly GUIContent addBoxCollider = EditorGUIUtility.TrTextContent("Add a Box Collider");
|
|
public static readonly GUIContent sphereBoxCollider = EditorGUIUtility.TrTextContent("Add a Sphere Collider");
|
|
public static readonly GUIContent capsuleBoxCollider = EditorGUIUtility.TrTextContent("Add a Capsule Collider");
|
|
public static readonly GUIContent meshBoxCollider = EditorGUIUtility.TrTextContent("Add a Mesh Collider");
|
|
public static readonly GUIContent enableColliderFixMessage = EditorGUIUtility.TrTextContentWithIcon("Local Volumes need a collider enabled. Enable the collider.", CoreEditorStyles.iconWarn);
|
|
public static readonly GUIContent[] modes = { EditorGUIUtility.TrTextContent("Global"), EditorGUIUtility.TrTextContent("Local"), EditorGUIUtility.TrTextContent("Camera") };
|
|
}
|
|
|
|
class SerializedPassVolume
|
|
{
|
|
public SerializedProperty isGlobal;
|
|
public SerializedProperty useTargetCamera;
|
|
public SerializedProperty targetCamera;
|
|
public SerializedProperty fadeRadius;
|
|
public SerializedProperty customPasses;
|
|
public SerializedProperty injectionPoint;
|
|
public SerializedProperty priority;
|
|
}
|
|
|
|
|
|
SerializedPassVolume m_SerializedPassVolume;
|
|
|
|
void OnEnable()
|
|
{
|
|
m_Volume = target as CustomPassVolume;
|
|
|
|
using (var o = new PropertyFetcher<CustomPassVolume>(serializedObject))
|
|
{
|
|
m_SerializedPassVolume = new SerializedPassVolume
|
|
{
|
|
isGlobal = o.Find(x => x.isGlobal),
|
|
useTargetCamera = o.Find(x => x.useTargetCamera),
|
|
targetCamera = o.Find(x => x.m_TargetCamera),
|
|
injectionPoint = o.Find(x => x.injectionPoint),
|
|
customPasses = o.Find(x => x.customPasses),
|
|
fadeRadius = o.Find(x => x.fadeRadius),
|
|
priority = o.Find(x => x.priority),
|
|
};
|
|
}
|
|
|
|
CreateReorderableList(m_SerializedPassVolume.customPasses);
|
|
|
|
UpdateMaterialEditors();
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
if (HDRenderPipeline.currentAsset == null || !HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.supportCustomPass)
|
|
HDEditorUtils.QualitySettingsHelpBox("The current HDRP asset does not support Custom Passes.", MessageType.Error,
|
|
HDRenderPipelineUI.ExpandableGroup.Rendering, "m_RenderPipelineSettings.supportCustomPass");
|
|
|
|
DrawSettingsGUI();
|
|
DrawCustomPassReorderableList();
|
|
DrawMaterialsGUI();
|
|
}
|
|
|
|
List<Material> GatherCustomPassesMaterials()
|
|
=> m_Volume.customPasses.Where(p => p != null).SelectMany(p => p.RegisterMaterialForInspector()).Where(m => m != null).ToList();
|
|
|
|
void UpdateMaterialEditors()
|
|
{
|
|
var materials = GatherCustomPassesMaterials();
|
|
|
|
// Destroy all material editors:
|
|
foreach (var materialEditor in m_MaterialEditors)
|
|
CoreUtils.Destroy(materialEditor);
|
|
|
|
m_MaterialEditors = new MaterialEditor[materials.Count];
|
|
for (int i = 0; i < materials.Count; i++)
|
|
m_MaterialEditors[i] = CreateEditor(materials[i], typeof(MaterialEditor)) as MaterialEditor;
|
|
}
|
|
|
|
void DrawMaterialsGUI()
|
|
{
|
|
int materialsHash = GatherCustomPassesMaterials().Aggregate(0, (c, m) => c += m.GetHashCode());
|
|
|
|
if (materialsHash != m_CustomPassMaterialsHash)
|
|
UpdateMaterialEditors();
|
|
|
|
// Draw the material inspectors:
|
|
foreach (var materialEditor in m_MaterialEditors)
|
|
{
|
|
using (new EditorGUI.DisabledScope((materialEditor.target.hideFlags & HideFlags.NotEditable) != 0))
|
|
{
|
|
materialEditor.DrawHeader();
|
|
materialEditor.OnInspectorGUI();
|
|
}
|
|
}
|
|
|
|
m_CustomPassMaterialsHash = materialsHash;
|
|
}
|
|
|
|
Dictionary<CustomPass, CustomPassDrawer> customPassDrawers = new Dictionary<CustomPass, CustomPassDrawer>();
|
|
CustomPassDrawer GetCustomPassDrawer(SerializedProperty pass, CustomPass reference, int listIndex)
|
|
{
|
|
CustomPassDrawer drawer;
|
|
|
|
if (customPassDrawers.TryGetValue(reference, out drawer))
|
|
return drawer;
|
|
|
|
var customPass = m_Volume.customPasses[listIndex];
|
|
|
|
if (customPass == null)
|
|
return null;
|
|
|
|
foreach (var drawerType in TypeCache.GetTypesWithAttribute(typeof(CustomPassDrawerAttribute)))
|
|
{
|
|
var attr = drawerType.GetCustomAttributes(typeof(CustomPassDrawerAttribute), true)[0] as CustomPassDrawerAttribute;
|
|
if (attr.targetPassType == customPass.GetType())
|
|
{
|
|
drawer = Activator.CreateInstance(drawerType) as CustomPassDrawer;
|
|
drawer.SetPass(customPass);
|
|
break;
|
|
}
|
|
if (attr.targetPassType.IsAssignableFrom(customPass.GetType()))
|
|
{
|
|
drawer = Activator.CreateInstance(drawerType) as CustomPassDrawer;
|
|
drawer.SetPass(customPass);
|
|
}
|
|
}
|
|
|
|
customPassDrawers[reference] = drawer;
|
|
|
|
return drawer;
|
|
}
|
|
|
|
void DrawSettingsGUI()
|
|
{
|
|
serializedObject.Update();
|
|
|
|
int GetMode() => m_SerializedPassVolume.useTargetCamera.boolValue ? k_CameraCustomPassMode : (m_SerializedPassVolume.isGlobal.boolValue ? k_GlobalCustomPassMode : k_LocalCustomPassMode);
|
|
void SetMode(int value)
|
|
{
|
|
m_SerializedPassVolume.isGlobal.boolValue = value == k_GlobalCustomPassMode;
|
|
m_SerializedPassVolume.useTargetCamera.boolValue = value == k_CameraCustomPassMode;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
{
|
|
Rect isGlobalRect = EditorGUILayout.GetControlRect();
|
|
EditorGUI.BeginProperty(isGlobalRect, Styles.isGlobal, m_SerializedPassVolume.isGlobal);
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
int selectedMode = EditorGUI.Popup(isGlobalRect, Styles.isGlobal, GetMode(), Styles.modes);
|
|
if (EditorGUI.EndChangeCheck())
|
|
SetMode(selectedMode);
|
|
if (selectedMode == k_LocalCustomPassMode)
|
|
{
|
|
if (m_Volume.TryGetComponent<Collider>(out var collider))
|
|
{
|
|
if (!collider.enabled)
|
|
CoreEditorUtils.DrawFixMeBox(Styles.enableColliderFixMessage, () => collider.enabled = true);
|
|
}
|
|
else
|
|
{
|
|
CoreEditorUtils.DrawFixMeBox(Styles.addColliderFixMessage, () =>
|
|
{
|
|
var menu = new GenericMenu();
|
|
menu.AddItem(Styles.addBoxCollider, false, () => Undo.AddComponent<BoxCollider>(m_Volume.gameObject));
|
|
menu.AddItem(Styles.sphereBoxCollider, false, () => Undo.AddComponent<SphereCollider>(m_Volume.gameObject));
|
|
menu.AddItem(Styles.capsuleBoxCollider, false, () => Undo.AddComponent<CapsuleCollider>(m_Volume.gameObject));
|
|
menu.AddItem(Styles.meshBoxCollider, false, () => Undo.AddComponent<MeshCollider>(m_Volume.gameObject));
|
|
menu.ShowAsContext();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
EditorGUI.EndProperty();
|
|
|
|
if (m_SerializedPassVolume.useTargetCamera.boolValue)
|
|
{
|
|
EditorGUILayout.PropertyField(m_SerializedPassVolume.targetCamera, Styles.targetCamera);
|
|
EditorGUILayout.PropertyField(m_SerializedPassVolume.injectionPoint, Styles.injectionPoint);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.PropertyField(m_SerializedPassVolume.injectionPoint, Styles.injectionPoint);
|
|
EditorGUILayout.PropertyField(m_SerializedPassVolume.priority, Styles.priority);
|
|
if (!m_SerializedPassVolume.isGlobal.boolValue)
|
|
EditorGUILayout.PropertyField(m_SerializedPassVolume.fadeRadius, Styles.fadeRadius);
|
|
}
|
|
}
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
// UUM-8410 custom pass UI height is not updated
|
|
reorderableListInvalidateCacheMethod.Invoke(m_CustomPassList, null);
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
void DrawCustomPassReorderableList()
|
|
{
|
|
if (targets.OfType<CustomPassVolume>().Count() > 1)
|
|
{
|
|
EditorGUILayout.HelpBox("Custom Pass List UI is not supported with multi-selection", MessageType.Warning, true);
|
|
return;
|
|
}
|
|
|
|
// Sanitize list:
|
|
for (int i = 0; i < m_SerializedPassVolume.customPasses.arraySize; i++)
|
|
{
|
|
if (m_SerializedPassVolume.customPasses.GetArrayElementAtIndex(i) == null)
|
|
{
|
|
m_SerializedPassVolume.customPasses.DeleteArrayElementAtIndex(i);
|
|
serializedObject.ApplyModifiedProperties();
|
|
i++;
|
|
}
|
|
}
|
|
|
|
float customPassListHeight = m_CustomPassList.GetHeight();
|
|
var customPassRect = EditorGUILayout.GetControlRect(false, customPassListHeight);
|
|
EditorGUI.BeginProperty(customPassRect, GUIContent.none, m_SerializedPassVolume.customPasses);
|
|
{
|
|
EditorGUILayout.BeginVertical();
|
|
m_CustomPassList.DoList(customPassRect);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
EditorGUI.EndProperty();
|
|
}
|
|
|
|
void CreateReorderableList(SerializedProperty passList)
|
|
{
|
|
m_CustomPassList = new ReorderableList(passList.serializedObject, passList);
|
|
|
|
m_CustomPassList.drawHeaderCallback = (rect) =>
|
|
{
|
|
EditorGUI.LabelField(rect, k_DefaultListName, EditorStyles.largeLabel);
|
|
};
|
|
|
|
m_CustomPassList.multiSelect = false;
|
|
m_CustomPassList.drawElementCallback = (rect, index, active, focused) =>
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
passList.serializedObject.ApplyModifiedProperties();
|
|
var customPass = passList.GetArrayElementAtIndex(index);
|
|
customPass.managedReferenceValue = m_Volume.customPasses[index];
|
|
var drawer = GetCustomPassDrawer(customPass, m_Volume.customPasses[index], index);
|
|
if (drawer != null)
|
|
drawer.OnGUI(rect, customPass, null);
|
|
else
|
|
EditorGUI.PropertyField(rect, customPass);
|
|
if (EditorGUI.EndChangeCheck())
|
|
customPass.serializedObject.ApplyModifiedProperties();
|
|
};
|
|
|
|
m_CustomPassList.elementHeightCallback = (index) =>
|
|
{
|
|
passList.serializedObject.ApplyModifiedProperties();
|
|
var customPass = passList.GetArrayElementAtIndex(index);
|
|
var drawer = GetCustomPassDrawer(customPass, m_Volume.customPasses[index], index);
|
|
if (drawer != null)
|
|
return drawer.GetPropertyHeight(customPass, null);
|
|
else
|
|
return EditorGUI.GetPropertyHeight(customPass, null);
|
|
};
|
|
|
|
m_CustomPassList.onAddCallback += (list) =>
|
|
{
|
|
var searchObject = ScriptableObject.CreateInstance<CustomPassListSearchWindow>();
|
|
searchObject.Initialize(AddCustomPass);
|
|
var windowPosition = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
|
|
SearchWindow.Open(new SearchWindowContext(windowPosition), searchObject);
|
|
};
|
|
|
|
m_CustomPassList.onReorderCallback = (index) =>
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
ClearCustomPassCache();
|
|
};
|
|
|
|
m_CustomPassList.onRemoveCallback = (list) =>
|
|
{
|
|
foreach (int index in list.selectedIndices)
|
|
passList.DeleteArrayElementAtIndex(index);
|
|
serializedObject.ApplyModifiedProperties();
|
|
ClearCustomPassCache();
|
|
};
|
|
|
|
void AddCustomPass(Type customPassType)
|
|
{
|
|
foreach (CustomPassVolume volume in targets)
|
|
{
|
|
Undo.RegisterCompleteObjectUndo(volume, "Add custom pass");
|
|
|
|
passList.serializedObject.ApplyModifiedProperties();
|
|
volume.AddPassOfType(customPassType);
|
|
UpdateMaterialEditors();
|
|
passList.serializedObject.Update();
|
|
// Notify the prefab that something have changed:
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(volume);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearCustomPassCache()
|
|
{
|
|
// When custom passes are re-ordered, a topological change happens in the SerializedProperties
|
|
// So we have to rebuild all the drawers that were storing SerializedProperties.
|
|
customPassDrawers.Clear();
|
|
}
|
|
|
|
float GetCustomPassEditorHeight(SerializedProperty pass) => EditorGUIUtility.singleLineHeight;
|
|
}
|
|
}
|