URP custom post effect on selected layer

Hello, i have question regarding URP custom post effects possible implementations (specifically need to render layer and apply post effect on it, then include it in render). I’ve been searching ways to do it, but had problems to resolve each way form start to end. Need help from someone with more knowledge and organized understanding of how things done (i’m just exploring reder stuff and have on surface understanding of it for now). Thanks! (sorry if it duplicates some topics, if so, can you comment with link please)

  1. Render feature that renders on temp render texture and then applies post effect material on it using Blit() (currently just have custom render feature that does blit from render to render)
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class PostEffectFeature : ScriptableRendererFeature
{
    class RenderPass : ScriptableRenderPass
    {
        private string profilingName;
        private Material material;
        private RenderTargetIdentifier targetId;
        private RenderTargetHandle tempTexture;

        public RenderPass(string profilingName, Material material, RenderPassEvent renderPassEvent) : base()
        {
            this.profilingName = profilingName;
            this.material = material;
            this.renderPassEvent = renderPassEvent;
        }

        public void Setup(RenderTargetIdentifier source)
        {
            targetId = source;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            cmd.GetTemporaryRT(tempTexture.id, cameraTextureDescriptor);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(profilingName);

            cmd.Blit(targetId, tempTexture.Identifier(), material, -1);
            cmd.Blit(tempTexture.Identifier(), targetId);

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(tempTexture.id);
        }
    }

    [System.Serializable]
    public class Settings
    {
        public Material material;
        public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingTransparents;
    }

    [SerializeField]
    private Settings settings = new Settings();
    private RenderPass renderPass;

    public override void Create()
    {
        renderPass = new RenderPass(name, settings.material, settings.renderEvent);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderPass.Setup(renderer.cameraColorTarget);
        renderer.EnqueuePass(renderPass);
    }
}
  1. Multiple cameras with separate renderers and culling layers.

  2. For 2DRenderer can use _CameraSortingLayerTexture (use _CameraSortingLayerTexture reference in effect shader, in renderer data settings under Camera Sorting Layer Texture choose foremost sorting layer, use it in custom render feature). But from what i understand this approach doesn’t let to choose “backmost” layer to create range and to have few effects with different settings you need to have separate cameras with separate renderers.

  3. Maybe it should be inside volumes workflow somehow?

Made some progress on option 1 custom render feature, maybe it will help someone like me (do not pretend it to be well optimized or “properly” done, but it does it’s job). Render feature renders selected layers on render texture and then applies it to previously rendered stuff using material.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class LayerEffectFeature : ScriptableRendererFeature
{
    class RenderPass : ScriptableRenderPass
    {
        private string profilingName;
        private Material material;
        private int materialPassIndex;
        private FilterMode filterMode;
        private RenderTargetIdentifier destination;
        private RenderTargetHandle source;
        private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
        private FilteringSettings filter;

        public RenderPass(string profilingName, Settings settings) : base()
        {
            this.profilingName = profilingName;
            material = settings.material;
            renderPassEvent = settings.renderEvent;
            filterMode = settings.filter;
            materialPassIndex = settings.passIndex;

            //drawing settings default values ??? copied from default "Render objects" feature
            m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
            m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
            m_ShaderTagIdList.Add(new ShaderTagId("UniversalForwardOnly"));

            //layer filter
            filter = new FilteringSettings(RenderQueueRange.transparent, settings.layer);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            //get previously rendered stuff
            ScriptableRenderer renderer = renderingData.cameraData.renderer;
            destination = renderer.cameraColorTarget;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            //create render texture
            cmd.GetTemporaryRT(source.id, cameraTextureDescriptor, filterMode);
            //set render texture as target
            ConfigureTarget(source.Identifier());
            //settings for target clear
            ConfigureClear(ClearFlag.Color, Color.clear);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            DrawingSettings drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonTransparent);
            //drawingSettings.overrideMaterial = material;

            CommandBuffer cmd = CommandBufferPool.Get(profilingName);

            //render stuff
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filter);

            //apply post effect material on render texture and copy to default render target
            if (material != null)
            {
                cmd.Blit(source.Identifier(), destination, material, materialPassIndex);
            }

            context.ExecuteCommandBuffer(cmd);
            //cmd.Clear();
            CommandBufferPool.Release(cmd);
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            //clean render texture
            cmd.ReleaseTemporaryRT(source.id);
        }   
    }

    [System.Serializable]
    public class Settings
    {
        public Material material;
        public LayerMask layer;
        public int passIndex = 0;
        public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingTransparents;
        public FilterMode filter;
    }

    [SerializeField]
    private Settings settings = new Settings();
    private RenderPass renderPass;

    public override void Create()
    {
        renderPass = new RenderPass(name, settings);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(renderPass);
    }
}

These are 3 different layers, before render feature and after with two included.

I adapted render blit feature example from this package GitHub - Unity-Technologies/UniversalRenderingExamples: This project contains a collection of Custom Renderer examples. This will be updated as we refine the feature and add more options.. So now it’s 2 component more universal stuff.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public enum BufferType { CameraColor, Custom }


public class ScreenFeature : ScriptableRendererFeature
{
    class RenderPass : ScriptableRenderPass
    {
        public FilterMode filterMode { get; set; }
        private Settings settings;
        private RenderTargetIdentifier source;
        private RenderTargetIdentifier destination;
        private int tempTextureId = Shader.PropertyToID("_TempTexture");
        private int sourceId;
        private int destinationId;
        private bool isSameTarget;
        private string profilingName;

        public void SetStencilState(int reference, CompareFunction compareFunction, StencilOp passOp, StencilOp failOp, StencilOp zFailOp)
        {
            StencilState stencilState = StencilState.defaultValue;
            stencilState.enabled = true;
            stencilState.SetCompareFunction(compareFunction);
            stencilState.SetPassOperation(passOp);
            stencilState.SetFailOperation(failOp);
            stencilState.SetZFailOperation(zFailOp);

            renderStateBlock.mask |= RenderStateMask.Stencil;
            renderStateBlock.stencilReference = reference;
            renderStateBlock.stencilState = stencilState;
        }

        RenderStateBlock renderStateBlock;


        public RenderPass(string profilingName, Settings settings)
        {
            this.profilingName = profilingName;
            renderPassEvent = settings.renderPassEvent;
            this.settings = settings;

            renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            RenderTextureDescriptor blitTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
            blitTargetDescriptor.depthBufferBits = 0;

            isSameTarget = settings.sourceType == settings.destinationType &&
                (settings.sourceType == BufferType.CameraColor || settings.sourceTextureId == settings.destinationTextureId);

            var renderer = renderingData.cameraData.renderer;

            if (settings.sourceType == BufferType.CameraColor)
            {
                sourceId = -1;
                source = renderer.cameraColorTarget;
            }
            else
            {
                sourceId = Shader.PropertyToID(settings.sourceTextureId);
                cmd.GetTemporaryRT(sourceId, blitTargetDescriptor, filterMode);
                source = new RenderTargetIdentifier(sourceId);
            }

            if (isSameTarget)
            {
                destinationId = tempTextureId;
                cmd.GetTemporaryRT(destinationId, blitTargetDescriptor, filterMode);
                destination = new RenderTargetIdentifier(destinationId);
            }
            else if (settings.destinationType == BufferType.CameraColor)
            {
                destinationId = -1;
                destination = renderer.cameraColorTarget;
            }
            else
            {
                destinationId = Shader.PropertyToID(settings.destinationTextureId);
                cmd.GetTemporaryRT(destinationId, blitTargetDescriptor, filterMode);
                destination = new RenderTargetIdentifier(destinationId);
            }
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(profilingName);

            // Can't read and write to same color target, create a temp render target to blit.
            if (settings.blitMaterial == null)
            {
                Blit(cmd, source, destination);
            }
            else if (isSameTarget)
            {
                Blit(cmd, source, destination, settings.blitMaterial, settings.blitMaterialPassIndex);
                Blit(cmd, destination, source);
            }
            else
            {
                Blit(cmd, source, destination, settings.blitMaterial, settings.blitMaterialPassIndex);
            }

            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();
            CommandBufferPool.Release(cmd);
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            if (destinationId != -1)
                cmd.ReleaseTemporaryRT(destinationId);

            if (source == destination && sourceId != -1)
                cmd.ReleaseTemporaryRT(sourceId);
        }
    }

    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;

        public Material blitMaterial = null;
        public int blitMaterialPassIndex = -1;
        public BufferType sourceType = BufferType.CameraColor;
        public BufferType destinationType = BufferType.CameraColor;
        public string sourceTextureId = "_SourceTexture";
        public string destinationTextureId = "_DestinationTexture";
        public StencilStateData stencilSettings = new StencilStateData();
    }

    public Settings settings = new Settings();
    RenderPass renderPass;

    public override void Create()
    {
        renderPass = new RenderPass(name, settings);

        if (settings.stencilSettings.overrideStencilState)
        {
            renderPass.SetStencilState(settings.stencilSettings.stencilReference,
                settings.stencilSettings.stencilCompareFunction, settings.stencilSettings.passOperation,
                settings.stencilSettings.failOperation, settings.stencilSettings.zFailOperation);
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(renderPass);
    }
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class LayerFeature : ScriptableRendererFeature
{
    class RenderPass : ScriptableRenderPass
    {
        private string profilingName;
        private RenderTargetIdentifier destination;
        private List<ShaderTagId> shaderTagIdList = new List<ShaderTagId>();
        private FilteringSettings filter;
        private Settings settings;
        private int destinationId;

        public void SetStencilState(int reference, CompareFunction compareFunction, StencilOp passOp, StencilOp failOp, StencilOp zFailOp)
        {
            StencilState stencilState = StencilState.defaultValue;
            stencilState.enabled = true;
            stencilState.SetCompareFunction(compareFunction);
            stencilState.SetPassOperation(passOp);
            stencilState.SetFailOperation(failOp);
            stencilState.SetZFailOperation(zFailOp);

            renderStateBlock.mask |= RenderStateMask.Stencil;
            renderStateBlock.stencilReference = reference;
            renderStateBlock.stencilState = stencilState;
        }

        RenderStateBlock renderStateBlock;

        public RenderPass(string profilingName, Settings settings) : base()
        {
            this.profilingName = profilingName;
            renderPassEvent = settings.renderEvent;
            this.settings = settings;

            shaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
            shaderTagIdList.Add(new ShaderTagId("UniversalForward"));
            shaderTagIdList.Add(new ShaderTagId("UniversalForwardOnly"));

            filter = new FilteringSettings(RenderQueueRange.transparent, settings.layer);

            renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            RenderTextureDescriptor cameraTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor;
            cameraTextureDescriptor.depthBufferBits = 0;

            ScriptableRenderer renderer = renderingData.cameraData.renderer;

            destinationId = Shader.PropertyToID(settings.destinationTextureId);
            cmd.GetTemporaryRT(destinationId, cameraTextureDescriptor, settings.filter);
            destination = new RenderTargetIdentifier(destinationId);
            ConfigureTarget(destination);
            ConfigureClear(ClearFlag.Color, Color.clear);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagIdList, ref renderingData, SortingCriteria.CommonTransparent);
            drawingSettings.overrideMaterial = settings.overrideMaterial;

            CommandBuffer cmd = CommandBufferPool.Get(profilingName);
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();

            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filter, ref renderStateBlock);

            CommandBufferPool.Release(cmd);
        }    
    }

    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingTransparents;
        public LayerMask layer;
        public FilterMode filter;
        public string destinationTextureId = "_DestinationTexture";
        public Material overrideMaterial;
        public StencilStateData stencilSettings = new StencilStateData();
    }

    [SerializeField]
    private Settings settings = new Settings();
    private RenderPass renderPass;

    public override void Create()
    {
        renderPass = new RenderPass(name, settings);

        if (settings.stencilSettings.overrideStencilState)
        {
            renderPass.SetStencilState(settings.stencilSettings.stencilReference,
                settings.stencilSettings.stencilCompareFunction, settings.stencilSettings.passOperation,
                settings.stencilSettings.failOperation, settings.stencilSettings.zFailOperation);
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(renderPass);
    }
}

Here’s the example of how to setup the same as in previous comment.

This is amazing work. Wanting to Pixelate 1 layer in a 2D render. I could Only do it fullscreen. Now using a modified version of your script. I maanged to filter on layer but now I only see the one item and not the whole screen with the one layer pixelated. And Everthing else is Black

Shader Included if anybody can help.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class LayerEffectFeature : ScriptableRendererFeature
{
    class RenderPass : ScriptableRenderPass
    {
        private string profilingName;
        private Material material;
        private int materialPassIndex;
        private FilterMode filterMode;
        private RenderTargetIdentifier destination,source2;
        private RenderTargetHandle source;
        private RenderTargetHandle source1;
        private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
        private FilteringSettings filter;
        private FilteringSettings filter2;
        private int pixelScreenHeight, pixelScreenWidth;
        private int screenHeight;

        public RenderPass(string profilingName, Settings settings) : base()
        {
            this.profilingName = profilingName;
            material = settings.material;
            renderPassEvent = settings.renderEvent;
            filterMode = settings.filter;
            materialPassIndex = settings.passIndex;
            screenHeight = settings.screenHeight;
            //drawing settings default values ??? copied from default "Render objects" feature
            m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
            m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
            m_ShaderTagIdList.Add(new ShaderTagId("UniversalForwardOnly"));

            //layer filter
            filter = new FilteringSettings(RenderQueueRange.transparent, settings.layer);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            //get previously rendered stuff
            ScriptableRenderer renderer = renderingData.cameraData.renderer;
            destination = renderer.cameraColorTarget;
            source2 = renderer.cameraColorTarget;

            pixelScreenHeight = screenHeight;
            pixelScreenWidth = (int)(pixelScreenHeight * renderingData.cameraData.camera.aspect + 0.5f);

            material.SetVector("_BlockCount", new Vector2(pixelScreenWidth, pixelScreenHeight));
            material.SetVector("_BlockSize", new Vector2(1.0f / pixelScreenWidth, 1.0f / pixelScreenHeight));
            material.SetVector("_HalfBlockSize", new Vector2(0.5f / pixelScreenWidth, 0.5f / pixelScreenHeight));
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            RenderTextureDescriptor descriptor = cameraTextureDescriptor;
            descriptor.height = pixelScreenHeight;
            descriptor.width = pixelScreenWidth;

            //create render texture
            cmd.GetTemporaryRT(source.id, descriptor, filterMode);
            //set render texture as target
            ConfigureTarget(source.Identifier());
            //settings for target clear
            ConfigureClear(ClearFlag.Color, Color.clear);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            DrawingSettings drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonTransparent);
            //drawingSettings.overrideMaterial = material;

            CommandBuffer cmd = CommandBufferPool.Get(profilingName);

            //render stuff
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filter);

            //apply post effect material on render texture and copy to default render target
            if (material != null)
            {
                cmd.Blit(source.Identifier(), destination, material, materialPassIndex);
           
                cmd.Blit(destination, source.Identifier());
             
            }












            context.ExecuteCommandBuffer(cmd);
            //cmd.Clear();
            CommandBufferPool.Release(cmd);
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            //clean render texture
            cmd.ReleaseTemporaryRT(source.id);
        }
    }

    [System.Serializable]
    public class Settings
    {
        public Material material;
        public LayerMask layer;
        public int passIndex = 0;
        public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingTransparents;
        public FilterMode filter;
        public int screenHeight = 180;
    }

    [SerializeField]
    private Settings settings = new Settings();
    private RenderPass renderPass;

    public override void Create()
    {
        renderPass = new RenderPass(name, settings);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(renderPass);
    }
}

8454995–1122044–Pixelize.shader (1.79 KB)

Hi I tried using this but getting a Black Screen. Is your shader spesific way?