URP14.0.8:second pass can't get the render texture correctly in the multiPass scriptable render feat

Hi everyone,

I’m learning scriptable render feature,I’m trying to coding a multiPass scriptable render feature,the first pass will draw a normal texture in render texture,it works,but in the second pass,no matter what I want to do,the render texture is always black,I guess the second pass do not get the render texture which the first pass drew,but I don’t know where I went wrong

here is the scriptable render feature code:

public class NormalOutLineFeature : ScriptableRendererFeature
{

    [System.Serializable]
    public class Settings
    {
        public LayerMask layer;
        public Material normalTexMat;
        public Material normalOutLineMat;
        public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingPrePasses;
    }

    public Settings settings = new Settings();

    public class DrawNormalTexPass : ScriptableRenderPass
    {
        private Settings settings;
        ShaderTagId shaderTag = new ShaderTagId("DepthOnly");

        FilteringSettings filter;
        NormalOutLineFeature feature;

        private RTHandle normalRTHC;
        private RTHandle normalRTHD;

        public DrawNormalTexPass(Settings settings,NormalOutLineFeature feature)
        {
            this.settings = settings;
            this.feature = feature;

            RenderQueueRange queue = new RenderQueueRange();

            queue.lowerBound = 1000;
            queue.upperBound = 3500;
            filter = new FilteringSettings(queue,settings.layer);
        }


        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            base.Configure(cmd, cameraTextureDescriptor);
            RenderTextureDescriptor desc = cameraTextureDescriptor;
            RenderingUtils.ReAllocateIfNeeded(ref normalRTHD, desc, wrapMode: TextureWrapMode.Clamp, name:"_NormalTex");
            desc.depthBufferBits = 0;
            RenderingUtils.ReAllocateIfNeeded(ref normalRTHC, desc, wrapMode: TextureWrapMode.Clamp, name: "_NormalTex");
            ConfigureTarget(normalRTHC,normalRTHD);
            ConfigureClear(ClearFlag.All, Color.black);

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("DrawNormalTex");
            var draw = CreateDrawingSettings(shaderTag, ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
            draw.overrideMaterial = settings.normalTexMat;
            draw.overrideMaterialPassIndex = 0;
            context.DrawRenderers(renderingData.cullResults, ref draw, ref filter);
            CommandBufferPool.Release(cmd);
        }

       
    }

    public class DrawNormalLinePass : ScriptableRenderPass
    {
        private Settings settings;
        NormalOutLineFeature feature;

        private RTHandle sourceRTH;
        private RTHandle destinationRTH;
        private RTHandle destinationRTHD;

        public DrawNormalLinePass(Settings settings,NormalOutLineFeature feature)
        {
            this.settings = settings;
            this.feature = feature;
        }

        public override void FrameCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(Shader.PropertyToID("_NormalOutLine"));
            cmd.ReleaseTemporaryRT(Shader.PropertyToID("_NormalTex"));
        }


        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            base.Configure(cmd, cameraTextureDescriptor);
            RenderTextureDescriptor desc = cameraTextureDescriptor;

            RenderingUtils.ReAllocateIfNeeded(ref destinationRTHD, desc, wrapMode: TextureWrapMode.Clamp, name: "_NormalOutLine");
            desc.depthBufferBits = 0;
            RenderingUtils.ReAllocateIfNeeded(ref destinationRTH, desc, wrapMode: TextureWrapMode.Clamp, name: "_NormalOutLine");
            ConfigureTarget(destinationRTH, destinationRTHD);
            ConfigureClear(ClearFlag.All, Color.black);

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("DrawOutLine");
            Blitter.BlitCameraTexture(cmd, destinationRTH, destinationRTH, settings.normalOutLineMat, 0);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    private DrawNormalTexPass _DrawNormalTexPass;

    private DrawNormalLinePass _DrawNormalLinePass;


    public override void Create()
    {
        _DrawNormalTexPass = new DrawNormalTexPass(settings, this);
        _DrawNormalTexPass.renderPassEvent = settings.passEvent;
        _DrawNormalLinePass= new DrawNormalLinePass(settings, this);
        _DrawNormalLinePass.renderPassEvent = settings.passEvent;
    }

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

and here is the first shader and second shader:

Shader "Unlit/NormalTex"
{
    Properties
    {
    }
    SubShader
    {
        Tags
            {
                "RenderPipeline" = "UniversalPipeline"
                "RenderType"="Opaque"
            }
        LOD 100

        Pass
        {
           HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
           

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float3 normalWS : TEXCOORD0;
                float3 normalVS : TEXCOORD1;
            };


            Varyings vert (Attributes v)
            {
                Varyings o;
                o.positionHCS = TransformObjectToHClip(v.positionOS);
                o.normalWS = TransformObjectToWorldNormal(v.normalOS);
                o.normalVS = TransformWorldToViewDir(o.normalWS,true);
                return o;
            }

            half4 frag (Varyings i) : SV_Target
            {
                half4 col = 0;
                col.rgb = i.normalVS;
                return col;
            }
            ENDHLSL
        }
    }
}
Shader "Unlit/TestShader"
{
    Properties
    {
    }
    SubShader
    {
        Tags
            {
                "RenderPipeline" = "UniversalPipeline"
                "RenderType"="Opaque"
            }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

           

            SAMPLER(sampler_BlitTexture);

           

            half4 frag (Varyings input) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D_X(_BlitTexture,sampler_BlitTexture,input.texcoord);
                return col;
            }
            ENDHLSL
        }
    }

and here is the frame debugger:

Hi, I can see in your second render pass (DrawNormalLinePass):

// The destinationRTH is a color format render texture
// The color is initialized to black by "ConfigureClear(ClearFlag.All, Color.black);"
// The Blitter.BlitCameraTexture firstly set the material's "_BlitTexture" property to destinationRTH.
// Then set the color render target to destinationRTH, depth target to none.
// After that, it creates a full screen mesh in front of the camera and render it with the first shader pass (0) of your material.
Blitter.BlitCameraTexture(cmd, destinationRTH, destinationRTH, settings.normalOutLineMat, 0);

Do you mean if I want my second pass get the render texture which my first pass drew, I need to use my first pass’s RTHandle as source RTHandle in the Blitter.BlitCameraTexture?

Yes, by the way, usually there’s no need to allocate a depth buffer (destinationRTHD) when using Blit, because the depth test is set to always for full screen mesh.

It works! Thank you!

Unfortunately,there is a another problem now.If I start unity with game view,it seem fine,but if I change to scene view,the scene view can’t render object,and the game view is always black,and unity report errors in every frame, but if I start play mode,it seem that everything is fine.The error says the value which in BlitCameraTexture can’t be null,but I do have a Non-Null judgement,is there something wrong when I pass RTHandle?

here is the errors:

Assertion failed
UnityEngine.Rendering.Blitter:BlitCameraTexture (UnityEngine.Rendering.CommandBuffer,UnityEngine.Rendering.RTHandle,UnityEngine.Rendering.RTHandle,UnityEngine.Material,int)
NormalOutLineFeature/DrawNormalLinePass:Execute (UnityEngine.Rendering.ScriptableRenderContext,UnityEngine.Rendering.Universal.RenderingData&) (at Assets/RendererFeatures/OutLine/NormalOutLineFeature.cs:121)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)
ArgumentNullException: Value cannot be null.
Parameter name: value
UnityEngine.MaterialPropertyBlock.SetTexture (System.Int32 nameID, UnityEngine.Texture value) (at <978764d1d9b243168a3a9f50b21a9490>:0)
UnityEngine.Rendering.Blitter.BlitTexture (UnityEngine.Rendering.CommandBuffer cmd, UnityEngine.Rendering.RTHandle source, UnityEngine.Vector4 scaleBias, UnityEngine.Material material, System.Int32 pass) (at ./Library/PackageCache/com.unity.render-pipelines.core@14.0.8/Runtime/Utilities/Blitter.cs:261)
UnityEngine.Rendering.Blitter.BlitCameraTexture (UnityEngine.Rendering.CommandBuffer cmd, UnityEngine.Rendering.RTHandle source, UnityEngine.Rendering.RTHandle destination, UnityEngine.Material material, System.Int32 pass) (at ./Library/PackageCache/com.unity.render-pipelines.core@14.0.8/Runtime/Utilities/Blitter.cs:377)
NormalOutLineFeature+DrawNormalLinePass.Execute (UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Rendering.Universal.RenderingData& renderingData) (at Assets/RendererFeatures/OutLine/NormalOutLineFeature.cs:121)
UnityEngine.Rendering.Universal.ScriptableRenderer.ExecuteRenderPass (UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Rendering.Universal.ScriptableRenderPass renderPass, UnityEngine.Rendering.Universal.RenderingData& renderingData) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/ScriptableRenderer.cs:1490)
UnityEngine.Rendering.Universal.ScriptableRenderer.ExecuteBlock (System.Int32 blockIndex, UnityEngine.Rendering.Universal.ScriptableRenderer+RenderBlocks& renderBlocks, UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Rendering.Universal.RenderingData& renderingData, System.Boolean submit) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/ScriptableRenderer.cs:1446)
UnityEngine.Rendering.Universal.ScriptableRenderer.Execute (UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Rendering.Universal.RenderingData& renderingData) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/ScriptableRenderer.cs:1222)
UnityEngine.Rendering.Universal.UniversalRenderPipeline.RenderSingleCamera (UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Rendering.Universal.CameraData& cameraData, System.Boolean anyPostProcessingEnabled) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/UniversalRenderPipeline.cs:650)
UnityEngine.Rendering.Universal.UniversalRenderPipeline.RenderSingleCameraInternal (UnityEngine.Rendering.ScriptableRenderContext context, UnityEngine.Camera camera) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/UniversalRenderPipeline.cs:534)
UnityEngine.Rendering.Universal.UniversalRenderPipeline.Render (UnityEngine.Rendering.ScriptableRenderContext renderContext, System.Collections.Generic.List`1[T] cameras) (at ./Library/PackageCache/com.unity.render-pipelines.universal@14.0.8/Runtime/UniversalRenderPipeline.cs:376)
UnityEngine.Rendering.RenderPipeline.InternalRender (UnityEngine.Rendering.ScriptableRenderContext context, System.Collections.Generic.List`1[T] cameras) (at <978764d1d9b243168a3a9f50b21a9490>:0)
UnityEngine.Rendering.RenderPipelineManager.DoRenderLoop_Internal (UnityEngine.Rendering.RenderPipelineAsset pipe, System.IntPtr loopPtr, UnityEngine.Object renderRequest, Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle safety) (at <978764d1d9b243168a3a9f50b21a9490>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)

and here is the scriptable render feature code:

public class NormalOutLineFeature : ScriptableRendererFeature
{

    [System.Serializable]
    public class Settings
    {
        public LayerMask layer;
        public Material normalTexMat;
        public Material normalOutLineMat;
        public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingPrePasses;
    }

    public Settings settings = new Settings();

    public class RTHDatas
    {
        public RTHandle normalRTH;
    }

    public class DrawNormalTexPass : ScriptableRenderPass
    {
        private Settings settings;
        ShaderTagId shaderTag = new ShaderTagId("DepthOnly");

        FilteringSettings filter;
        NormalOutLineFeature feature;

        private RTHandle normalRTHC;
        private RTHandle normalRTHD;

        public RTHandle NormalRTHC { get => normalRTHC; }


        public DrawNormalTexPass(Settings settings,NormalOutLineFeature feature)
        {
            this.settings = settings;
            this.feature = feature;

            RenderQueueRange queue = new RenderQueueRange();

            queue.lowerBound = 1000;
            queue.upperBound = 3500;
            filter = new FilteringSettings(queue,settings.layer);
        }

        public void SetTarget(RTHandle noramlRTHC)
        {
            this.normalRTHC = noramlRTHC;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            base.Configure(cmd, cameraTextureDescriptor);
            RenderTextureDescriptor desc = cameraTextureDescriptor;
            RenderingUtils.ReAllocateIfNeeded(ref normalRTHD, desc, wrapMode: TextureWrapMode.Clamp, name:"_NormalTex");
            desc.depthBufferBits = 0;
            RenderingUtils.ReAllocateIfNeeded(ref normalRTHC, desc, wrapMode: TextureWrapMode.Clamp, name: "_NormalTex");
            ConfigureTarget(normalRTHC,normalRTHD);
            ConfigureClear(ClearFlag.All, Color.black);

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("DrawNormalTex");
            var draw = CreateDrawingSettings(shaderTag, ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
            draw.overrideMaterial = settings.normalTexMat;
            draw.overrideMaterialPassIndex = 0;
            context.DrawRenderers(renderingData.cullResults, ref draw, ref filter);
            CommandBufferPool.Release(cmd);
        }

        public void Dispose()
        {
            normalRTHC?.Release();
            normalRTHD?.Release();
        }

       
    }

    public class DrawNormalLinePass : ScriptableRenderPass
    {
        private Settings settings;
        NormalOutLineFeature feature;

        private RTHandle sourceRTH;
        private RTHandle destinationRTH;

        public DrawNormalLinePass(Settings settings,NormalOutLineFeature feature)
        {
            this.settings = settings;
            this.feature = feature;
        }

        public void SetTarget(RTHandle sourceRTH)
        {
            this.sourceRTH = sourceRTH;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            base.Configure(cmd, cameraTextureDescriptor);
            RenderTextureDescriptor desc = cameraTextureDescriptor;
            desc.depthBufferBits = 0;
            RenderingUtils.ReAllocateIfNeeded(ref destinationRTH, desc, wrapMode: TextureWrapMode.Clamp, name: "_NormalOutLine");
            ConfigureTarget(destinationRTH);
            ConfigureClear(ClearFlag.All, Color.black);

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("DrawOutLine");
            if (sourceRTH != null)
            {
                Blitter.BlitCameraTexture(cmd, sourceRTH, destinationRTH, settings.normalOutLineMat, 0);
            }
            else
            {
                Debug.Log("sourceRTH is null!");
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public void Dispose()
        {
            sourceRTH?.Release();
            destinationRTH?.Release();
        }
    }

    private DrawNormalTexPass _DrawNormalTexPass;

    private DrawNormalLinePass _DrawNormalLinePass;



    public override void Create()
    {
        _DrawNormalTexPass = new DrawNormalTexPass(settings, this);
        _DrawNormalTexPass.renderPassEvent = settings.passEvent;
        _DrawNormalLinePass= new DrawNormalLinePass(settings, this);
        _DrawNormalLinePass.renderPassEvent = settings.passEvent;
    }


    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(_DrawNormalTexPass);
        if (_DrawNormalTexPass.NormalRTHC != null)
        {
            _DrawNormalLinePass.SetTarget(_DrawNormalTexPass.NormalRTHC);
        }
        else
        {
            Debug.Log("normalRTHC is null!");
        }
        renderer.EnqueuePass(_DrawNormalLinePass);
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _DrawNormalTexPass?.Dispose();
        _DrawNormalLinePass?.Dispose();

    }
}

Haven’t met this kind of issues before, but I suggest comparing the code with the example in the URP docs, such as calling cmd.Clear(); before releasing cmd to pool.

Thanks for your patience

I have long stopped supporting image effects from URP in scene view, i always check for Camera.main before do any blitting and have peace of mind :slight_smile:

I even went so far as to make a Game view controller for preview everything and move my camera and light directly in Game view due to how much suffer i had trying to support URP image effects in scene view.

This also makes sure the effect is not rendered in some random helper camera.