using System; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule.Util; using static UnityEngine.Rendering.RenderGraphModule.Util.RenderGraphUtils; namespace UnityEngine.Rendering.Universal { /// /// This renderer feature lets you create single-pass full screen post processing effects without needing to write code. /// [URPHelpURL("urp/renderer-features/renderer-feature-full-screen-pass")] public partial class FullScreenPassRendererFeature : ScriptableRendererFeature { /// /// An injection point for the full screen pass. This is similar to the RenderPassEvent enum but limited to only supported events. /// public enum InjectionPoint { /// /// Inject a full screen pass before transparents are rendered. /// BeforeRenderingTransparents = RenderPassEvent.BeforeRenderingTransparents, /// /// Inject a full screen pass before post processing is rendered. /// BeforeRenderingPostProcessing = RenderPassEvent.BeforeRenderingPostProcessing, /// /// Inject a full screen pass after post processing is rendered. /// AfterRenderingPostProcessing = RenderPassEvent.AfterRenderingPostProcessing } /// /// Specifies at which injection point the pass will be rendered. /// public InjectionPoint injectionPoint = InjectionPoint.AfterRenderingPostProcessing; /// /// Specifies whether the assigned material will need to use the current screen contents as an input texture. /// Disable this to optimize away an extra color copy pass when you know that the assigned material will only need /// to write on top of or hardware blend with the contents of the active color target. /// public bool fetchColorBuffer = true; /// /// A mask of URP textures that the assigned material will need access to. Requesting unused requirements can degrade /// performance unnecessarily as URP might need to run additional rendering passes to generate them. /// public ScriptableRenderPassInput requirements = ScriptableRenderPassInput.None; /// /// The material used to render the full screen pass (typically based on the Fullscreen Shader Graph target). /// public Material passMaterial; /// /// The shader pass index that should be used when rendering the assigned material. /// public int passIndex = 0; /// /// Specifies if the active camera's depth-stencil buffer should be bound when rendering the full screen pass. /// Disabling this will ensure that the material's depth and stencil commands will have no effect (this could also have a slight performance benefit). /// public bool bindDepthStencilAttachment = false; private FullScreenRenderPass m_FullScreenPass; /// public override void Create() { m_FullScreenPass = new FullScreenRenderPass(name); } internal override bool RequireRenderingLayers(bool isDeferred, bool needsGBufferAccurateNormals, out RenderingLayerUtils.Event atEvent, out RenderingLayerUtils.MaskSize maskSize) { atEvent = RenderingLayerUtils.Event.Opaque; maskSize = RenderingLayerUtils.MaskSize.Bits8; return false; } /// /// Returns true if the combination of settings is compatible with Tile-Only Mode on the Universal Renderer. /// internal bool IsCompatibleWithTileOnlyMode() { // These checks must match the Tile-Only Mode validation in FullScreenPassRendererFeatureEditor. if (fetchColorBuffer) return false; return RenderingUtils.IsCompatibleWithTileOnlyMode(requirements, (RenderPassEvent)injectionPoint); } /// public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (renderingData.cameraData.cameraType == CameraType.Preview || renderingData.cameraData.cameraType == CameraType.Reflection || UniversalRenderer.IsOffscreenDepthTexture(ref renderingData.cameraData)) return; if (passMaterial == null) return; if (passIndex < 0 || passIndex >= passMaterial.passCount) { Debug.LogWarningFormat("The full screen feature \"{0}\" will not execute - the pass index is out of bounds for the material.", name); return; } if (renderer is UniversalRenderer universalRenderer && universalRenderer.useTileOnlyMode && !IsCompatibleWithTileOnlyMode()) { Debug.LogErrorFormat( "Full Screen Renderer Feature \"{0}\": the current settings are not compatible with Tile-Only Mode. Open the Universal Renderer \"{1}\" in the Inspector for more information.", name, universalRenderer.name); return; } m_FullScreenPass.renderPassEvent = (RenderPassEvent)injectionPoint; m_FullScreenPass.ConfigureInput(requirements); m_FullScreenPass.SetupMembers(passMaterial, passIndex, fetchColorBuffer, bindDepthStencilAttachment); m_FullScreenPass.requiresIntermediateTexture = fetchColorBuffer; renderer.EnqueuePass(m_FullScreenPass); } internal class FullScreenRenderPass : ScriptableRenderPass { private Material m_Material; private int m_PassIndex; private bool m_FetchActiveColor; private bool m_BindDepthStencilAttachment; private readonly MaterialPropertyBlock m_MaterialPropertyBlock = new MaterialPropertyBlock(); public FullScreenRenderPass(string passName) { profilingSampler = new ProfilingSampler(passName); } public void SetupMembers(Material material, int passIndex, bool fetchActiveColor, bool bindDepthStencilAttachment) { m_Material = material; m_PassIndex = passIndex; m_FetchActiveColor = fetchActiveColor; m_BindDepthStencilAttachment = bindDepthStencilAttachment; } private static void ExecuteMainPass(RasterCommandBuffer cmd, MaterialPropertyBlock mbp, RTHandle sourceTexture, Material material, int passIndex, Vector4 blitScaleBias) { mbp.Clear(); if (sourceTexture != null) mbp.SetTexture(ShaderPropertyId.blitTexture, sourceTexture); // We need to set the "_BlitScaleBias" uniform for user materials with shaders relying on core Blit.hlsl to work mbp.SetVector(ShaderPropertyId.blitScaleBias, blitScaleBias); cmd.DrawProcedural(Matrix4x4.identity, material, passIndex, MeshTopology.Triangles, 3, 1, mbp); } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourcesData = frameData.Get(); UniversalCameraData cameraData = frameData.Get(); TextureHandle source, destination; if (m_FetchActiveColor) { // The pass requests the intermediate textures so this should always be valid Debug.Assert(resourcesData.cameraColor.IsValid()); var targetDesc = renderGraph.GetTextureDesc(resourcesData.cameraColor); targetDesc.name = "_CameraColorFullScreenPass"; targetDesc.clearBuffer = false; source = resourcesData.cameraColor; destination = renderGraph.CreateTexture(targetDesc); renderGraph.AddBlitPass(source, destination, Vector2.one, Vector2.zero, passName: "Copy Color Full Screen"); // Swap for next pass; source = destination; } else { source = TextureHandle.nullHandle; } // If resourcesData.isActiveTargetBackBuffer == true, then the backbuffer is alread written to and this could overwrite it. // However, the user might want to blend into the backbuffer so we allow it here. destination = resourcesData.activeColorTexture; AddFullscreenRenderPassInputPass(renderGraph, resourcesData, cameraData, source, destination); } private void AddFullscreenRenderPassInputPass(RenderGraph renderGraph, UniversalResourceData resourcesData, UniversalCameraData cameraData, in TextureHandle source, in TextureHandle destination) { using (var builder = renderGraph.AddRasterRenderPass(passName, out var passData, profilingSampler)) { passData.material = m_Material; passData.materialPropertyBlock = m_MaterialPropertyBlock; passData.passIndex = m_PassIndex; passData.source = source; passData.destination = destination; if (passData.source.IsValid()) builder.UseTexture(passData.source, AccessFlags.Read); bool needsColor = (input & ScriptableRenderPassInput.Color) != ScriptableRenderPassInput.None; bool needsDepth = (input & ScriptableRenderPassInput.Depth) != ScriptableRenderPassInput.None; bool needsMotion = (input & ScriptableRenderPassInput.Motion) != ScriptableRenderPassInput.None; bool needsNormal = (input & ScriptableRenderPassInput.Normal) != ScriptableRenderPassInput.None; if (needsColor && cameraData.renderer.SupportsCameraOpaque()) { Debug.Assert(resourcesData.cameraOpaqueTexture.IsValid()); builder.UseTexture(resourcesData.cameraOpaqueTexture); } if (needsDepth) { Debug.Assert(resourcesData.cameraDepthTexture.IsValid()); builder.UseTexture(resourcesData.cameraDepthTexture); } if (needsMotion) { Debug.Assert(cameraData.renderer.SupportsMotionVectors(), "Current renderer does not support motion vectors."); if (cameraData.renderer.SupportsMotionVectors()) { Debug.Assert(resourcesData.motionVectorColor.IsValid()); builder.UseTexture(resourcesData.motionVectorColor); Debug.Assert(resourcesData.motionVectorDepth.IsValid()); builder.UseTexture(resourcesData.motionVectorDepth); } } if (needsNormal && cameraData.renderer.SupportsCameraNormals()) { Debug.Assert(resourcesData.cameraNormalsTexture.IsValid()); builder.UseTexture(resourcesData.cameraNormalsTexture); } builder.SetRenderAttachment(destination, 0, AccessFlags.Write); if (m_BindDepthStencilAttachment) builder.SetRenderAttachmentDepth(resourcesData.activeDepthTexture, AccessFlags.ReadWrite); builder.SetRenderFunc(static (MainPassData data, RasterGraphContext rgContext) => { Vector4 scaleBias = RenderingUtils.GetFinalBlitScaleBias(rgContext, in data.source, in data.destination); ExecuteMainPass(rgContext.cmd, data.materialPropertyBlock, data.source, data.material, data.passIndex, scaleBias); }); } } private class MainPassData { internal Material material; internal MaterialPropertyBlock materialPropertyBlock; internal int passIndex; internal TextureHandle source; internal TextureHandle destination; } } } }