using System.Collections.Generic; using System; using UnityEngine.Serialization; using UnityEngine.Experimental.Rendering.RenderGraphModule; namespace UnityEngine.Rendering.HighDefinition { /// /// Class that holds data and logic for the pass to be executed /// [System.Serializable] public abstract class CustomPass : IVersionable { /// /// Name of the custom pass /// public string name { get => m_Name; set { m_Name = value; m_ProfilingSampler = new ProfilingSampler(m_Name); } } [SerializeField, FormerlySerializedAsAttribute("name")] string m_Name = "Custom Pass"; internal ProfilingSampler profilingSampler { get { if (m_ProfilingSampler == null) m_ProfilingSampler = new ProfilingSampler(m_Name ?? "Custom Pass"); return m_ProfilingSampler; } } ProfilingSampler m_ProfilingSampler; /// /// Is the custom pass enabled or not /// public bool enabled = true; /// /// Target color buffer (Camera or Custom) /// public TargetBuffer targetColorBuffer; /// /// Target depth buffer (camera or custom) /// public TargetBuffer targetDepthBuffer; /// /// What clear to apply when the color and depth buffer are bound /// public ClearFlag clearFlags; [SerializeField] bool passFoldout; [System.NonSerialized] bool isSetup = false; bool isExecuting = false; RenderTargets currentRenderTarget; CustomPassVolume owner; HDCamera currentHDCamera; MaterialPropertyBlock userMaterialPropertyBlock; // TODO RENDERGRAPH: Remove this when we move things to render graph completely. MaterialPropertyBlock m_MSAAResolveMPB = null; void Awake() { if (m_MSAAResolveMPB == null) m_MSAAResolveMPB = new MaterialPropertyBlock(); } /// /// Mirror of the value in the CustomPassVolume where this custom pass is listed /// /// The blend value that should be applied to the custom pass effect protected float fadeValue => owner.fadeValue; /// /// Get the injection point in HDRP where this pass will be executed /// /// protected CustomPassInjectionPoint injectionPoint => owner.injectionPoint; /// /// True if you want your custom pass to be executed in the scene view. False for game cameras only. /// protected virtual bool executeInSceneView => true; /// /// Used to select the target buffer when executing the custom pass /// public enum TargetBuffer { /// The buffers for the currently rendering Camera. Camera, /// The custom rendering buffers that HDRP allocates. Custom, /// No target buffer. None, } /// /// Render Queue filters for the DrawRenderers custom pass /// public enum RenderQueueType { /// Opaque GameObjects without alpha test only. OpaqueNoAlphaTest, /// Opaque GameObjects with alpha test only. OpaqueAlphaTest, /// All opaque GameObjects. AllOpaque, /// Opaque GameObjects that use the after post process render pass. AfterPostProcessOpaque, /// Transparent GameObjects that use the the pre refraction render pass. PreRefraction, /// Transparent GameObjects that use the default render pass. Transparent, /// Transparent GameObjects that use the low resolution render pass. LowTransparent, /// All Transparent GameObjects. AllTransparent, /// Transparent GameObjects that use the Pre-refraction, Default, or Low resolution render pass. AllTransparentWithLowRes, /// Transparent GameObjects that use after post process render pass. AfterPostProcessTransparent, /// All GameObjects All, } internal struct RenderTargets { public Lazy customColorBuffer; public Lazy customDepthBuffer; // Render graph specific // TODO RENDERGRAPH cleanup the other ones when we only have the render graph path. public TextureHandle colorBufferRG; public TextureHandle nonMSAAColorBufferRG; public TextureHandle depthBufferRG; public TextureHandle normalBufferRG; public TextureHandle motionVectorBufferRG; } enum Version { Initial, } [SerializeField] Version m_Version = MigrationDescription.LastVersion(); Version IVersionable.version { get => m_Version; set => m_Version = value; } internal bool WillBeExecuted(HDCamera hdCamera) { if (!enabled) return false; if (hdCamera.camera.cameraType == CameraType.SceneView && !executeInSceneView) return false; return true; } class ExecutePassData { public CustomPass customPass; public CullingResults cullingResult; public CullingResults cameraCullingResult; public HDCamera hdCamera; } RenderTargets ReadRenderTargets(in RenderGraphBuilder builder, in RenderTargets targets) { RenderTargets output = new RenderTargets(); // Copy over builtin textures. output.customColorBuffer = targets.customColorBuffer; output.customDepthBuffer = targets.customDepthBuffer; // TODO RENDERGRAPH // For now we assume that all "outside" textures are both read and written. // We can change that once we properly integrate render graph into custom passes. // Problem with that is that it will extend the lifetime of any of those textures to the last custom pass that is executed... // Also, we test validity of all handles because depending on where the custom pass is executed, they may not always be. if (targets.colorBufferRG.IsValid()) output.colorBufferRG = builder.ReadWriteTexture(targets.colorBufferRG); if (targets.nonMSAAColorBufferRG.IsValid()) output.nonMSAAColorBufferRG = builder.ReadWriteTexture(targets.nonMSAAColorBufferRG); if (targets.depthBufferRG.IsValid()) output.depthBufferRG = builder.ReadWriteTexture(targets.depthBufferRG); if (targets.normalBufferRG.IsValid()) output.normalBufferRG = builder.ReadWriteTexture(targets.normalBufferRG); if (targets.motionVectorBufferRG.IsValid()) output.motionVectorBufferRG = builder.ReadTexture(targets.motionVectorBufferRG); return output; } internal void ExecuteInternal(RenderGraph renderGraph, HDCamera hdCamera, CullingResults cullingResult, CullingResults cameraCullingResult, in RenderTargets targets, CustomPassVolume owner) { this.owner = owner; this.currentRenderTarget = targets; this.currentHDCamera = hdCamera; using (var builder = renderGraph.AddRenderPass(name, out ExecutePassData passData, profilingSampler)) { passData.customPass = this; passData.cullingResult = cullingResult; passData.cameraCullingResult = cameraCullingResult; passData.hdCamera = hdCamera; this.currentRenderTarget = ReadRenderTargets(builder, targets); builder.SetRenderFunc( (ExecutePassData data, RenderGraphContext ctx) => { var customPass = data.customPass; ctx.cmd.SetGlobalFloat(HDShaderIDs._CustomPassInjectionPoint, (float)customPass.injectionPoint); if (customPass.currentRenderTarget.colorBufferRG.IsValid() && customPass.injectionPoint == CustomPassInjectionPoint.AfterPostProcess) ctx.cmd.SetGlobalTexture(HDShaderIDs._AfterPostProcessColorBuffer, customPass.currentRenderTarget.colorBufferRG); if (customPass.currentRenderTarget.motionVectorBufferRG.IsValid() && (customPass.injectionPoint == CustomPassInjectionPoint.BeforePostProcess || customPass.injectionPoint == CustomPassInjectionPoint.AfterPostProcess)) ctx.cmd.SetGlobalTexture(HDShaderIDs._CameraMotionVectorsTexture, customPass.currentRenderTarget.motionVectorBufferRG); if (customPass.currentRenderTarget.normalBufferRG.IsValid() && customPass.injectionPoint != CustomPassInjectionPoint.AfterPostProcess) ctx.cmd.SetGlobalTexture(HDShaderIDs._NormalBufferTexture, customPass.currentRenderTarget.normalBufferRG); if (!customPass.isSetup) { customPass.Setup(ctx.renderContext, ctx.cmd); customPass.isSetup = true; // TODO RENDERGRAPH: We still need to allocate this otherwise it would be null when switching off render graph (because isSetup stays true). // We can remove the member altogether when we remove the non render graph code path. customPass.userMaterialPropertyBlock = new MaterialPropertyBlock(); } customPass.SetCustomPassTarget(ctx.cmd); var outputColorBuffer = customPass.currentRenderTarget.colorBufferRG; // Create the custom pass context: CustomPassContext customPassCtx = new CustomPassContext( ctx.renderContext, ctx.cmd, data.hdCamera, data.cullingResult, data.cameraCullingResult, outputColorBuffer, customPass.currentRenderTarget.depthBufferRG, customPass.currentRenderTarget.normalBufferRG, customPass.currentRenderTarget.customColorBuffer, customPass.currentRenderTarget.customDepthBuffer, ctx.renderGraphPool.GetTempMaterialPropertyBlock() ); customPass.isExecuting = true; customPass.Execute(customPassCtx); customPass.isExecuting = false; // Set back the camera color buffer if we were using a custom buffer as target if (customPass.targetDepthBuffer != TargetBuffer.Camera) CoreUtils.SetRenderTarget(ctx.cmd, outputColorBuffer); }); } } internal void InternalAggregateCullingParameters(ref ScriptableCullingParameters cullingParameters, HDCamera hdCamera) => AggregateCullingParameters(ref cullingParameters, hdCamera); /// /// Cleans up the custom pass when Unity destroys it unexpectedly. Currently, this happens every time you edit /// the UI because of a bug with the SerializeReference attribute. /// ~CustomPass() { CleanupPassInternal(); } internal void CleanupPassInternal() { if (isSetup) { Cleanup(); isSetup = false; } } bool IsMSAAEnabled(HDCamera hdCamera) { // if MSAA is enabled and the current injection point is before transparent. bool msaa = hdCamera.frameSettings.IsEnabled(FrameSettingsField.MSAA); msaa &= injectionPoint == CustomPassInjectionPoint.BeforePreRefraction || injectionPoint == CustomPassInjectionPoint.BeforeTransparent || injectionPoint == CustomPassInjectionPoint.AfterOpaqueDepthAndNormal; return msaa; } // This function must be only called from the ExecuteInternal method (requires current render target and current RT manager) void SetCustomPassTarget(CommandBuffer cmd) { // In case all the buffer are set to none, we can't bind anything if (targetColorBuffer == TargetBuffer.None && targetDepthBuffer == TargetBuffer.None) return; RTHandle colorBuffer = (targetColorBuffer == TargetBuffer.Custom) ? currentRenderTarget.customColorBuffer.Value : currentRenderTarget.colorBufferRG; RTHandle depthBuffer = (targetDepthBuffer == TargetBuffer.Custom) ? currentRenderTarget.customDepthBuffer.Value : currentRenderTarget.depthBufferRG; if (targetColorBuffer == TargetBuffer.None && targetDepthBuffer != TargetBuffer.None) CoreUtils.SetRenderTarget(cmd, depthBuffer, clearFlags); else if (targetColorBuffer != TargetBuffer.None && targetDepthBuffer == TargetBuffer.None) CoreUtils.SetRenderTarget(cmd, colorBuffer, clearFlags); else CoreUtils.SetRenderTarget(cmd, colorBuffer, depthBuffer, clearFlags); } /// /// Use this method if you want to draw objects that are not visible in the camera. /// For example if you disable a layer in the camera and add it in the culling parameters, then the culling result will contains your layer. /// /// Aggregate the parameters in this property (use |= for masks fields, etc.) /// The camera where the culling is being done protected virtual void AggregateCullingParameters(ref ScriptableCullingParameters cullingParameters, HDCamera hdCamera) {} /// /// Called when your pass needs to be executed by a camera /// /// /// /// /// [Obsolete("This Execute signature is obsolete and will be removed in the future. Please use Execute(CustomPassContext) instead")] protected virtual void Execute(ScriptableRenderContext renderContext, CommandBuffer cmd, HDCamera hdCamera, CullingResults cullingResult) {} /// /// Called when your pass needs to be executed by a camera /// /// The context of the custom pass. Contains command buffer, render context, buffer, etc. // TODO: move this function to abstract when we remove the method above protected virtual void Execute(CustomPassContext ctx) { #pragma warning disable CS0618 // Member is obsolete Execute(ctx.renderContext, ctx.cmd, ctx.hdCamera, ctx.cullingResults); #pragma warning restore CS0618 } /// /// Called before the first execution of the pass occurs. /// Allow you to allocate custom buffers. /// /// The render context /// Current command buffer of the frame protected virtual void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd) {} /// /// Called when HDRP is destroyed. /// Allow you to free custom buffers. /// protected virtual void Cleanup() {} /// /// Bind the camera color buffer as the current render target /// /// /// if true we bind the camera depth buffer in addition to the color /// [Obsolete("Use directly CoreUtils.SetRenderTarget with the render target of your choice.")] protected void SetCameraRenderTarget(CommandBuffer cmd, bool bindDepth = true, ClearFlag clearFlags = ClearFlag.None) { if (!isExecuting) throw new Exception("SetCameraRenderTarget can only be called inside the CustomPass.Execute function"); RTHandle colorBuffer = currentRenderTarget.colorBufferRG; RTHandle depthBuffer = currentRenderTarget.depthBufferRG; if (bindDepth) CoreUtils.SetRenderTarget(cmd, colorBuffer, depthBuffer, clearFlags); else CoreUtils.SetRenderTarget(cmd, colorBuffer, clearFlags); } /// /// Bind the custom color buffer as the current render target /// /// /// if true we bind the custom depth buffer in addition to the color /// [Obsolete("Use directly CoreUtils.SetRenderTarget with the render target of your choice.")] protected void SetCustomRenderTarget(CommandBuffer cmd, bool bindDepth = true, ClearFlag clearFlags = ClearFlag.None) { if (!isExecuting) throw new Exception("SetCameraRenderTarget can only be called inside the CustomPass.Execute function"); if (bindDepth) CoreUtils.SetRenderTarget(cmd, currentRenderTarget.customColorBuffer.Value, currentRenderTarget.customDepthBuffer.Value, clearFlags); else CoreUtils.SetRenderTarget(cmd, currentRenderTarget.customColorBuffer.Value, clearFlags); } /// /// Bind the render targets according to the parameters of the UI (targetColorBuffer, targetDepthBuffer and clearFlags) /// /// protected void SetRenderTargetAuto(CommandBuffer cmd) => SetCustomPassTarget(cmd); /// /// Resolve the camera color buffer only if the MSAA is enabled and the pass is executed in before transparent. /// /// /// protected void ResolveMSAAColorBuffer(CommandBuffer cmd, HDCamera hdCamera) { if (!isExecuting) throw new Exception("ResolveMSAAColorBuffer can only be called inside the CustomPass.Execute function"); // TODO RENDERGRAPH // See how to implement this correctly... // When running with render graph, the design was to have both msaa/non-msaa textures at the same time, which makes a lot of the code simpler. // This pattern here breaks this. // Also this should be reimplemented as a render graph pass completely instead of being nested like this (and reuse existing code). if (IsMSAAEnabled(hdCamera)) { CoreUtils.SetRenderTarget(cmd, currentRenderTarget.nonMSAAColorBufferRG); m_MSAAResolveMPB.SetTexture(HDShaderIDs._ColorTextureMS, currentRenderTarget.colorBufferRG); cmd.DrawProcedural(Matrix4x4.identity, HDRenderPipeline.currentPipeline.GetMSAAColorResolveMaterial(), HDRenderPipeline.SampleCountToPassIndex(hdCamera.msaaSamples), MeshTopology.Triangles, 3, 1, m_MSAAResolveMPB); } } /// /// Resolve the camera color buffer only if the MSAA is enabled and the pass is executed in before transparent. /// /// Custom Pass Context from the Execute() method protected void ResolveMSAAColorBuffer(CustomPassContext ctx) => ResolveMSAAColorBuffer(ctx.cmd, ctx.hdCamera); /// /// Get the current camera buffers (can be MSAA) /// /// outputs the camera color buffer /// outputs the camera depth buffer [Obsolete("GetCameraBuffers is obsolete and will be removed in the future. All camera buffers are now avaliable directly in the CustomPassContext in parameter of the Execute function")] protected void GetCameraBuffers(out RTHandle colorBuffer, out RTHandle depthBuffer) { if (!isExecuting) throw new Exception("GetCameraBuffers can only be called inside the CustomPass.Execute function"); colorBuffer = currentRenderTarget.colorBufferRG; depthBuffer = currentRenderTarget.depthBufferRG; } /// /// Get the current custom buffers /// /// outputs the custom color buffer /// outputs the custom depth buffer [Obsolete("GetCustomBuffers is obsolete and will be removed in the future. All custom buffers are now avaliable directly in the CustomPassContext in parameter of the Execute function")] protected void GetCustomBuffers(out RTHandle colorBuffer, out RTHandle depthBuffer) { if (!isExecuting) throw new Exception("GetCustomBuffers can only be called inside the CustomPass.Execute function"); colorBuffer = currentRenderTarget.customColorBuffer.Value; depthBuffer = currentRenderTarget.customDepthBuffer.Value; } /// /// Get the current normal buffer (can be MSAA) /// /// [Obsolete("GetNormalBuffer is obsolete and will be removed in the future. Normal buffer is now avaliable directly in the CustomPassContext in parameter of the Execute function")] protected RTHandle GetNormalBuffer() { if (!isExecuting) throw new Exception("GetNormalBuffer can only be called inside the CustomPass.Execute function"); return currentRenderTarget.normalBufferRG; } /// /// List all the materials that need to be displayed at the bottom of the component. /// All the materials gathered by this method will be used to create a Material Editor and then can be edited directly on the custom pass. /// /// An enumerable of materials to show in the inspector. These materials can be null, the list is cleaned afterwards public virtual IEnumerable RegisterMaterialForInspector() { yield break; } /// /// Returns the render queue range associated with the custom render queue type /// /// The custom pass render queue type. /// Returns a render queue range compatible with a ScriptableRenderContext.DrawRenderers. protected RenderQueueRange GetRenderQueueRange(CustomPass.RenderQueueType type) => CustomPassUtils.GetRenderQueueRangeFromRenderQueueType(type); /// /// Create a custom pass to execute a fullscreen pass /// /// The material to use for your fullscreen pass. It must have a shader based on the Custom Pass Fullscreen shader or equivalent /// /// /// public static FullScreenCustomPass CreateFullScreenPass(Material fullScreenMaterial, TargetBuffer targetColorBuffer = TargetBuffer.Camera, TargetBuffer targetDepthBuffer = TargetBuffer.Camera) { return new FullScreenCustomPass() { name = "FullScreen Pass", targetColorBuffer = targetColorBuffer, targetDepthBuffer = targetDepthBuffer, fullscreenPassMaterial = fullScreenMaterial, }; } /// /// Create a Custom Pass to render objects /// /// The render queue filter to select which object will be rendered /// The layer mask to select which layer(s) will be rendered /// The replacement material to use when renering objects /// The pass name to use in the override material /// Sorting options when rendering objects /// Clear options when the target buffers are bound. Before executing the pass /// Target Color buffer /// Target Depth buffer. Note: It's also the buffer which will do the Depth Test /// public static DrawRenderersCustomPass CreateDrawRenderersPass(RenderQueueType queue, LayerMask mask, Material overrideMaterial, string overrideMaterialPassName = "Forward", SortingCriteria sorting = SortingCriteria.CommonOpaque, ClearFlag clearFlags = ClearFlag.None, TargetBuffer targetColorBuffer = TargetBuffer.Camera, TargetBuffer targetDepthBuffer = TargetBuffer.Camera) { return new DrawRenderersCustomPass() { name = "DrawRenderers Pass", renderQueueType = queue, layerMask = mask, overrideMaterial = overrideMaterial, overrideMaterialPassName = overrideMaterialPassName, sortingCriteria = sorting, clearFlags = clearFlags, targetColorBuffer = targetColorBuffer, targetDepthBuffer = targetDepthBuffer, }; } } }