using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.Rendering.HighDefinition.Attributes; using UnityEngine.Video; using UnityEngine.Experimental.Rendering; namespace UnityEngine.Rendering.HighDefinition.Compositor { // The main entry point for the compositing operations. Manages the list of layers, output displays, etc. [AddComponentMenu("")] // Hide. [ExecuteAlways] internal class CompositionManager : MonoBehaviour { public enum OutputDisplay { Display1 = 0, Display2, Display3, Display4, Display5, Display6, Display7, Display8 } public enum AlphaChannelSupport { None = 0, Rendering, RenderingAndPostProcessing } [SerializeField] bool m_Enable = true; public bool enableInternal { get { return m_Enable; } set { m_Enable = value; } } [SerializeField] Material m_Material; [SerializeField] OutputDisplay m_OutputDisplay = OutputDisplay.Display1; public List layers => m_InputLayers; [SerializeField] List m_InputLayers = new List(); public AlphaChannelSupport alphaSupport => m_AlphaSupport; internal AlphaChannelSupport m_AlphaSupport = AlphaChannelSupport.RenderingAndPostProcessing; internal float timeSinceLastRepaint; public bool enableOutput { get { if (m_OutputCamera) { return m_OutputCamera.enabled; } return false; } set { if (m_OutputCamera) { // If the state did not change, don't do anything if (m_OutputCamera.enabled == value) return; m_OutputCamera.enabled = value; // Aside from the output compositor camera, we also have to change the cameras of the layers foreach (var layer in m_InputLayers) { if (layer.camera && layer.isUsingACameraClone) { layer.camera.enabled = value; } else { // The target texture was managed by the compositor, reset it so the user can see the camera output if (layer.camera && value == false) layer.camera.targetTexture = null; } } // Toggle the compositor-related custom passes if (value) { RegisterCustomPasses(); } else { UnRegisterCustomPasses(); } } } } public int numLayers => m_InputLayers.Count; public Shader shader { get => m_Shader; set { m_Shader = value; } } [SerializeField] Shader m_Shader; public CompositionProfile profile { get => m_CompositionProfile; set => m_CompositionProfile = value; } [HideInInspector, SerializeField] CompositionProfile m_CompositionProfile; public Camera outputCamera { get => m_OutputCamera; set => m_OutputCamera = value; } [SerializeField] Camera m_OutputCamera; public float aspectRatio { get { if (m_OutputCamera) { return (float)m_OutputCamera.pixelWidth / m_OutputCamera.pixelHeight; } return 1.0f; } } public bool shaderPropertiesAreDirty { set { m_ShaderPropertiesAreDirty = true; } } internal bool m_ShaderPropertiesAreDirty = false; internal Matrix4x4 m_ViewProjMatrix; internal Matrix4x4 m_ViewProjMatrixFlipped; internal GameObject m_CompositorGameObject; internal MaterialPropertyBlock fullscreenProperties; ShaderVariablesGlobal m_ShaderVariablesGlobalCB = new ShaderVariablesGlobal(); int m_RecorderTempRT = Shader.PropertyToID("TempRecorder"); // Built-in Color.black has an alpha of 1, so defien here a fully transparent black static Color s_TransparentBlack = new Color(0, 0, 0, 0); #region Validation public bool ValidateLayerListOrder(int oldIndex, int newIndex) { if (m_InputLayers.Count > 1) { if (m_InputLayers[0].outputTarget == CompositorLayer.OutputTarget.CameraStack) { var tmp = m_InputLayers[newIndex]; m_InputLayers.RemoveAt(newIndex); m_InputLayers.Insert(oldIndex, tmp); return false; } } return true; } public bool RuntimeCheck() { for (int i = 0; i < m_InputLayers.Count; ++i) { if (!m_InputLayers[i].Validate()) { return false; } } return true; } // Validates the rendering pipeline and fixes potential issues bool ValidatePipeline() { var hdPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline; if (hdPipeline != null) { m_AlphaSupport = AlphaChannelSupport.RenderingAndPostProcessing; if (hdPipeline.GetColorBufferFormat() == (GraphicsFormat)RenderPipelineSettings.ColorBufferFormat.R11G11B10) { m_AlphaSupport = AlphaChannelSupport.None; } else if (hdPipeline.GetColorBufferFormat() == (GraphicsFormat)PostProcessBufferFormat.R11G11B10) { m_AlphaSupport = AlphaChannelSupport.Rendering; } RegisterCustomPasses(); return true; } return false; } bool ValidateCompositionShader() { if (m_Shader == null) { return false; } if (m_CompositionProfile == null) { Debug.Log("A composition profile was not found. Set the composition graph from the Compositor window to create one."); return false; } return true; } bool ValidateProfile() { if (m_CompositionProfile) { return true; } else { Debug.LogError("No composition profile was found! Use the compositor tool to create one."); return false; } } bool ValidateMainCompositorCamera() { if (m_OutputCamera == null) { return false; } if (!m_OutputCamera.TryGetComponent(out var cameraData)) { m_OutputCamera.gameObject.AddComponent(typeof(HDAdditionalCameraData)); cameraData = m_OutputCamera.GetComponent(); } // Setup custom rendering (we don't want HDRP to compute anything in this camera) if (cameraData) { cameraData.customRender += CustomRender; } else { Debug.Log("Null additional data in compositor output"); } return true; } bool ValidateAndFixRuntime() { if (m_OutputCamera == null) { return false; } if (m_Shader == null) { m_InputLayers.Clear(); m_CompositionProfile = null; return false; } if (m_CompositionProfile == null) { return false; } if (m_Material == null) { SetupCompositionMaterial(); } var cameraData = m_OutputCamera.GetComponent(); if (cameraData && !cameraData.hasCustomRender) { cameraData.customRender += CustomRender; } return true; } #endregion // This is called when we change camera, to remove the custom draw callback from the old camera before we set the new one public void DropCompositorCamera() { if (m_OutputCamera) { var cameraData = m_OutputCamera.GetComponent(); if (cameraData && cameraData.hasCustomRender) { cameraData.customRender -= CustomRender; } } } public void Init() { if (ValidateCompositionShader() && ValidateProfile() && ValidateMainCompositorCamera()) { UpdateDisplayNumber(); SetupCompositionMaterial(); SetupCompositorLayers(false); SetupGlobalCompositorVolume(); SetupCompositorConstants(); SetupLayerPriorities(); } else { Debug.LogError("The compositor was disabled due to a validation error in the configuration."); enableInternal = false; } } // Start is called before the first frame update void Start() { Init(); } void OnValidate() { if (shader == null) { m_InputLayers.Clear(); m_CompositionProfile = null; } } public void OnEnable() { enableOutput = true; s_CompositorInstance = null; #if UNITY_EDITOR //This is a work-around, to make edit and continue work when editing source code UnityEditor.AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; #endif RenderPipelineManager.beginContextRendering += ResizeCallback; } public void DeleteLayerRTs() { // Delete all layer cameras first, and then the Render Targets (to avoid deleting RT that are still in use) for (int i = m_InputLayers.Count - 1; i >= 0; --i) { m_InputLayers[i].DestroyCameras(); } for (int i = m_InputLayers.Count - 1; i >= 0; --i) { m_InputLayers[i].DestroyRT(); } } public bool IsOutputLayer(int layerID) { if (layerID >= 0 && layerID < m_InputLayers.Count) { if (m_InputLayers[layerID].outputTarget == CompositorLayer.OutputTarget.CameraStack) { return false; } } return true; } public void UpdateDisplayNumber() { if (m_OutputCamera) { m_OutputCamera.targetDisplay = (int)m_OutputDisplay; } } void SetupCompositorLayers(bool allowUndo = true) { for (int i = 0; i < m_InputLayers.Count; ++i) { m_InputLayers[i].Init($"Layer{i}", allowUndo); } SetLayerRenderTargets(); } public void SetNewCompositionShader() { // When we load a new shader, we need to clear the serialized material. m_Material = null; SetupCompositionMaterial(); } public void SetupCompositionMaterial() { // Create the composition material if (m_Shader) { m_Material = new Material(m_Shader); m_CompositionProfile.AddPropertiesFromShaderAndMaterial(this, m_Shader, m_Material); // [case 1265631] The profile asset is auto-generated by the compositor, so do not allow the users to manually edit/reset the values in the asset because it might break things m_CompositionProfile.hideFlags = HideFlags.NotEditable; } else { m_CompositionProfile = null; m_Material = null; } } public void SetupLayerPriorities() { int count = 0; foreach (var layer in m_InputLayers) { // Set camera priority (camera's at the beginning of the list should be rendered first) layer.SetPriotiry(count * 1.0f); count++; } } public void OnAfterAssemblyReload() { // Bug? : After assembly reload, the customRender callback is dropped, so set it again if (m_OutputCamera) { var cameraData = m_OutputCamera.GetComponent(); if (cameraData && !cameraData.hasCustomRender) { cameraData.customRender += CustomRender; } } } public void OnDisable() { enableOutput = false; } static string s_CompositorGlobalVolumeName = "__Internal_Global_Composition_Volume"; // Setup a global volume used for chroma keying, alpha injection etc void SetupGlobalCompositorVolume() { var compositorVolumes = Resources.FindObjectsOfTypeAll(typeof(CustomPassVolume)); // Instead of using one volume per layer/camera, we setup one global volume and we store the data in the camera // This way the compositor has to use only one layer/volume for N cameras (instead of N). m_CompositorGameObject = new GameObject(s_CompositorGlobalVolumeName) { hideFlags = HideFlags.HideAndDontSave }; Volume globalPPVolume = m_CompositorGameObject.AddComponent(); globalPPVolume.gameObject.layer = 31; AlphaInjection injectAlphaNode = globalPPVolume.profile.Add(); ChromaKeying chromaKeyingPass = globalPPVolume.profile.Add(); chromaKeyingPass.activate.Override(true); // Custom pass for "Clear to Texture" CustomPassVolume globalCustomPassVolume = m_CompositorGameObject.AddComponent(); globalCustomPassVolume.injectionPoint = CustomPassInjectionPoint.BeforeRendering; globalCustomPassVolume.AddPassOfType(typeof(CustomClear)); } void SetupCompositorConstants() { m_ViewProjMatrix = Matrix4x4.Scale(new Vector3(2.0f, 2.0f, 0.0f)) * Matrix4x4.Translate(new Vector3(-0.5f, -0.5f, 0.0f)); m_ViewProjMatrixFlipped = Matrix4x4.Scale(new Vector3(2.0f, -2.0f, 0.0f)) * Matrix4x4.Translate(new Vector3(-0.5f, -0.5f, 0.0f)); } public void UpdateLayerSetup() { SetupCompositorLayers(); SetupLayerPriorities(); } static HDRenderPipelineAsset m_CurrentAsset; // LateUpdate is called once per frame void LateUpdate() { // TODO: move all validation calls to onValidate. Before doing it, this needs some extra testing to ensure nothing breaks if (enableOutput == false || ValidatePipeline() == false || ValidateAndFixRuntime() == false || RuntimeCheck() == false) { return; } UpdateDisplayNumber(); #if UNITY_EDITOR if (m_ShaderPropertiesAreDirty) { SetNewCompositionShader(); m_ShaderPropertiesAreDirty = false; SetupCompositorLayers();//< required to allocate RT for the new layers } #endif if (m_CompositionProfile) { foreach (var layer in m_InputLayers) { layer.Update(); } SetLayerRenderTargets(); } } void OnDestroy() { DeleteLayerRTs(); if (m_CompositorGameObject != null) { CoreUtils.Destroy(m_CompositorGameObject); m_CompositorGameObject = null; } var compositorVolumes = Resources.FindObjectsOfTypeAll(typeof(CustomPassVolume)); foreach (CustomPassVolume volume in compositorVolumes) { if (volume.name == "Global Composition Volume" && volume.injectionPoint == CustomPassInjectionPoint.BeforeRendering) { CoreUtils.Destroy(volume); } } // We don't need the custom passes anymore UnRegisterCustomPasses(); // By now the s_CompositorManagedCameras should be empty, but clear it just to be safe CompositorCameraRegistry.GetInstance().CleanUpCameraOrphans(); RenderPipelineManager.beginContextRendering -= ResizeCallback; } public void AddInputFilterAtLayer(CompositionFilter filter, int index) { m_InputLayers[index].AddInputFilter(filter); } int GetBaseLayerForSubLayerAtIndex(int index) { int baseIndex = 0; index = (index > m_InputLayers.Count - 1) ? m_InputLayers.Count - 1 : index; for (int i = index; i >= 0; --i) { if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CompositorLayer) { baseIndex = i; break; } } return baseIndex; } static string GetSubLayerName(int count) { if (count == 0) { return "New SubLayer"; } else { return $"New SubLayer ({count + 1})"; } } public string GetNewSubLayerName(int index, CompositorLayer.LayerType type = CompositorLayer.LayerType.Camera) { // First find the base layer int baseIndex = GetBaseLayerForSubLayerAtIndex(index - 1); // Get a candidate name and check if it already exists int count = 0; string candidateName = GetSubLayerName(count); int i = baseIndex + 1; while (i < m_InputLayers.Count && m_InputLayers[i].outputTarget != CompositorLayer.OutputTarget.CompositorLayer) { if (m_InputLayers[i].name == candidateName) { // If this candidate name exists, get the next one and start again candidateName = GetSubLayerName(++count); i = baseIndex + 1; } else { ++i; } } return candidateName; } public void AddNewLayer(int index, CompositorLayer.LayerType type = CompositorLayer.LayerType.Camera) { var newLayer = CompositorLayer.CreateStackLayer(type, GetNewSubLayerName(index, type)); if (index >= 0 && index < m_InputLayers.Count) { m_InputLayers.Insert(index, newLayer); } else { m_InputLayers.Add(newLayer); } } int GetNumChildrenForLayerAtIndex(int indx) { if (m_InputLayers[indx].outputTarget == CompositorLayer.OutputTarget.CameraStack) { return 0; } int num = 0; for (int i = indx + 1; i < m_InputLayers.Count; ++i) { if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CameraStack) { num++; } else { break; } } return num; } public void RemoveLayerAtIndex(int indx) { Debug.Assert(indx >= 0 && indx < m_InputLayers.Count); int numChildren = GetNumChildrenForLayerAtIndex(indx); for (int i = numChildren; i >= 0; --i) { m_InputLayers[indx + i].Destroy(); m_InputLayers.RemoveAt(indx + i); } } public void SetLayerRenderTargets() { int layerPositionInStack = 0; CompositorLayer lastLayer = null; for (int i = 0; i < m_InputLayers.Count; ++i) { if (m_InputLayers[i].outputTarget != CompositorLayer.OutputTarget.CameraStack) { lastLayer = m_InputLayers[i]; // [case 1265061] If this layer does not have any cameras that will clear/draw the background, set a flag so the compositor will clear it explicitly. m_InputLayers[i].clearsBackGround = (i + 1 < m_InputLayers.Count) ? (m_InputLayers[i + 1].outputTarget == CompositorLayer.OutputTarget.CompositorLayer) : true; } if (m_InputLayers[i].outputTarget == CompositorLayer.OutputTarget.CameraStack && i > 0) { m_InputLayers[i].SetupLayerCamera(lastLayer, layerPositionInStack); // Corner case: If the first layer in a camera stack was disabled, then it should still clear the color buffer if (!m_InputLayers[i].enabled && layerPositionInStack == 0) { m_InputLayers[i].SetupClearColor(); } layerPositionInStack++; } else { layerPositionInStack = 0; } } } public void ReorderChildren(int oldIndex, int newIndex) { if (m_InputLayers[newIndex].outputTarget == CompositorLayer.OutputTarget.CompositorLayer) { if (oldIndex > newIndex) { for (int i = 1; oldIndex + i < m_InputLayers.Count; ++i) { if (m_InputLayers[oldIndex + i].outputTarget == CompositorLayer.OutputTarget.CameraStack) { var tmp = m_InputLayers[oldIndex + i]; m_InputLayers.RemoveAt(oldIndex + i); m_InputLayers.Insert(newIndex + i, tmp); } else { break; } } } else { while (m_InputLayers[oldIndex].outputTarget == CompositorLayer.OutputTarget.CameraStack) { var tmp = m_InputLayers[oldIndex]; m_InputLayers.RemoveAt(oldIndex); m_InputLayers.Insert(newIndex, tmp); } } } } public RenderTexture GetRenderTarget(int indx) { if (indx >= 0 && indx < m_InputLayers.Count) { return m_InputLayers[indx].GetRenderTarget(true, true); } return null; } public void Repaint() { for (int indx = 0; indx < m_InputLayers.Count; indx++) { if (m_InputLayers[indx].camera) m_InputLayers[indx].camera.Render(); } } void ResizeCallback(ScriptableRenderContext cntx, List cameras) { if (m_OutputCamera && enableOutput) { // Detect runtime resolution change foreach (var layer in m_InputLayers) { if (!layer.ValidateRTSize(m_OutputCamera.pixelWidth, m_OutputCamera.pixelHeight)) { for (int i = m_InputLayers.Count - 1; i >= 0; --i) { if (m_InputLayers[i].camera) m_InputLayers[i].camera.targetTexture = null; m_InputLayers[i].DestroyRT(); } SetupCompositorLayers(); // After a resolution change, redraw the internal layer cameras. InternalRender(cntx); break; } } } } void InternalRender(ScriptableRenderContext cntx) { HDRenderPipeline renderPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline; if (enableOutput && renderPipeline != null) { List cameras = new List(1); foreach (var layer in m_InputLayers) { if (layer.camera && layer.camera.enabled) { cameras.Clear(); // Emit geometry manually for this camera (Unity will not do it for us because we call the internal render) ScriptableRenderContext.EmitGeometryForCamera(layer.camera); cameras.Add(layer.camera); #if UNITY_2021_1_OR_NEWER renderPipeline.InternalRender(cntx, cameras); #endif } } } } void CustomRender(ScriptableRenderContext context, HDCamera camera) { if (camera == null || camera.camera == null || m_Material == null || m_Shader == null) { // If something is wrong, don't keep the previous image (clear to black) to avoid confusion var cmdbuff = CommandBufferPool.Get("Compositor Blit"); cmdbuff.ClearRenderTarget(false, true, Color.black); return; } timeSinceLastRepaint = 0; // set shader uniforms m_CompositionProfile.CopyPropertiesToMaterial(m_Material); int layerIndex = 0; foreach (var layer in m_InputLayers) { if (layer.outputTarget != CompositorLayer.OutputTarget.CameraStack) // stacked cameras are not exposed as compositor layers { m_Material.SetTexture(layer.name, layer.GetRenderTarget(), RenderTextureSubElement.Color); } layerIndex++; } // Blit command var cmd = CommandBufferPool.Get("Compositor Blit"); { // fill the camera-related entries in the global constant buffer // (Note: we later patch the position/_ViewProjMatrix values in order to perform a full screen blit with a SG Unlit material) camera.UpdateShaderVariablesGlobalCB(ref m_ShaderVariablesGlobalCB, 0); m_ShaderVariablesGlobalCB._WorldSpaceCameraPos_Internal = new Vector3(0.0f, 0.0f, 0.0f); cmd.SetViewport(new Rect(0, 0, camera.camera.pixelWidth, camera.camera.pixelHeight)); cmd.ClearRenderTarget(true, false, Color.red); foreach (var layer in m_InputLayers) { if (layer.clearsBackGround) { cmd.SetRenderTarget(layer.GetRenderTarget()); cmd.ClearRenderTarget(false, true, s_TransparentBlack); } } } int materialPass = m_Material.FindPass("DrawProcedural"); bool isFullscreen = materialPass != -1; if (!isFullscreen) materialPass = m_Material.FindPass("ForwardOnly"); if (camera.camera.targetTexture) { if (isFullscreen) { CoreUtils.DrawFullScreen(cmd, m_Material, camera.camera.targetTexture, shaderPassId: materialPass); } else { // When rendering to texture (or to camera bridge) we don't need to flip the image. // If this matrix was used for the game view, then the image would appear flipped, hense the name of the variable. m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrixFlipped; ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal); cmd.Blit(null, camera.camera.targetTexture, m_Material, materialPass); } var recorderCaptureActions = CameraCaptureBridge.GetCaptureActions(camera.camera); if (recorderCaptureActions != null) { for (recorderCaptureActions.Reset(); recorderCaptureActions.MoveNext();) { recorderCaptureActions.Current(camera.camera.targetTexture, cmd); } } } else { var recorderCaptureActions = CameraCaptureBridge.GetCaptureActions(camera.camera); if (recorderCaptureActions != null) { var format = m_InputLayers[0].GetRenderTarget().format; cmd.GetTemporaryRT(m_RecorderTempRT, camera.camera.pixelWidth, camera.camera.pixelHeight, 0, FilterMode.Point, format); if (isFullscreen) { CoreUtils.DrawFullScreen(cmd, m_Material, m_RecorderTempRT, shaderPassId: materialPass); } else { m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrixFlipped; ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal); cmd.Blit(null, m_RecorderTempRT, m_Material, materialPass); for (recorderCaptureActions.Reset(); recorderCaptureActions.MoveNext();) { recorderCaptureActions.Current(m_RecorderTempRT, cmd); } } } if (isFullscreen) { if (fullscreenProperties == null) fullscreenProperties = new MaterialPropertyBlock(); fullscreenProperties.SetFloat("_FlipY", 1); CoreUtils.DrawFullScreen(cmd, m_Material, BuiltinRenderTextureType.CameraTarget, fullscreenProperties, shaderPassId: materialPass); } else { // When we render directly to game view, we render the image flipped up-side-down, like other HDRP cameras m_ShaderVariablesGlobalCB._ViewProjMatrix = m_ViewProjMatrix; ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesGlobalCB, HDShaderIDs._ShaderVariablesGlobal); cmd.Blit(null, BuiltinRenderTextureType.CameraTarget, m_Material, materialPass); } } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } /// /// Helper function that indicates if a camera is shared between multiple layers /// /// The input camera /// Returns true if this camera is used to render in more than one layer internal bool IsThisCameraShared(Camera camera) { if (camera == null) { return false; } int count = 0; foreach (var layer in m_InputLayers) { if (layer.outputTarget == CompositorLayer.OutputTarget.CameraStack && camera.Equals(layer.sourceCamera)) { count++; } } // If we found the camera in more than one layer then it is shared between layers return count > 1; } static public Camera GetSceneCamera() { if (Camera.main != null) { return Camera.main; } foreach (var camera in Camera.allCameras) { if (camera != CompositionManager.GetInstance().outputCamera) { return camera; } } return null; } static public Camera CreateCamera(string cameraName) { var newCameraGameObject = new GameObject(cameraName) { hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy | HideFlags.HideAndDontSave }; var newCamera = newCameraGameObject.AddComponent(); newCameraGameObject.AddComponent(); return newCamera; } private static CompositionManager s_CompositorInstance; public static CompositionManager GetInstance() => s_CompositorInstance ??= FindAnyObjectByType(FindObjectsInactive.Include); static public Vector4 GetAlphaScaleAndBiasForCamera(HDCamera hdCamera) { AdditionalCompositorData compositorData = null; hdCamera.camera.TryGetComponent(out compositorData); if (compositorData) { float alphaMin = compositorData.alphaMin; float alphaMax = compositorData.alphaMax; if (alphaMax == alphaMin) alphaMax += 0.0001f; // Mathf.Epsilon is too small and in this case it creates precission issues float alphaScale = 1.0f / (alphaMax - alphaMin); float alphaBias = -alphaMin * alphaScale; return new Vector4(alphaScale, alphaBias, 0.0f, 0.0f); } // No compositor-specific data for this camera/layer, just return the default/neutral scale and bias return new Vector4(1.0f, 0.0f, 0.0f, 0.0f); } /// /// For stacked cameras, returns the color buffer that will be used to draw on top /// /// The input camera /// The color buffer that will be used to draw on top, or null if not a stacked camera static internal Texture GetClearTextureForStackedCamera(HDCamera hdCamera) { if (hdCamera.camera.TryGetComponent(out var compositorData)) { return compositorData.clearColorTexture; } return null; } /// /// For stacked cameras, returns the depth buffer that will be used to draw on top /// /// The input camera /// The depth buffer that will be used to draw on top, or null if not a stacked camera static internal RenderTexture GetClearDepthForStackedCamera(HDCamera hdCamera) { if (hdCamera.camera.TryGetComponent(out var compositorData)) { return compositorData.clearDepthTexture; } return null; } // Register the custom pp passes used by the compositor void RegisterCustomPasses() { if (GraphicsSettings.currentRenderPipeline is not HDRenderPipelineAsset hdrpAsset) { UnRegisterCustomPasses(); m_CurrentAsset = null; } else if (m_CurrentAsset != hdrpAsset) { UnRegisterCustomPasses(); m_CurrentAsset = hdrpAsset; } if (m_CurrentAsset == null) return; // If custom post processes are not registered in the HDRP asset, they are never executed so we have to add them manually m_CurrentAsset.compositorCustomVolumeComponentsList.Add(); m_CurrentAsset.compositorCustomVolumeComponentsList.Add(); } // Unregister the custom pp passes used by the compositor void UnRegisterCustomPasses() { if (m_CurrentAsset != null) { m_CurrentAsset.compositorCustomVolumeComponentsList.Remove(); m_CurrentAsset.compositorCustomVolumeComponentsList.Remove(); } } } }