URP 17 RenderGraph API blur multi-pass fullscreen blur shader

I am following a tutorial about making a gaussian blur post-process.

However the tutorial is for 2021 LTS, and I am on version 6. Now, in URP 17, render graph API has been implemented, so the tutorial no longer works :confused:

I could enable compatibility mode, but nothing else needs it, and I would like to learn the current way of doing things, but I had difficulty in doing so…

I am having issues with converting the tutorial to the modern version (VolumeComponent and the shader itself seem to be fine)

Here is my current attempt at a ScriptableRendererFeature and a ScriptableRenderPass:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;

public sealed class BlurRendererFeature : ScriptableRendererFeature
{
    private Material material;

    private BlurRenderPass blurRenderPass;

    public override void Create()
    {
        if (material == null)
        {
            material = new Material(Shader.Find("Post-processing/Blur"));
        }

        if(material)
        {
            blurRenderPass = new BlurRenderPass();
        }

        name = "Blur";
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        if(material == null || blurRenderPass == null) 
        {
            return;
        }

        if(renderingData.cameraData.cameraType == CameraType.Preview || renderingData.cameraData.cameraType == CameraType.Reflection)
        {
            return;
        }

        BlurVolumeComponent volume = VolumeManager.instance.stack?.GetComponent<BlurVolumeComponent>();
        if(volume == null || !volume.IsActive())
        {
            return;
        }

        blurRenderPass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;

        blurRenderPass.ConfigureInput(ScriptableRenderPassInput.None);

        renderer.EnqueuePass(blurRenderPass);
    }

    private class BlurRenderPass : ScriptableRenderPass
    {
        class PassData
        {
            internal TextureHandle copySourceTexture;
        }

        static void ExecutePass(PassData data, RasterGraphContext context)
        {
            Blitter.BlitTexture(context.cmd, data.copySourceTexture, new Vector4(1, 1, 0, 0), 0, false);
        }

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            string passName = "Blur";

            // Add a raster render pass to the render graph. The PassData type parameter determines
            // the type of the passData output variable.
            using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
            {
                // UniversalResourceData contains all the texture references used by URP,
                // including the active color and depth textures of the camera.
                UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

                // Populate passData with the data needed by the rendering function
                // of the render pass.
                // Use the camera's active color texture
                // as the source texture for the copy operation.
                passData.copySourceTexture = resourceData.activeColorTexture;

                // Create a destination texture for the copy operation based on the settings,
                // such as dimensions, of the textures that the camera uses.
                // Set msaaSamples to 1 to get a non-multisampled destination texture.
                // Set depthBufferBits to 0 to ensure that the CreateRenderGraphTexture method
                // creates a color texture and not a depth texture.
                UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
                RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
                desc.msaaSamples = 1;
                desc.depthBufferBits = 0;

                // For demonstrative purposes, this sample creates a temporary destination texture.
                // UniversalRenderer.CreateRenderGraphTexture is a helper method
                // that calls the RenderGraph.CreateTexture method.
                // Using a RenderTextureDescriptor instance instead of a TextureDesc instance
                // simplifies your code.
                TextureHandle destination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "Blur", false);

                // Declare that this render pass uses the source texture as a read-only input.
                builder.UseTexture(passData.copySourceTexture);

                // Declare that this render pass uses the temporary destination texture
                // as its color render target.
                // This is similar to cmd.SetRenderTarget prior to the RenderGraph API.
                builder.SetRenderAttachment(destination, 0);

                // RenderGraph automatically determines that it can remove this render pass
                // because its results, which are stored in the temporary destination texture,
                // are not used by other passes.
                // For demonstrative purposes, this sample turns off this behavior to make sure
                // that render graph executes the render pass. 
                builder.AllowPassCulling(false);

                // Set the ExecutePass method as the rendering function that render graph calls
                // for the render pass. 
                // This sample uses a lambda expression to avoid memory allocations.
                builder.SetRenderFunc((PassData data, RasterGraphContext context)
                    => ExecutePass(data, context)); 
            }
        }
    }
}

Most of it is copied from documentation here

So how can I change this to work like in the video?

I am sorry if it feels like I’m to lazy to do it myself, but I spent over 4 hours already trying to do something with no progress :frowning:

Here are the rest of the code that seem to be functioning:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[VolumeComponentMenu("Post-processing/Blur")]
[VolumeRequiresRendererFeatures(typeof(BlurRendererFeature))]
[SupportedOnRenderPipeline(typeof(UniversalRenderPipelineAsset))]
public sealed class BlurVolumeComponent : VolumeComponent, IPostProcessComponent
{
    public BlurVolumeComponent()
    {
        displayName = "Blur";
    }

    [Tooltip("Standart deviation (spread) of the blur. Grid size is approx. 3x larger.")]
    public ClampedFloatParameter intesity = new ClampedFloatParameter(0f, 0f, 15f);

    public bool IsActive()
    {
        return (intesity.value > 0f) && active;
    }
}

and the shader

Shader "Post-processing/Blur"
{
    Properties
    {
        _MainTex("InputTex", 2D) = "white" {}
        _Spread("Standard Deviation (Spread)", Float) = 0
        _GridSize("Grid Size", Integer) = 1
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
        }

        Blend One Zero

        HLSLINCLUDE

        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #define E 2.71828f

        sampler2D _MainTex;

        CBUFFER_START(UnityPerMaterial)
            float4 _MainTex_TexelSize;
            uint _GridSize;
            float _Spread;
        CBUFFER_END

        float gaussian(int x)
        {
            float sigmaSqu = _Spread * _Spread;
            return (1 / sqrt(TWO_PI * sigmaSqu)) * pow(E, -(x*x) / (2 * sigmaSqu));
        }

        struct appdata
        {
            float4 positionOS : Position;
            float2 uv : TEXCOORD0;
        };

        struct v2f
        {
            float4 positionCS : SV_Position;
            float2 uv : TEXCOORD0;
        };

        v2f vert(appdata v)
        {
            v2f o;
            o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
            o.uv = v.uv;
            return o;
        }

        ENDHLSL

        Pass
        {
            Name "Horizontal"

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag_horizontal

            float4 frag_horizontal(v2f i) : SV_Target
            {
                float3 col = float3(0.0f, 0.0f, 0.0f);
                float gridSum = 0.0f;

                int upper = ((_GridSize - 1) * 0.5f);
                int lower = -upper;

                for(int x = lower; x <= upper; ++x)
                {
                    float gauss = gaussian(x);
                    gridSum += gauss;
                    float2 uv = i.uv + float2(_MainTex_TexelSize.x * x, 0.0f);
                    col += gauss * tex2D(_MainTex, uv).xyz;
                }

                col /= gridSum;
                return float4(col, 1.0f);
            }
            ENDHLSL
        }

        Pass
        {
            Name "Vertical"

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag_vertical

            float4 frag_vertical(v2f i) : SV_Target
            {
                float3 col = float3(0.0f, 0.0f, 0.0f);
                float gridSum = 0.0f;

                int upper = ((_GridSize - 1) * 0.5f);
                int lower = -upper;

                for(int y = lower; y <= upper; ++y)
                {
                    float gauss = gaussian(y);
                    gridSum += gauss;
                    float2 uv = i.uv + float2(0.0f, _MainTex_TexelSize.y * y);
                    col += gauss * tex2D(_MainTex, uv).xyz;
                }

                col /= gridSum;
                return float4(col, 1.0f);
            }
            ENDHLSL
        }
    }
}

Currently I also receive these errors:

I followed this tutorial and it worked. I am having issues with using a transparent rendertexture over the top of it in the UI, but you might not have that issue.