You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
565 lines
22 KiB
565 lines
22 KiB
using System.Collections.Generic; |
|
using System.Linq; |
|
using UnityEditor.SceneManagement; |
|
using UnityEditorInternal; |
|
using UnityEngine; |
|
using UnityEngine.Rendering.Universal; |
|
|
|
namespace UnityEditor.Rendering.Universal |
|
{ |
|
using Styles = UniversalRenderPipelineCameraUI.Styles; |
|
|
|
[CustomEditorForRenderPipeline(typeof(Camera), typeof(UniversalRenderPipelineAsset))] |
|
[CanEditMultipleObjects] |
|
class UniversalRenderPipelineCameraEditor : CameraEditor |
|
{ |
|
ReorderableList m_LayerList; |
|
|
|
public Camera camera => target as Camera; |
|
static Camera selectedCameraInStack; |
|
|
|
List<Camera> validCameras = new List<Camera>(); |
|
List<Camera> m_TypeErrorCameras = new List<Camera>(); |
|
List<Camera> m_NotSupportedOverlayCameras = new List<Camera>(); |
|
List<Camera> m_IncompatibleCameras = new List<Camera>(); |
|
List<(Camera, UniversalRenderPipelineSerializedCamera)> m_OutputWarningCameras = new(); |
|
|
|
UniversalRenderPipelineSerializedCamera m_SerializedCamera; |
|
|
|
public new void OnEnable() |
|
{ |
|
base.OnEnable(); |
|
settings.OnEnable(); |
|
selectedCameraInStack = null; |
|
m_SerializedCamera = new UniversalRenderPipelineSerializedCamera(serializedObject, settings); |
|
|
|
validCameras.Clear(); |
|
m_TypeErrorCameras.Clear(); |
|
m_NotSupportedOverlayCameras.Clear(); |
|
m_IncompatibleCameras.Clear(); |
|
m_OutputWarningCameras.Clear(); |
|
|
|
UpdateCameras(); |
|
} |
|
|
|
void UpdateCameras() |
|
{ |
|
m_SerializedCamera.Refresh(); |
|
|
|
m_LayerList = new ReorderableList(m_SerializedCamera.serializedObject, m_SerializedCamera.cameras, true, true, true, true) |
|
{ |
|
drawHeaderCallback = rect => EditorGUI.LabelField(rect, Styles.cameras), |
|
drawElementCallback = DrawElementCallback, |
|
onSelectCallback = SelectElement, |
|
onRemoveCallback = RemoveCamera, |
|
onCanRemoveCallback = CanRemoveCamera, |
|
onAddDropdownCallback = AddCameraToCameraList |
|
}; |
|
} |
|
|
|
bool CanRemoveCamera(ReorderableList list) => m_SerializedCamera.numCameras > 0; |
|
|
|
void RemoveCamera(ReorderableList list) |
|
{ |
|
// As multi selection is disabled, selectedIndices will only return 1 element, remove that element from the list |
|
if (list.selectedIndices.Any()) |
|
{ |
|
m_SerializedCamera.cameras.DeleteArrayElementAtIndex(list.selectedIndices.First()); |
|
} |
|
else |
|
{ |
|
// Nothing selected, remove the last item on the list |
|
ReorderableList.defaultBehaviours.DoRemoveButton(list); |
|
} |
|
|
|
// Force update the list as removed camera could been there |
|
m_TypeErrorCameras.Clear(); |
|
m_NotSupportedOverlayCameras.Clear(); |
|
m_IncompatibleCameras.Clear(); |
|
m_OutputWarningCameras.Clear(); |
|
} |
|
|
|
void SelectElement(ReorderableList list) |
|
{ |
|
var element = m_SerializedCamera.cameras.GetArrayElementAtIndex(list.index); |
|
selectedCameraInStack = element.objectReferenceValue as Camera; |
|
if (Event.current.clickCount == 2) |
|
{ |
|
Selection.activeObject = selectedCameraInStack; |
|
} |
|
|
|
EditorGUIUtility.PingObject(selectedCameraInStack); |
|
} |
|
|
|
void DrawElementCallback(Rect rect, int index, bool isActive, bool isFocused) |
|
{ |
|
rect.height = EditorGUIUtility.singleLineHeight; |
|
rect.y += 1; |
|
|
|
(Camera camera, UniversalRenderPipelineSerializedCamera serializedCamera) overlayCamera = m_SerializedCamera[index]; |
|
Camera cam = overlayCamera.camera; |
|
|
|
if (cam != null) |
|
{ |
|
var baseAdditionalData = camera.GetUniversalAdditionalCameraData(); |
|
bool outputWarning = false; |
|
|
|
// Checking if the Base Camera and the overlay cameras are of the same type. |
|
// If not, we report an error. |
|
var overlayAdditionalData = cam.GetUniversalAdditionalCameraData(); |
|
var type = overlayAdditionalData.renderType; |
|
|
|
GUIContent errorContent = EditorGUIUtility.TrTextContent(type.GetName()); ; |
|
|
|
|
|
var renderer = overlayAdditionalData.scriptableRenderer; |
|
|
|
if (baseAdditionalData.scriptableRenderer.GetType() != renderer.GetType()) |
|
{ |
|
if (!m_IncompatibleCameras.Contains(cam)) |
|
{ |
|
m_IncompatibleCameras.Add(cam); |
|
} |
|
|
|
errorContent = EditorGUIUtility.TrTextContent("", |
|
$"Only cameras with compatible renderer types can be stacked. " + |
|
$"The camera: {cam.name} are using the renderer {renderer.GetType().Name}, " + |
|
$"but the base camera: {camera.name} are using {baseAdditionalData.scriptableRenderer.GetType().Name}. Will skip rendering", CoreEditorStyles.iconFail); |
|
} |
|
else if (m_IncompatibleCameras.Contains(cam)) |
|
{ |
|
m_IncompatibleCameras.Remove(cam); |
|
} |
|
|
|
// Check if the renderer on the camera we are checking does indeed support overlay camera |
|
// This can fail due to changing the renderer in the UI to a renderer that does not support overlay cameras |
|
// The UI will not stop you from changing the renderer sadly so this will have to tell the user that the |
|
// entry in the stack now is invalid. |
|
else if (!renderer.SupportsCameraStackingType(CameraRenderType.Overlay)) |
|
{ |
|
if (!m_NotSupportedOverlayCameras.Contains(cam)) |
|
{ |
|
m_NotSupportedOverlayCameras.Add(cam); |
|
} |
|
|
|
errorContent = EditorGUIUtility.TrTextContent("", |
|
$"The camera: {cam.name} is using a renderer of type {renderer.GetType().Name} which does not support Overlay cameras in it's current state.", CoreEditorStyles.iconFail); |
|
} |
|
else if (m_NotSupportedOverlayCameras.Contains(cam)) |
|
{ |
|
m_NotSupportedOverlayCameras.Remove(cam); |
|
} |
|
|
|
else if (type != CameraRenderType.Overlay) |
|
{ |
|
if (!m_TypeErrorCameras.Contains(cam)) |
|
{ |
|
m_TypeErrorCameras.Add(cam); |
|
} |
|
errorContent = EditorGUIUtility.TrTextContent(type.GetName(), $"Stack can only contain Overlay cameras. The camera: {cam.name} " + |
|
$"has a type {type} that is not supported. Will skip rendering.", |
|
CoreEditorStyles.iconFail); |
|
} |
|
else if (m_TypeErrorCameras.Contains(cam)) |
|
{ |
|
m_TypeErrorCameras.Remove(cam); |
|
} |
|
|
|
if (IsStackCameraOutputDirty(cam, overlayCamera.serializedCamera)) |
|
{ |
|
outputWarning = true; |
|
if (!m_OutputWarningCameras.Exists(c => c.Item1 == cam)) |
|
{ |
|
m_OutputWarningCameras.Add((cam, overlayCamera.serializedCamera)); |
|
} |
|
} |
|
else |
|
{ |
|
|
|
m_OutputWarningCameras.RemoveAll(c => c.Item1 == cam); |
|
} |
|
|
|
|
|
GUIContent nameContent = |
|
outputWarning ? |
|
EditorGUIUtility.TrTextContent(cam.name, "Output properties do not match base camera", CoreEditorStyles.iconWarn) : |
|
EditorGUIUtility.TrTextContent(cam.name); |
|
|
|
EditorGUI.BeginProperty(rect, GUIContent.none, m_SerializedCamera.cameras.GetArrayElementAtIndex(index)); |
|
var labelWidth = EditorGUIUtility.labelWidth; |
|
EditorGUIUtility.labelWidth -= 20f; |
|
|
|
using (var iconSizeScope = new EditorGUIUtility.IconSizeScope(new Vector2(rect.height, rect.height))) |
|
{ |
|
EditorGUI.LabelField(rect, nameContent, errorContent); |
|
} |
|
|
|
// Printing if Post Processing is on or not. |
|
var isPostActive = cam.GetUniversalAdditionalCameraData().renderPostProcessing; |
|
if (isPostActive) |
|
{ |
|
Rect selectRect = new Rect(rect.width - 20, rect.y, 50, EditorGUIUtility.singleLineHeight); |
|
|
|
EditorGUI.LabelField(selectRect, "PP"); |
|
} |
|
EditorGUI.EndProperty(); |
|
|
|
EditorGUIUtility.labelWidth = labelWidth; |
|
} |
|
else |
|
{ |
|
camera.GetUniversalAdditionalCameraData().UpdateCameraStack(); |
|
|
|
// Need to clean out the errorCamera list here. |
|
m_TypeErrorCameras.Clear(); |
|
m_OutputWarningCameras.Clear(); |
|
} |
|
} |
|
|
|
// Modified version of StageHandle.FindComponentsOfType<T>() |
|
// This version more closely represents unity object referencing restrictions. |
|
// I added these restrictions: |
|
// - Can not reference scene object outside scene |
|
// - Can not reference cross scenes |
|
// - Can reference child objects if it is prefab |
|
Camera[] FindCamerasToReference(GameObject gameObject) |
|
{ |
|
var scene = gameObject.scene; |
|
|
|
var inScene = !EditorUtility.IsPersistent(camera) || scene.IsValid(); |
|
var inPreviewScene = EditorSceneManager.IsPreviewScene(scene) && scene.IsValid(); |
|
var inCurrentScene = !EditorUtility.IsPersistent(camera) && scene.IsValid(); |
|
|
|
Camera[] cameras = Resources.FindObjectsOfTypeAll<Camera>(); |
|
List<Camera> result = new List<Camera>(); |
|
if (!inScene) |
|
{ |
|
foreach (var camera in cameras) |
|
{ |
|
if (camera.transform.IsChildOf(gameObject.transform)) |
|
result.Add(camera); |
|
} |
|
} |
|
else if (inPreviewScene) |
|
{ |
|
foreach (var camera in cameras) |
|
{ |
|
if (camera.gameObject.scene == scene) |
|
result.Add(camera); |
|
} |
|
} |
|
else if (inCurrentScene) |
|
{ |
|
foreach (var camera in cameras) |
|
{ |
|
if (!EditorUtility.IsPersistent(camera) && !EditorSceneManager.IsPreviewScene(camera.gameObject.scene) && camera.gameObject.scene == scene) |
|
result.Add(camera); |
|
} |
|
} |
|
|
|
return result.ToArray(); |
|
} |
|
|
|
void AddCameraToCameraList(Rect rect, ReorderableList list) |
|
{ |
|
// Need to do clear the list here otherwise the menu just fills up with more and more entries |
|
validCameras.Clear(); |
|
// Need to get the base renderer here first |
|
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer.GetType(); |
|
var allCameras = FindCamerasToReference(camera.gameObject); |
|
foreach (var camera in allCameras) |
|
{ |
|
var component = camera.GetUniversalAdditionalCameraData(); |
|
if (component != null) |
|
{ |
|
if (component.renderType == CameraRenderType.Overlay && |
|
component.scriptableRenderer.GetType() == renderer) |
|
{ |
|
validCameras.Add(camera); |
|
} |
|
} |
|
} |
|
|
|
var names = new GUIContent[validCameras.Count]; |
|
for (int i = 0; i < validCameras.Count; ++i) |
|
{ |
|
names[i] = new GUIContent((i + 1) + " " + validCameras[i].name); |
|
} |
|
|
|
if (!validCameras.Any()) |
|
{ |
|
names = new GUIContent[1]; |
|
names[0] = new GUIContent("No Overlay Cameras exist."); |
|
} |
|
EditorUtility.DisplayCustomMenu(rect, names, -1, AddCameraToCameraListMenuSelected, null); |
|
} |
|
|
|
void AddCameraToCameraListMenuSelected(object userData, string[] options, int selected) |
|
{ |
|
if (!validCameras.Any()) |
|
return; |
|
|
|
m_SerializedCamera.cameras.InsertArrayElementAtIndex(m_SerializedCamera.numCameras); |
|
m_SerializedCamera.cameras.GetArrayElementAtIndex(m_SerializedCamera.numCameras - 1).objectReferenceValue = validCameras[selected]; |
|
m_SerializedCamera.serializedAdditionalDataObject.ApplyModifiedProperties(); |
|
|
|
m_SerializedCamera.Refresh(); |
|
|
|
(Camera camera, UniversalRenderPipelineSerializedCamera serializedCamera) overlayCamera = m_SerializedCamera[m_SerializedCamera.numCameras - 1]; |
|
UpdateStackCameraOutput(overlayCamera.camera, overlayCamera.serializedCamera); |
|
} |
|
|
|
public new void OnDisable() |
|
{ |
|
base.OnDisable(); |
|
} |
|
|
|
// IsPreset is an internal API - lets reuse the usable part of this function |
|
// 93 is a "magic number" and does not represent a combination of other flags here |
|
internal static bool IsPresetEditor(UnityEditor.Editor editor) |
|
{ |
|
return (int)((editor.target as Component).gameObject.hideFlags) == 93; |
|
} |
|
|
|
public override void OnInspectorGUI() |
|
{ |
|
var rpAsset = UniversalRenderPipeline.asset; |
|
if (rpAsset == null) |
|
{ |
|
base.OnInspectorGUI(); |
|
return; |
|
} |
|
|
|
m_SerializedCamera.Update(); |
|
|
|
if (IsPresetEditor(this)) |
|
{ |
|
UniversalRenderPipelineCameraUI.PresetInspector.Draw(m_SerializedCamera, this); |
|
} |
|
else |
|
{ |
|
UniversalRenderPipelineCameraUI.Inspector.Draw(m_SerializedCamera, this); |
|
} |
|
|
|
m_SerializedCamera.Apply(); |
|
} |
|
|
|
private void UpdateStackCameraToOverlay() |
|
{ |
|
var additionalCameraData = selectedCameraInStack.GetUniversalAdditionalCameraData(); |
|
if (additionalCameraData == null) |
|
return; |
|
|
|
if (additionalCameraData.renderType == CameraRenderType.Base) |
|
{ |
|
Undo.RecordObject(additionalCameraData, Styles.inspectorOverlayCameraText); |
|
additionalCameraData.renderType = CameraRenderType.Overlay; |
|
EditorUtility.SetDirty(additionalCameraData); |
|
} |
|
} |
|
|
|
private void UpdateStackCameraOutput(Camera cam, UniversalRenderPipelineSerializedCamera serializedCamera) |
|
{ |
|
if ((CameraRenderType)serializedCamera.cameraType.intValue == CameraRenderType.Base) |
|
return; |
|
|
|
serializedCamera.Update(); |
|
Undo.RecordObject(camera, Styles.inspectorOverlayCameraText); |
|
|
|
bool isChanged = false; |
|
|
|
// Force same render texture |
|
RenderTexture targetTexture = settings.targetTexture.objectReferenceValue as RenderTexture; |
|
if (cam.targetTexture != targetTexture) |
|
{ |
|
cam.targetTexture = targetTexture; |
|
isChanged = true; |
|
} |
|
|
|
// Force same hdr |
|
bool allowHDR = settings.HDR.boolValue; |
|
if (cam.allowHDR != allowHDR) |
|
{ |
|
cam.allowHDR = allowHDR; |
|
isChanged = true; |
|
} |
|
|
|
// Force same mssa |
|
bool allowMSSA = settings.allowMSAA.boolValue; |
|
if (cam.allowMSAA != allowMSSA) |
|
{ |
|
cam.allowMSAA = allowMSSA; |
|
isChanged = true; |
|
} |
|
|
|
// Force same viewport rect |
|
Rect rect = settings.normalizedViewPortRect.rectValue; |
|
if (cam.rect != rect) |
|
{ |
|
cam.rect = settings.normalizedViewPortRect.rectValue; |
|
isChanged = true; |
|
} |
|
|
|
// Force same dynamic resolution |
|
bool allowDynamicResolution = settings.allowDynamicResolution.boolValue; |
|
if (serializedCamera.allowDynamicResolution.boolValue != allowDynamicResolution) |
|
{ |
|
cam.allowDynamicResolution = allowDynamicResolution; |
|
isChanged = true; |
|
} |
|
|
|
// Force same target display |
|
int targetDisplay = settings.targetDisplay.intValue; |
|
if (cam.targetDisplay != targetDisplay) |
|
{ |
|
cam.targetDisplay = targetDisplay; |
|
isChanged = true; |
|
} |
|
|
|
#if ENABLE_VR && ENABLE_XR_MODULE |
|
// Force same target display |
|
int selectedValue = !m_SerializedCamera.allowXRRendering.boolValue ? 0 : 1; |
|
int overlayCameraSelectedValue = !serializedCamera.allowXRRendering.boolValue ? 0 : 1; |
|
if (overlayCameraSelectedValue != selectedValue) |
|
{ |
|
serializedCamera.allowXRRendering.boolValue = selectedValue == 1; |
|
isChanged = true; |
|
} |
|
#endif |
|
|
|
if (isChanged) |
|
{ |
|
EditorUtility.SetDirty(cam); |
|
serializedCamera.Apply(); |
|
} |
|
} |
|
|
|
private bool IsStackCameraOutputDirty(Camera cam, UniversalRenderPipelineSerializedCamera serializedCamera) |
|
{ |
|
serializedCamera.Update(); |
|
|
|
// Force same render texture |
|
RenderTexture targetTexture = settings.targetTexture.objectReferenceValue as RenderTexture; |
|
if (cam.targetTexture != targetTexture) |
|
return true; |
|
|
|
// Force same hdr |
|
bool allowHDR = settings.HDR.boolValue; |
|
if (cam.allowHDR != allowHDR) |
|
return true; |
|
|
|
// Force same mssa |
|
bool allowMSSA = settings.allowMSAA.boolValue; |
|
if (cam.allowMSAA != allowMSSA) |
|
return true; |
|
|
|
// Force same viewport rect |
|
Rect rect = settings.normalizedViewPortRect.rectValue; |
|
if (cam.rect != rect) |
|
return true; |
|
|
|
// Force same dynamic resolution |
|
bool allowDynamicResolution = settings.allowDynamicResolution.boolValue; |
|
if (serializedCamera.allowDynamicResolution.boolValue != allowDynamicResolution) |
|
return true; |
|
|
|
// Force same target display |
|
int targetDisplay = settings.targetDisplay.intValue; |
|
if (cam.targetDisplay != targetDisplay) |
|
return true; |
|
|
|
#if ENABLE_VR && ENABLE_XR_MODULE |
|
// Force same target display |
|
int selectedValue = !m_SerializedCamera.allowXRRendering.boolValue ? 0 : 1; |
|
int overlayCameraSelectedValue = !serializedCamera.allowXRRendering.boolValue ? 0 : 1; |
|
if (overlayCameraSelectedValue != selectedValue) |
|
return true; |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
internal void DrawStackSettings() |
|
{ |
|
if (m_SerializedCamera.cameras.hasMultipleDifferentValues) |
|
{ |
|
EditorGUILayout.HelpBox("Cannot multi edit stack of multiple cameras.", MessageType.Info); |
|
EditorGUILayout.EndFoldoutHeaderGroup(); |
|
return; |
|
} |
|
|
|
bool cameraStackingAvailable = m_SerializedCamera |
|
.camerasAdditionalData |
|
.All(c => c.scriptableRenderer?.SupportsCameraStackingType(CameraRenderType.Base) ?? false); |
|
|
|
if (!cameraStackingAvailable) |
|
{ |
|
EditorGUILayout.HelpBox("The renderer used by this camera doesn't support camera stacking. Only Base camera will render.", MessageType.Warning); |
|
return; |
|
} |
|
|
|
EditorGUILayout.Space(); |
|
|
|
m_LayerList.DoLayoutList(); |
|
m_SerializedCamera.Apply(); |
|
|
|
EditorGUI.indentLevel--; |
|
|
|
bool oldRichTextSupport = EditorStyles.helpBox.richText; |
|
EditorStyles.helpBox.richText = true; |
|
|
|
if (selectedCameraInStack != null) |
|
{ |
|
if (m_IncompatibleCameras.Any()) |
|
{ |
|
if (m_IncompatibleCameras.Contains(selectedCameraInStack)) |
|
{ |
|
var message = "This camera does not use the same type of renderer as the Base camera."; |
|
EditorGUILayout.HelpBox(message, MessageType.Error); |
|
} |
|
} |
|
|
|
if (m_NotSupportedOverlayCameras.Any()) |
|
{ |
|
if (m_NotSupportedOverlayCameras.Contains(selectedCameraInStack)) |
|
{ |
|
var message = "This camera uses a renderer which does not support Overlays in it's current state."; |
|
EditorGUILayout.HelpBox(message, MessageType.Error); |
|
} |
|
} |
|
|
|
if (m_TypeErrorCameras.Any()) |
|
{ |
|
if (m_TypeErrorCameras.Contains(selectedCameraInStack)) |
|
{ |
|
var message = "The type of this Camera must be Overlay render type."; |
|
CoreEditorUtils.DrawFixMeBox(message, MessageType.Error, UpdateStackCameraToOverlay); |
|
} |
|
} |
|
|
|
if (m_OutputWarningCameras.Any()) |
|
{ |
|
var camIndex = m_OutputWarningCameras.FindIndex(c => c.Item1 == selectedCameraInStack); |
|
if (camIndex != -1) |
|
{ |
|
var message = "The output properties of this Camera do not match the output properties."; |
|
if ((CameraRenderType)m_OutputWarningCameras[camIndex].Item2.cameraType.intValue == CameraRenderType.Base) |
|
{ |
|
EditorGUILayout.HelpBox(message, MessageType.Warning); |
|
} |
|
else |
|
{ |
|
CoreEditorUtils.DrawFixMeBox(message, MessageType.Warning, |
|
() => UpdateStackCameraOutput(m_OutputWarningCameras[camIndex].Item1, m_OutputWarningCameras[camIndex].Item2)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
EditorStyles.helpBox.richText = oldRichTextSupport; |
|
EditorGUI.indentLevel++; |
|
|
|
EditorGUILayout.Space(); |
|
} |
|
} |
|
}
|
|
|