using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering.RenderGraphModule; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; /// /// FullScreenPass is a renderer feature used to change screen appearance such as post processing effect. This implementation /// lets it's user create an effect with minimal code involvement. /// [URPHelpURL("renderer-features/renderer-feature-full-screen-pass")] public class FullScreenPassRendererFeature : ScriptableRendererFeature { /// /// An injection point for the full screen pass. This is similar to RenderPassEvent enum but limits 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 } /// /// Material the Renderer Feature uses to render the effect. /// public Material passMaterial; /// /// Selection for when the effect is rendered. /// public InjectionPoint injectionPoint = InjectionPoint.AfterRenderingPostProcessing; /// /// One or more requirements for pass. Based on chosen flags certain passes will be added to the pipeline. /// public ScriptableRenderPassInput requirements = ScriptableRenderPassInput.Color; /// /// An index that tells renderer feature which pass to use if passMaterial contains more than one. Default is 0. /// We draw custom pass index entry with the custom dropdown inside FullScreenPassRendererFeatureEditor that sets this value. /// Setting it directly will be overridden by the editor class. /// [HideInInspector] public int passIndex = 0; private FullScreenRenderPass fullScreenPass; private bool requiresColor; private bool injectedBeforeTransparents; /// public override void Create() { fullScreenPass = new FullScreenRenderPass(); fullScreenPass.renderPassEvent = (RenderPassEvent)injectionPoint; // This copy of requirements is used as a parameter to configure input in order to avoid copy color pass ScriptableRenderPassInput modifiedRequirements = requirements; requiresColor = (requirements & ScriptableRenderPassInput.Color) != 0; injectedBeforeTransparents = injectionPoint <= InjectionPoint.BeforeRenderingTransparents; if (requiresColor && !injectedBeforeTransparents) { // Removing Color flag in order to avoid unnecessary CopyColor pass // Does not apply to before rendering transparents, due to how depth and color are being handled until // that injection point. modifiedRequirements ^= ScriptableRenderPassInput.Color; } fullScreenPass.ConfigureInput(modifiedRequirements); } /// public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (passMaterial == null) { Debug.LogWarningFormat("Missing Post Processing effect Material. {0} Fullscreen pass will not execute. Check for missing reference in the assigned renderer.", GetType().Name); return; } fullScreenPass.Setup(passMaterial, passIndex, requiresColor, injectedBeforeTransparents, "FullScreenPassRendererFeature", renderingData); renderer.EnqueuePass(fullScreenPass); } /// protected override void Dispose(bool disposing) { fullScreenPass.Dispose(); } class FullScreenRenderPass : ScriptableRenderPass { private static Material s_PassMaterial; private int m_PassIndex; private bool m_RequiresColor; private bool m_IsBeforeTransparents; private PassData m_PassData; private ProfilingSampler m_ProfilingSampler; private RTHandle m_CopiedColor; private static readonly int m_BlitTextureShaderID = Shader.PropertyToID("_BlitTexture"); public void Setup(Material mat, int index, bool requiresColor, bool isBeforeTransparents, string featureName, in RenderingData renderingData) { s_PassMaterial = mat; m_PassIndex = index; m_RequiresColor = requiresColor; m_IsBeforeTransparents = isBeforeTransparents; m_ProfilingSampler ??= new ProfilingSampler(featureName); var colorCopyDescriptor = renderingData.cameraData.cameraTargetDescriptor; colorCopyDescriptor.depthBufferBits = (int) DepthBits.None; RenderingUtils.ReAllocateIfNeeded(ref m_CopiedColor, colorCopyDescriptor, name: "_FullscreenPassColorCopy"); m_PassData ??= new PassData(); } public void Dispose() { m_CopiedColor?.Release(); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { ref var cameraData = ref renderingData.cameraData; var cmd = renderingData.commandBuffer; // ExecutePass(m_PassData, renderingData.cameraData, renderingData.commandBuffer); if (s_PassMaterial == null) { // should not happen as we check it in feature return; } if (cameraData.isPreviewCamera) { return; } using (new ProfilingScope(cmd, profilingSampler)) { if (m_RequiresColor) { // For some reason BlitCameraTexture(cmd, dest, dest) scenario (as with before transparents effects) blitter fails to correctly blit the data // Sometimes it copies only one effect out of two, sometimes second, sometimes data is invalid (as if sampling failed?). // Adding RTHandle in between solves this issue. var source = m_IsBeforeTransparents ? cameraData.renderer.GetCameraColorBackBuffer(cmd) : cameraData.renderer.cameraColorTargetHandle; Blitter.BlitCameraTexture(cmd, source, m_CopiedColor); s_PassMaterial.SetTexture(m_BlitTextureShaderID, m_CopiedColor); } CoreUtils.SetRenderTarget(cmd, cameraData.renderer.GetCameraColorBackBuffer(cmd)); CoreUtils.DrawFullScreen(cmd, s_PassMaterial); } } public override void RecordRenderGraph(RenderGraph renderGraph, FrameResources frameResources, ref RenderingData renderingData) { UniversalRenderer renderer = (UniversalRenderer) renderingData.cameraData.renderer; var colorCopyDescriptor = renderingData.cameraData.cameraTargetDescriptor; colorCopyDescriptor.depthBufferBits = (int) DepthBits.None; TextureHandle copiedColor = UniversalRenderer.CreateRenderGraphTexture(renderGraph, colorCopyDescriptor, "_FullscreenPassColorCopy", false); if (m_RequiresColor) { using (var builder = renderGraph.AddRasterRenderPass("CustomPostPro_ColorPass", out var passData, m_ProfilingSampler)) { passData.source = builder.UseTexture(renderer.activeColorTexture, IBaseRenderGraphBuilder.AccessFlags.Read); passData.copiedColor = builder.UseTextureFragment(copiedColor, 0, IBaseRenderGraphBuilder.AccessFlags.Write); builder.SetRenderFunc((PassData data, RasterGraphContext rgContext) => { Blitter.BlitTexture(rgContext.cmd, data.source, new Vector4(1, 1, 0, 0), 0.0f, false); }); } } using (var builder = renderGraph.AddRasterRenderPass("CustomPostPro_FullScreenPass", out var passData, m_ProfilingSampler)) { passData.passIndex = m_PassIndex; if (m_RequiresColor) passData.copiedColor = builder.UseTexture(copiedColor, IBaseRenderGraphBuilder.AccessFlags.Read); passData.source = builder.UseTextureFragment(renderer.activeColorTexture, 0, IBaseRenderGraphBuilder.AccessFlags.Write); builder.SetRenderFunc((PassData data, RasterGraphContext rgContext) => { Blitter.BlitTexture(rgContext.cmd, data.copiedColor, new Vector4(1, 1, 0, 0), s_PassMaterial, data.passIndex); }); } } private class PassData { internal Material effectMaterial; internal int passIndex; internal TextureHandle source; public TextureHandle copiedColor; } } }