using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine.Serialization; using UnityEngine.Assertions; namespace UnityEngine.Rendering.Universal { /// /// Holds information about whether to override certain camera rendering options from the render pipeline asset. /// When set to Off option will be disabled regardless of what is set on the pipeline asset. /// When set to On option will be enabled regardless of what is set on the pipeline asset. /// When set to UsePipelineSetting value set in the . /// public enum CameraOverrideOption { Off, On, UsePipelineSettings, } //[Obsolete("Renderer override is no longer used, renderers are referenced by index on the pipeline asset.")] public enum RendererOverrideOption { Custom, UsePipelineSettings, } /// /// Holds information about the post-processing anti-aliasing mode. /// When set to None no post-processing anti-aliasing pass will be performed. /// When set to Fast a fast approximated anti-aliasing pass will render when resolving the camera to screen. /// When set to SubpixelMorphologicalAntiAliasing SMAA pass will render when resolving the camera to screen. You can choose the SMAA quality by setting /// public enum AntialiasingMode { [InspectorName("No Anti-aliasing")] None, [InspectorName("Fast Approximate Anti-aliasing (FXAA)")] FastApproximateAntialiasing, [InspectorName("Subpixel Morphological Anti-aliasing (SMAA)")] SubpixelMorphologicalAntiAliasing, //TemporalAntialiasing } /// /// Holds information about the render type of a camera. Options are Base or Overlay. /// Base rendering type allows the camera to render to either the screen or to a texture. /// Overlay rendering type allows the camera to render on top of a previous camera output, thus compositing camera results. /// public enum CameraRenderType { Base, Overlay, } /// /// Controls SMAA anti-aliasing quality. /// public enum AntialiasingQuality { Low, Medium, High } /// /// Contains extension methods for Camera class. /// public static class CameraExtensions { /// /// Universal Render Pipeline exposes additional rendering data in a separate component. /// This method returns the additional data component for the given camera or create one if it doesn't exist yet. /// /// /// The UniversalAdditionalCameraData for this camera. /// public static UniversalAdditionalCameraData GetUniversalAdditionalCameraData(this Camera camera) { var gameObject = camera.gameObject; bool componentExists = gameObject.TryGetComponent(out var cameraData); if (!componentExists) cameraData = gameObject.AddComponent(); return cameraData; } /// /// Returns the VolumeFrameworkUpdateMode set on the camera. /// /// /// public static VolumeFrameworkUpdateMode GetVolumeFrameworkUpdateMode(this Camera camera) { UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData(); return cameraData.volumeFrameworkUpdateMode; } /// /// Sets the VolumeFrameworkUpdateMode for the camera. /// /// /// public static void SetVolumeFrameworkUpdateMode(this Camera camera, VolumeFrameworkUpdateMode mode) { UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData(); if (cameraData.volumeFrameworkUpdateMode == mode) return; bool requiredUpdatePreviously = cameraData.requiresVolumeFrameworkUpdate; cameraData.volumeFrameworkUpdateMode = mode; // We only update the local volume stacks for cameras set to ViaScripting. // Otherwise it will be updated in every frame. // We also check the previous value to make sure we're not updating when // switching between Camera ViaScripting and the URP Asset set to ViaScripting if (requiredUpdatePreviously && !cameraData.requiresVolumeFrameworkUpdate) camera.UpdateVolumeStack(cameraData); } /// /// Updates the volume stack for this camera. /// This function will only update the stack when the camera has VolumeFrameworkUpdateMode set to ViaScripting /// or when it set to UsePipelineSettings and the update mode on the Render Pipeline Asset is set to ViaScripting. /// /// public static void UpdateVolumeStack(this Camera camera) { UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData(); camera.UpdateVolumeStack(cameraData); } /// /// Updates the volume stack for this camera. /// This function will only update the stack when the camera has ViaScripting selected or if /// the camera is set to UsePipelineSettings and the Render Pipeline Asset is set to ViaScripting. /// /// /// public static void UpdateVolumeStack(this Camera camera, UniversalAdditionalCameraData cameraData) { Assert.IsNotNull(cameraData, "cameraData can not be null when updating the volume stack."); // We only update the local volume stacks for cameras set to ViaScripting. // Otherwise it will be updated in the frame. if (cameraData.requiresVolumeFrameworkUpdate) return; // Create stack for camera if (cameraData.volumeStack == null) cameraData.GetOrCreateVolumeStack(); camera.GetVolumeLayerMaskAndTrigger(cameraData, out LayerMask layerMask, out Transform trigger); VolumeManager.instance.Update(cameraData.volumeStack, trigger, layerMask); } /// /// Destroys the volume stack for this camera. /// /// public static void DestroyVolumeStack(this Camera camera) { UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData(); camera.DestroyVolumeStack(cameraData); } /// /// Destroys the volume stack for this camera. /// /// /// public static void DestroyVolumeStack(this Camera camera, UniversalAdditionalCameraData cameraData) { if (cameraData == null || cameraData.volumeStack == null) return; cameraData.volumeStack = null; } /// /// Returns the mask and trigger assigned for volumes on the camera. /// /// /// /// /// internal static void GetVolumeLayerMaskAndTrigger(this Camera camera, UniversalAdditionalCameraData cameraData, out LayerMask layerMask, out Transform trigger) { // Default values when there's no additional camera data available layerMask = 1; // "Default" trigger = camera.transform; if (cameraData != null) { layerMask = cameraData.volumeLayerMask; trigger = (cameraData.volumeTrigger != null) ? cameraData.volumeTrigger : trigger; } else if (camera.cameraType == CameraType.SceneView) { // Try to mirror the MainCamera volume layer mask for the scene view - do not mirror the target var mainCamera = Camera.main; UniversalAdditionalCameraData mainAdditionalCameraData = null; if (mainCamera != null && mainCamera.TryGetComponent(out mainAdditionalCameraData)) { layerMask = mainAdditionalCameraData.volumeLayerMask; } trigger = (mainAdditionalCameraData != null && mainAdditionalCameraData.volumeTrigger != null) ? mainAdditionalCameraData.volumeTrigger : trigger; } } } static class CameraTypeUtility { static string[] s_CameraTypeNames = Enum.GetNames(typeof(CameraRenderType)).ToArray(); public static string GetName(this CameraRenderType type) { int typeInt = (int)type; if (typeInt < 0 || typeInt >= s_CameraTypeNames.Length) typeInt = (int)CameraRenderType.Base; return s_CameraTypeNames[typeInt]; } } [DisallowMultipleComponent] [RequireComponent(typeof(Camera))] [ImageEffectAllowedInSceneView] [URPHelpURL("universal-additional-camera-data")] public class UniversalAdditionalCameraData : MonoBehaviour, ISerializationCallbackReceiver, IAdditionalData { const string k_GizmoPath = "Packages/com.unity.render-pipelines.universal/Editor/Gizmos/"; const string k_BaseCameraGizmoPath = k_GizmoPath + "Camera_Base.png"; const string k_OverlayCameraGizmoPath = k_GizmoPath + "Camera_Base.png"; const string k_PostProcessingGizmoPath = k_GizmoPath + "Camera_PostProcessing.png"; [FormerlySerializedAs("renderShadows"), SerializeField] bool m_RenderShadows = true; [SerializeField] CameraOverrideOption m_RequiresDepthTextureOption = CameraOverrideOption.UsePipelineSettings; [SerializeField] CameraOverrideOption m_RequiresOpaqueTextureOption = CameraOverrideOption.UsePipelineSettings; [SerializeField] CameraRenderType m_CameraType = CameraRenderType.Base; [SerializeField] List m_Cameras = new List(); [SerializeField] int m_RendererIndex = -1; [SerializeField] LayerMask m_VolumeLayerMask = 1; // "Default" [SerializeField] Transform m_VolumeTrigger = null; [SerializeField] VolumeFrameworkUpdateMode m_VolumeFrameworkUpdateModeOption = VolumeFrameworkUpdateMode.UsePipelineSettings; [SerializeField] bool m_RenderPostProcessing = false; [SerializeField] AntialiasingMode m_Antialiasing = AntialiasingMode.None; [SerializeField] AntialiasingQuality m_AntialiasingQuality = AntialiasingQuality.High; [SerializeField] bool m_StopNaN = false; [SerializeField] bool m_Dithering = false; [SerializeField] bool m_ClearDepth = true; [SerializeField] bool m_AllowXRRendering = true; [NonSerialized] Camera m_Camera; // Deprecated: [FormerlySerializedAs("requiresDepthTexture"), SerializeField] bool m_RequiresDepthTexture = false; [FormerlySerializedAs("requiresColorTexture"), SerializeField] bool m_RequiresColorTexture = false; [HideInInspector] [SerializeField] float m_Version = 2; // These persist over multiple frames [NonSerialized] MotionVectorsPersistentData m_MotionVectorsPersistentData = new MotionVectorsPersistentData(); public float version => m_Version; static UniversalAdditionalCameraData s_DefaultAdditionalCameraData = null; internal static UniversalAdditionalCameraData defaultAdditionalCameraData { get { if (s_DefaultAdditionalCameraData == null) s_DefaultAdditionalCameraData = new UniversalAdditionalCameraData(); return s_DefaultAdditionalCameraData; } } #if UNITY_EDITOR internal new Camera camera #else internal Camera camera #endif { get { if (!m_Camera) { gameObject.TryGetComponent(out m_Camera); } return m_Camera; } } /// /// Controls if this camera should render shadows. /// public bool renderShadows { get => m_RenderShadows; set => m_RenderShadows = value; } /// /// Controls if a camera should render depth. /// The depth is available to be bound in shaders as _CameraDepthTexture. /// /// public CameraOverrideOption requiresDepthOption { get => m_RequiresDepthTextureOption; set => m_RequiresDepthTextureOption = value; } /// /// Controls if a camera should copy the color contents of a camera after rendering opaques. /// The color texture is available to be bound in shaders as _CameraOpaqueTexture. /// public CameraOverrideOption requiresColorOption { get => m_RequiresOpaqueTextureOption; set => m_RequiresOpaqueTextureOption = value; } /// /// Returns the camera renderType. /// . /// public CameraRenderType renderType { get => m_CameraType; set => m_CameraType = value; } /// /// Returns the camera stack. Only valid for Base cameras. /// Will return null if it is not a Base camera. /// . /// public List cameraStack { get { if (renderType != CameraRenderType.Base) { var camera = gameObject.GetComponent(); Debug.LogWarning(string.Format("{0}: This camera is of {1} type. Only Base cameras can have a camera stack.", camera.name, renderType)); return null; } if (!scriptableRenderer.SupportsCameraStackingType(CameraRenderType.Base)) { var camera = gameObject.GetComponent(); Debug.LogWarning(string.Format("{0}: This camera has a ScriptableRenderer that doesn't support camera stacking. Camera stack is null.", camera.name)); return null; } return m_Cameras; } } internal void UpdateCameraStack() { #if UNITY_EDITOR Undo.RecordObject(this, "Update camera stack"); #endif int prev = m_Cameras.Count; m_Cameras.RemoveAll(cam => cam == null); int curr = m_Cameras.Count; int removedCamsCount = prev - curr; if (removedCamsCount != 0) { Debug.LogWarning(name + ": " + removedCamsCount + " camera overlay" + (removedCamsCount > 1 ? "s" : "") + " no longer exists and will be removed from the camera stack."); } } /// /// If true, this camera will clear depth value before rendering. Only valid for Overlay cameras. /// public bool clearDepth { get => m_ClearDepth; } /// /// Returns true if this camera needs to render depth information in a texture. /// If enabled, depth texture is available to be bound and read from shaders as _CameraDepthTexture after rendering skybox. /// public bool requiresDepthTexture { get { if (m_RequiresDepthTextureOption == CameraOverrideOption.UsePipelineSettings) { return UniversalRenderPipeline.asset.supportsCameraDepthTexture; } else { return m_RequiresDepthTextureOption == CameraOverrideOption.On; } } set { m_RequiresDepthTextureOption = (value) ? CameraOverrideOption.On : CameraOverrideOption.Off; } } /// /// Returns true if this camera requires to color information in a texture. /// If enabled, color texture is available to be bound and read from shaders as _CameraOpaqueTexture after rendering skybox. /// public bool requiresColorTexture { get { if (m_RequiresOpaqueTextureOption == CameraOverrideOption.UsePipelineSettings) { return UniversalRenderPipeline.asset.supportsCameraOpaqueTexture; } else { return m_RequiresOpaqueTextureOption == CameraOverrideOption.On; } } set { m_RequiresOpaqueTextureOption = (value) ? CameraOverrideOption.On : CameraOverrideOption.Off; } } /// /// Returns the that is used to render this camera. /// public ScriptableRenderer scriptableRenderer { get { if (UniversalRenderPipeline.asset is null) return null; if (!UniversalRenderPipeline.asset.ValidateRendererData(m_RendererIndex)) { int defaultIndex = UniversalRenderPipeline.asset.m_DefaultRendererIndex; Debug.LogWarning( $"Renderer at index {m_RendererIndex.ToString()} is missing for camera {camera.name}, falling back to Default Renderer. {UniversalRenderPipeline.asset.m_RendererDataList[defaultIndex].name}", UniversalRenderPipeline.asset); return UniversalRenderPipeline.asset.GetRenderer(defaultIndex); } return UniversalRenderPipeline.asset.GetRenderer(m_RendererIndex); } } /// /// Use this to set this Camera's current to one listed on the Render Pipeline Asset. Takes an index that maps to the list on the Render Pipeline Asset. /// /// The index that maps to the RendererData list on the currently assigned Render Pipeline Asset public void SetRenderer(int index) { m_RendererIndex = index; } /// /// Returns the selected scene-layers affecting this camera. /// public LayerMask volumeLayerMask { get => m_VolumeLayerMask; set => m_VolumeLayerMask = value; } /// /// Returns the Transform that acts as a trigger for Volume blending. /// public Transform volumeTrigger { get => m_VolumeTrigger; set => m_VolumeTrigger = value; } /// /// Returns the selected mode for Volume Frame Updates. /// internal VolumeFrameworkUpdateMode volumeFrameworkUpdateMode { get => m_VolumeFrameworkUpdateModeOption; set => m_VolumeFrameworkUpdateModeOption = value; } /// /// Returns true if this camera requires the volume framework to be updated every frame. /// public bool requiresVolumeFrameworkUpdate { get { if (m_VolumeFrameworkUpdateModeOption == VolumeFrameworkUpdateMode.UsePipelineSettings) { return UniversalRenderPipeline.asset.volumeFrameworkUpdateMode != VolumeFrameworkUpdateMode.ViaScripting; } return m_VolumeFrameworkUpdateModeOption == VolumeFrameworkUpdateMode.EveryFrame; } } /// /// Container for volume stacks in order to reuse stacks and avoid /// creating new ones every time a new camera is instantiated. /// private static List s_CachedVolumeStacks; /// /// Returns the current volume stack used by this camera. /// VolumeStack m_VolumeStack = null; public VolumeStack volumeStack { get => m_VolumeStack; set { // If the volume stack is being removed, // add it back to the list so it can be reused later if (value == null && m_VolumeStack != null) { if (s_CachedVolumeStacks == null) s_CachedVolumeStacks = new List(4); m_VolumeStack.Dispose(); s_CachedVolumeStacks.Add(m_VolumeStack); } m_VolumeStack = value; } } /// /// Tries to retrieve a volume stack from the container /// and creates a new one if that fails. /// internal void GetOrCreateVolumeStack() { // Try first to reuse a volume stack if (s_CachedVolumeStacks != null && s_CachedVolumeStacks.Count > 0) { int index = s_CachedVolumeStacks.Count - 1; volumeStack = s_CachedVolumeStacks[index]; s_CachedVolumeStacks.RemoveAt(index); } // Create a new stack if was not possible to reuse an old one if (volumeStack == null) volumeStack = VolumeManager.instance.CreateStack(); } /// /// Returns true if this camera should render post-processing. /// public bool renderPostProcessing { get => m_RenderPostProcessing; set => m_RenderPostProcessing = value; } /// /// Returns the current anti-aliasing mode used by this camera. /// . /// public AntialiasingMode antialiasing { get => m_Antialiasing; set => m_Antialiasing = value; } /// /// Returns the current anti-aliasing quality used by this camera. /// . /// public AntialiasingQuality antialiasingQuality { get => m_AntialiasingQuality; set => m_AntialiasingQuality = value; } /// /// Motion data that persists over a frame. /// internal MotionVectorsPersistentData motionVectorsPersistentData => m_MotionVectorsPersistentData; /// /// Returns true if this camera should automatically replace NaN/Inf in shaders by a black pixel to avoid breaking some effects. /// public bool stopNaN { get => m_StopNaN; set => m_StopNaN = value; } /// /// Returns true if this camera applies 8-bit dithering to the final render to reduce color banding /// public bool dithering { get => m_Dithering; set => m_Dithering = value; } /// /// Returns true if this camera allows render in XR. /// public bool allowXRRendering { get => m_AllowXRRendering; set => m_AllowXRRendering = value; } public void OnBeforeSerialize() { } public void OnAfterDeserialize() { if (version <= 1) { m_RequiresDepthTextureOption = (m_RequiresDepthTexture) ? CameraOverrideOption.On : CameraOverrideOption.Off; m_RequiresOpaqueTextureOption = (m_RequiresColorTexture) ? CameraOverrideOption.On : CameraOverrideOption.Off; } } public void OnDrawGizmos() { string gizmoName = ""; Color tint = Color.white; if (m_CameraType == CameraRenderType.Base) { gizmoName = k_BaseCameraGizmoPath; } else if (m_CameraType == CameraRenderType.Overlay) { gizmoName = k_OverlayCameraGizmoPath; } #if UNITY_2019_2_OR_NEWER #if UNITY_EDITOR if (Selection.activeObject == gameObject) { // Get the preferences selection color tint = SceneView.selectedOutlineColor; } #endif if (!string.IsNullOrEmpty(gizmoName)) { Gizmos.DrawIcon(transform.position, gizmoName, true, tint); } if (renderPostProcessing) { Gizmos.DrawIcon(transform.position, k_PostProcessingGizmoPath, true, tint); } #else if (renderPostProcessing) { Gizmos.DrawIcon(transform.position, k_PostProcessingGizmoPath); } Gizmos.DrawIcon(transform.position, gizmoName); #endif } /// public void OnDestroy() { m_Camera.DestroyVolumeStack(this); } } }