diff --git a/README.md b/README.md index 046d976..2e767f3 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,10 @@ Other custom effects in samples but not used in screenshots: ## Compatibility -* Unity 2020.2 -* URP 10.2.2 +This branch is under development and it targets: -**Note:** There is a branch with support for URP 8.2.0 (Unity 2020.1) which you can find [here](https://github.com/yahiaetman/URPCustomPostProcessingStack/tree/URP-8.2.0). +* Unity 2021.2 & 2021.3 +* URP 12.1 ## Features diff --git a/Runtime/RenderFeatures/CustomPostProcess.cs b/Runtime/RenderFeatures/CustomPostProcess.cs index eb3e7f9..63e3eeb 100644 --- a/Runtime/RenderFeatures/CustomPostProcess.cs +++ b/Runtime/RenderFeatures/CustomPostProcess.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; -namespace UnityEngine.Rendering.Universal.PostProcessing { +namespace UnityEngine.Rendering.Universal.PostProcessing +{ /// /// This render feature is responsible for: @@ -21,7 +22,8 @@ public class CustomPostProcess : ScriptableRendererFeature /// The settings for the custom post processing render feature. /// [Serializable] - public class CustomPostProcessSettings { + public class CustomPostProcessSettings + { /// /// Three list (one for each injection point) that holds the custom post processing renderers in order of execution during the render pass @@ -29,7 +31,8 @@ public class CustomPostProcessSettings { [SerializeField] public List renderersAfterOpaqueAndSky, renderersBeforePostProcess, renderersAfterPostProcess; - public CustomPostProcessSettings(){ + public CustomPostProcessSettings() + { renderersAfterOpaqueAndSky = new List(); renderersBeforePostProcess = new List(); renderersAfterPostProcess = new List(); @@ -46,11 +49,6 @@ public CustomPostProcessSettings(){ /// private CustomPostProcessRenderPass m_AfterOpaqueAndSky, m_BeforePostProcess, m_AfterPostProcess; - /// - /// A handle to the "_AfterPostProcessTexture" used as the target for the builtin post processing pass in the last camera in the camera stack - /// - private RenderTargetHandle m_AfterPostProcessColor; - /// /// Injects the custom post-processing render passes. /// @@ -58,21 +56,26 @@ public CustomPostProcessSettings(){ /// Rendering state. Use this to setup render passes. public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { + // UniversalRenderer u_renderer = renderer as UniversalRenderer; + // if(u_renderer == null) { + // Debug.LogWarning("The CustomPostProcess renderer feature supports the UniversalRenderer only"); + // return; + // } + // Only inject passes if post processing is enabled - if(renderingData.cameraData.postProcessEnabled) { + if (renderingData.cameraData.postProcessEnabled) + { // For each pass, only inject if there is at least one custom post-processing renderer class in it. - if(m_AfterOpaqueAndSky.HasPostProcessRenderers && m_AfterOpaqueAndSky.PrepareRenderers(ref renderingData)){ - m_AfterOpaqueAndSky.Setup(renderer.cameraColorTarget, renderer.cameraColorTarget); + if (m_AfterOpaqueAndSky.HasPostProcessRenderers && m_AfterOpaqueAndSky.PrepareRenderers(ref renderingData)) + { renderer.EnqueuePass(m_AfterOpaqueAndSky); } - if(m_BeforePostProcess.HasPostProcessRenderers && m_BeforePostProcess.PrepareRenderers(ref renderingData)){ - m_BeforePostProcess.Setup(renderer.cameraColorTarget, renderer.cameraColorTarget); + if (m_BeforePostProcess.HasPostProcessRenderers && m_BeforePostProcess.PrepareRenderers(ref renderingData)) + { renderer.EnqueuePass(m_BeforePostProcess); } - if(m_AfterPostProcess.HasPostProcessRenderers && m_AfterPostProcess.PrepareRenderers(ref renderingData)){ - // If this camera resolve to the final target, then both the source & destination will be "_AfterPostProcessTexture" (Note: a final blit/post pass is added by the renderer). - var source = renderingData.cameraData.resolveFinalTarget ? m_AfterPostProcessColor.Identifier() : renderer.cameraColorTarget; - m_AfterPostProcess.Setup(source, source); + if (m_AfterPostProcess.HasPostProcessRenderers && m_AfterPostProcess.PrepareRenderers(ref renderingData)) + { renderer.EnqueuePass(m_AfterPostProcess); } } @@ -83,36 +86,39 @@ public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingD /// public override void Create() { - // This is copied from the forward renderer - m_AfterPostProcessColor.Init("_AfterPostProcessTexture"); // Create the three render passes and send the custom post-processing renderer classes to each Dictionary shared = new Dictionary(); m_AfterOpaqueAndSky = new CustomPostProcessRenderPass(CustomPostProcessInjectionPoint.AfterOpaqueAndSky, InstantiateRenderers(settings.renderersAfterOpaqueAndSky, shared)); m_BeforePostProcess = new CustomPostProcessRenderPass(CustomPostProcessInjectionPoint.BeforePostProcess, InstantiateRenderers(settings.renderersBeforePostProcess, shared)); m_AfterPostProcess = new CustomPostProcessRenderPass(CustomPostProcessInjectionPoint.AfterPostProcess, InstantiateRenderers(settings.renderersAfterPostProcess, shared)); } - + /// /// Converts the class name (AssemblyQualifiedName) to an instance. Filters out types that don't exist or don't match the requirements. /// /// The list of assembly-qualified class names /// Dictionary of shared instances keyed by class name /// List of renderers - private List InstantiateRenderers(List names, Dictionary shared){ + private List InstantiateRenderers(List names, Dictionary shared) + { var renderers = new List(names.Count); - foreach(var name in names){ - if(shared.TryGetValue(name, out var renderer)){ + foreach (var name in names) + { + if (shared.TryGetValue(name, out var renderer)) + { renderers.Add(renderer); - } else { + } + else + { var type = Type.GetType(name); - if(type == null || !type.IsSubclassOf(typeof(CustomPostProcessRenderer))) continue; + if (type == null || !type.IsSubclassOf(typeof(CustomPostProcessRenderer))) continue; var attribute = CustomPostProcessAttribute.GetAttribute(type); - if(attribute == null) continue; + if (attribute == null) continue; renderer = Activator.CreateInstance(type) as CustomPostProcessRenderer; renderers.Add(renderer); - - if(attribute.ShareInstance) + + if (attribute.ShareInstance) shared.Add(name, renderer); } } @@ -148,28 +154,13 @@ public class CustomPostProcessRenderPass : ScriptableRenderPass /// /// Array of 2 intermediate render targets used to hold intermediate results. /// - private RenderTargetHandle[] m_Intermediate; - - /// - /// Indentifies whether the intermediate render targets are allocated or not. - /// - private bool[] m_IntermediateAllocated; + private RTHandle[] m_Intermediate; /// /// The texture descriptor for the intermediate render targets. /// private RenderTextureDescriptor m_IntermediateDesc; - /// - /// The source of the color data for the render pass - /// - private RenderTargetIdentifier m_Source; - - /// - /// The destination of the color data for the render pass - /// - private RenderTargetIdentifier m_Destination; - /// /// A list of profiling samplers, one for each post process renderer /// @@ -180,16 +171,20 @@ public class CustomPostProcessRenderPass : ScriptableRenderPass /// public bool HasPostProcessRenderers => m_PostProcessRenderers.Count != 0; + private readonly string[] _IntermediateRTNames = new string[2] { "_IntermediateRT0", "_IntermediateRT1" }; + /// /// Construct the custom post-processing render pass /// /// The post processing injection point /// The list of classes for the renderers to be executed by this render pass - public CustomPostProcessRenderPass(CustomPostProcessInjectionPoint injectionPoint, List renderers){ + public CustomPostProcessRenderPass(CustomPostProcessInjectionPoint injectionPoint, List renderers) + { this.injectionPoint = injectionPoint; this.m_ProfilingSamplers = new List(renderers.Count); this.m_PostProcessRenderers = renderers; - foreach(var renderer in renderers){ + foreach (var renderer in renderers) + { // Get renderer name and add it to the names list var attribute = CustomPostProcessAttribute.GetAttribute(renderer.GetType()); m_ProfilingSamplers.Add(new ProfilingSampler(attribute?.Name)); @@ -197,65 +192,50 @@ public CustomPostProcessRenderPass(CustomPostProcessInjectionPoint injectionPoin // Pre-allocate a list for active renderers this.m_ActivePostProcessRenderers = new List(renderers.Count); // Set render pass event and name based on the injection point. - switch(injectionPoint){ - case CustomPostProcessInjectionPoint.AfterOpaqueAndSky: - renderPassEvent = RenderPassEvent.AfterRenderingSkybox; + switch (injectionPoint) + { + case CustomPostProcessInjectionPoint.AfterOpaqueAndSky: + renderPassEvent = RenderPassEvent.AfterRenderingSkybox; m_PassName = "Custom PostProcess after Opaque & Sky"; break; - case CustomPostProcessInjectionPoint.BeforePostProcess: + case CustomPostProcessInjectionPoint.BeforePostProcess: renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; m_PassName = "Custom PostProcess before PostProcess"; break; case CustomPostProcessInjectionPoint.AfterPostProcess: // NOTE: This was initially "AfterRenderingPostProcessing" but it made the builtin post-processing to blit directly to the camera target. - renderPassEvent = RenderPassEvent.AfterRendering; + // NOTE: For URP 12, we are back to using "AfterRenderingPostProcessing" + renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; m_PassName = "Custom PostProcess after PostProcess"; break; } // Initialize the IDs and allocation state of the intermediate render targets - m_Intermediate = new RenderTargetHandle[2]; - m_Intermediate[0].Init("_IntermediateRT0"); - m_Intermediate[1].Init("_IntermediateRT1"); - m_IntermediateAllocated = new bool[2]; - m_IntermediateAllocated[0] = false; - m_IntermediateAllocated[1] = false; + m_Intermediate = new RTHandle[2]; } - /// - /// Gets the corresponding intermediate RT and allocates it if not already allocated - /// - /// The command buffer to use for allocation - /// The intermediate RT index - /// - private RenderTargetIdentifier GetIntermediate(CommandBuffer cmd, int index){ - if(!m_IntermediateAllocated[index]){ - cmd.GetTemporaryRT(m_Intermediate[index].id, m_IntermediateDesc); - m_IntermediateAllocated[index] = true; - } - return m_Intermediate[index].Identifier(); + public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) + { + base.OnCameraSetup(cmd, ref renderingData); + // Copy camera target description for intermediate RTs. Disable multisampling and depth buffer for the intermediate targets. + m_IntermediateDesc = renderingData.cameraData.cameraTargetDescriptor; + m_IntermediateDesc.msaaSamples = 1; + m_IntermediateDesc.depthBufferBits = 0; + m_Intermediate[0] = RTHandles.Alloc(m_IntermediateDesc, FilterMode.Point, TextureWrapMode.Clamp, name: _IntermediateRTNames[0]); + m_Intermediate[1] = RTHandles.Alloc(m_IntermediateDesc, FilterMode.Point, TextureWrapMode.Clamp, name: _IntermediateRTNames[1]); } - /// - /// Release allocated intermediate RTs - /// - /// The command buffer to use for deallocation - private void CleanupIntermediate(CommandBuffer cmd){ - for(int index = 0; index < 2; ++index){ - if(m_IntermediateAllocated[index]){ - cmd.ReleaseTemporaryRT(m_Intermediate[index].id); - m_IntermediateAllocated[index] = false; - } + public override void OnCameraCleanup(CommandBuffer cmd) + { + base.OnCameraCleanup(cmd); + for (int i = 0; i < m_Intermediate.Length; i++) + { + m_Intermediate[i].Release(); } } - /// - /// Setup the source and destination render targets - /// - /// Source render target - /// Destination render target - public void Setup(RenderTargetIdentifier source, RenderTargetIdentifier destination){ - this.m_Source = source; - this.m_Destination = destination; + public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) + { + base.Configure(cmd, cameraTextureDescriptor); } /// @@ -263,7 +243,8 @@ public void Setup(RenderTargetIdentifier source, RenderTargetIdentifier destinat /// /// Current rendering data /// True if any renderer will be executed for the given camera. False Otherwise. - public bool PrepareRenderers(ref RenderingData renderingData){ + public bool PrepareRenderers(ref RenderingData renderingData) + { // See if current camera is a scene view camera to skip renderers with "visibleInSceneView" = false. bool isSceneView = renderingData.cameraData.cameraType == CameraType.SceneView; @@ -272,12 +253,14 @@ public bool PrepareRenderers(ref RenderingData renderingData){ // Collect the active renderers m_ActivePostProcessRenderers.Clear(); - for(int index = 0; index < m_PostProcessRenderers.Count; index++){ + for (int index = 0; index < m_PostProcessRenderers.Count; index++) + { var ppRenderer = m_PostProcessRenderers[index]; // Skips current renderer if "visibleInSceneView" = false and the current camera is a scene view camera. - if(isSceneView && !ppRenderer.visibleInSceneView) continue; + if (isSceneView && !ppRenderer.visibleInSceneView) continue; // Setup the camera for the renderer and if it will render anything, add to active renderers and get its required inputs - if(ppRenderer.Setup(ref renderingData, injectionPoint)){ + if (ppRenderer.Setup(ref renderingData, injectionPoint)) + { m_ActivePostProcessRenderers.Add(index); passInput |= ppRenderer.input; } @@ -288,7 +271,7 @@ public bool PrepareRenderers(ref RenderingData renderingData){ // return if no renderers are active return m_ActivePostProcessRenderers.Count != 0; - } + } /// /// Execute the custom post processing renderers @@ -297,80 +280,77 @@ public bool PrepareRenderers(ref RenderingData renderingData){ /// Current rendering data public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { - // Copy camera target description for intermediate RTs. Disable multisampling and depth buffer for the intermediate targets. - m_IntermediateDesc = renderingData.cameraData.cameraTargetDescriptor; - m_IntermediateDesc.msaaSamples = 1; - m_IntermediateDesc.depthBufferBits = 0; - + RTHandle target = renderingData.cameraData.renderer.cameraColorTargetHandle; CommandBuffer cmd = CommandBufferPool.Get(m_PassName); - + context.ExecuteCommandBuffer(cmd); cmd.Clear(); int width = m_IntermediateDesc.width; int height = m_IntermediateDesc.height; - cmd.SetGlobalVector("_ScreenSize", new Vector4(width, height, 1.0f/width, 1.0f/height)); - + cmd.SetGlobalVector("_ScreenSize", new Vector4(width, height, 1.0f / width, 1.0f / height)); + // The variable will be true if the last renderer couldn't blit to destination. // This happens if there is only 1 renderer and the source is the same as the destination. bool requireBlitBack = false; // The current intermediate RT to use as a source. int intermediateIndex = 0; - for(int index = 0; index < m_ActivePostProcessRenderers.Count; ++index){ + for (int index = 0; index < m_ActivePostProcessRenderers.Count; ++index) + { var rendererIndex = m_ActivePostProcessRenderers[index]; var renderer = m_PostProcessRenderers[rendererIndex]; - - RenderTargetIdentifier source, destination; - if(index == 0){ + + RTHandle source, destination; + if (index == 0) + { // If this is the first renderers then the source will be the external source (not intermediate). - source = m_Source; - if(m_ActivePostProcessRenderers.Count == 1){ - // There is only one renderer, check if the source is the same as the destination - if(m_Source == m_Destination){ - // Since we can't bind the same RT as a texture and a render target at the same time, we will blit to an intermediate RT. - destination = GetIntermediate(cmd, 0); - // Then we will blit back to the destination. - requireBlitBack = true; - } else { - // Otherwise, we can directly blit from source to destination. - destination = m_Destination; - } - } else { + source = target; + if (m_ActivePostProcessRenderers.Count == 1) + { + // Since we can't bind the same RT as a texture and a render target at the same time, we will blit to an intermediate RT. + destination = m_Intermediate[0]; + // Then we will blit back to the destination. + requireBlitBack = true; + } + else + { // If there is more than one renderer, we will need to the intermediate RT anyway. - destination = GetIntermediate(cmd, intermediateIndex); + destination = m_Intermediate[intermediateIndex]; } - } else { + } + else + { // If this is not the first renderer, we will want to the read from the intermediate RT. - source = GetIntermediate(cmd, intermediateIndex); - if(index == m_ActivePostProcessRenderers.Count - 1){ + source = m_Intermediate[intermediateIndex]; + if (index == m_ActivePostProcessRenderers.Count - 1) + { // If this is the last renderer, blit to the destination directly. - destination = m_Destination; - } else { + destination = target; + } + else + { // Otherwise, flip the intermediate RT index and set as destination. // This will act as a ping pong process between the 2 RT where color data keeps moving back and forth while being processed on each pass. intermediateIndex = 1 - intermediateIndex; - destination = GetIntermediate(cmd, intermediateIndex); + destination = m_Intermediate[intermediateIndex]; } } - using(new ProfilingScope(cmd, m_ProfilingSamplers[rendererIndex])) + using (new ProfilingScope(cmd, m_ProfilingSamplers[rendererIndex])) { // If the renderer was not already initialized, initialize it. - if(!renderer.Initialized) + if (!renderer.Initialized) renderer.InitializeInternal(); // Execute the renderer. renderer.Render(cmd, source, destination, ref renderingData, injectionPoint); } - + } // If blit back is needed, blit from the intermediate RT to the destination (see above for explanation) - if(requireBlitBack) - Blit(cmd, m_Intermediate[0].Identifier(), m_Destination); - - // Release allocated Intermediate RTs. - CleanupIntermediate(cmd); + if (requireBlitBack) + Blit(cmd, m_Intermediate[0], target); // Send command buffer for execution, then release it. context.ExecuteCommandBuffer(cmd); diff --git a/package.json b/package.json index 6dcbaec..c44822d 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,11 @@ "version": "2.0.0", "displayName": "Universal RP Post-Processing Stack", "description": "A post-processing stack for Universal RP", - "unity": "2020.2", + "unity": "2021.2", "unityRelease": "0f1", "dependencies": { - "com.unity.render-pipelines.universal": "10.2.2", - "com.unity.render-pipelines.core": "10.2.2" + "com.unity.render-pipelines.universal": "12.1.0", + "com.unity.render-pipelines.core": "12.1.0" }, "keywords": [ "graphics",