既存のRendererFeatureをURP14のBlitに対応させる
はじめに
URP14ではRendererFeatureでのポストエフェクトのかけ方が変わりました。
過去バージョンでは使用できていたRendererFeatureを使って、URP14への対応を行っていきます。
環境
Unity 2022.2.2f1
Universal RP 14.0.4
過去バージョンのコードを持ってくる
実際にURP10環境で動いていた、RendererFeatureをURP14に持ってきます。
機能としては至ってシンプルで、グレースケールをかけるだけになります。
RendererFeature
using UnityEngine;
using UnityEngine.Rendering.Universal;
namespace ConvertBlitter
{
    public class ConvertBlitterGrayscaleRendererFeature : ScriptableRendererFeature
    {
        [SerializeField]
        private Shader shader;
        private ConvertBlitterGrayscalePass grayscalePass;
        public override void Create()
        {
            grayscalePass = new ConvertBlitterGrayscalePass(shader);
        }
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
            renderer.EnqueuePass(grayscalePass);
        }
    }
}
RenderPass
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace ConvertBlitter
{
    public class ConvertBlitterGrayscalePass : ScriptableRenderPass
    {
        private const string ProfilerTag = nameof(ConvertBlitterGrayscalePass);
        private readonly Material material;
        private RenderTargetHandle tmpRenderTargetHandle;
        private RenderTargetIdentifier cameraColorTarget;
        public ConvertBlitterGrayscalePass(Shader shader)
        {
            material = CoreUtils.CreateEngineMaterial(shader);
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            tmpRenderTargetHandle.Init("_TempRT");
        }
        public void SetRenderTarget(RenderTargetIdentifier target)
        {
            cameraColorTarget = target;
        }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (renderingData.cameraData.isSceneViewCamera)
            {
                return;
            }
            var cmd = CommandBufferPool.Get(ProfilerTag);
            var descriptor = renderingData.cameraData.cameraTargetDescriptor;
            descriptor.depthBufferBits = 0;
            cmd.GetTemporaryRT(tmpRenderTargetHandle.id, descriptor);
            cmd.Blit(cameraColorTarget, tmpRenderTargetHandle.Identifier(), material);
            cmd.Blit(tmpRenderTargetHandle.Identifier(), cameraColorTarget);
            cmd.ReleaseTemporaryRT(tmpRenderTargetHandle.id);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }
}
シェーダー
Shader "ConvertBlitter/ConvertBlitterGrayScale"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Renderpipeline"="UniversalPipeline" }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct Varyings
            {
                float2 uv : TEXCOORD0;
                float4 positionHCS : SV_POSITION;
            };
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            CBUFFER_START(UnityPerMaterial)
            float4 _MainTex_ST;
            CBUFFER_END
            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                return OUT;
            }
            half4 frag (Varyings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                half gray = dot(col.rgb, half3(0.299, 0.587, 0.114));
                return half4(gray, gray, gray, col.a);
            }
            ENDHLSL
        }
    }
}
描画できるようにする
このままですと、エラーで描画ができません。
1つ1つエラーを読み解いて、正しくポストエフェクトがかかるようにします。
SetupRenderPassesに移行する
上記のソースコードそのままですと以下のようなエラーが出ます。

You can only call cameraColorTarget inside the scope of a ScriptableRenderPass. Otherwise the pipeline camera target texture might have not been created or might have already been disposed.
これは、RenderTargetTextureが作成されていない、もしくは破棄されている可能性があります。
cameraColorTargetは、ScriptableRenderPassのスコープで呼んでください。
という意味合いです。
このエラー文通り、ScriptableRenderPassで呼ぶとエラーが消えてグレースケールのポストエフェクトがかかります。
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
-       grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
        renderer.EnqueuePass(grayscalePass);
    }
+    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
+    {
+        grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
+    }
描画結果

警告を消す
これで描画自体はできましたが、警告が出ているので直します。
RTHandleを渡すようにする

Assets/ConvertBliter/Scripts/ConvertBliterGrayscalePass.cs(13,17): warning CS0618: 'RenderTargetHandle' is obsolete: 'Deprecated in favor of RTHandle'
RenderTargetHandleは廃止予定なので、RTHandleに置換してねという警告です。
ですので、RTHandleに置換してあげます。
    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
-       grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
+       grayscalePass.SetRenderTarget(renderer.cameraColorTargetHandle);
    }
ついでに呼び出し側のSetRenderTargetの引数もRenderTargetIdentifierからRTHandleに変えます。
-   private RenderTargetIdentifier cameraColorTarget;
+   private RTHandle cameraColorTarget;
    ・・・
-   public void SetRenderTarget(RenderTargetIdentifier target)
+   public void SetRenderTarget(RTHandle target)
    {
        cameraColorTarget = target;
    }
private RenderTargetHandle tmpRenderTargetHandle;に関しては、次のフェーズで消します。
RTHandleをRenderTargetIdentifierに代入できる理由
RTHandleが、RenderTargetIdentifierを持っており、その値と比較してくれているからになります。
/// <summary>
/// Implicit conversion operator to RenderTargetIdentifier
/// </summary>
/// <param name="handle">Input RTHandle</param>
/// <returns>RenderTargetIdentifier representation of the RTHandle.</returns>
public static implicit operator RenderTargetIdentifier(RTHandle handle)
{
    return handle != null ? handle.nameID : default(RenderTargetIdentifier);
}
Blitterで描画
URP14ですのでBlitterを使用して描画します。
Blitterに置換
ConvertBlitterGrayscalePassでcmd.BlitをしていたのをBlitter.BlitCameraTextureへと変えます。
-   private RenderTargetHandle tmpRenderTargetHandle;
    ・・・
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (renderingData.cameraData.isSceneViewCamera)
        {
            return;
        }
        var cmd = CommandBufferPool.Get(ProfilerTag);
-       var descriptor = renderingData.cameraData.cameraTargetDescriptor;
-       descriptor.depthBufferBits = 0;
-
-       cmd.GetTemporaryRT(tmpRenderTargetHandle.id, descriptor);
-       cmd.Blit(cameraColorTarget, tmpRenderTargetHandle.Identifier(), material);
-       cmd.Blit(tmpRenderTargetHandle.Identifier(), cameraColorTarget);
-       cmd.ReleaseTemporaryRT(tmpRenderTargetHandle.id);
+       Blitter.BlitCameraTexture(cmd, cameraColorTarget, cameraColorTarget, material, 0);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }
エラーは出ていませんが、gameビュー上でポストエフェクトが適応されなくなってしまいました。

そこで、FrameDebugを見てみます。
そうすると、ポストエフェクトのPassが通っていることがわかります。
なので、シェーダー側に問題がありそうです。

シェーダーをBlitterに対応させる
Blit.hlslに処理を任せても良いですが、頂点シェーダーを変更したいケースだと仮定してなるべくBlit.hlslに任せないようにします。
Blitした画像の解像度やRenderTargetはBlit.hlslにしかないのでincludeします。
includeすると構造体の名前が重複してしまうので、変更します。
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
+   #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
-   struct Attributes
+   struct GrayscaleAttributes
    {
        float4 positionOS : POSITION;
        float2 uv : TEXCOORD0;
    };
-   struct Varyings
+   struct GrayscaleVaryings
    {
        float2 uv : TEXCOORD0;
        float4 positionHCS : SV_POSITION;
    };
    // 以降のAttributesとVaryingsも置換する
後は、頂点シェーダーとフラグメントシェーダーを対応させて完了になります。
-   struct GrayscaleAttributes
-   {
-       float4 positionOS : POSITION;
-       float2 uv : TEXCOORD0;
-   };
+   #if SHADER_API_GLES
+       struct GrayscaleAttributes
+       {
+           float4 positionOS : POSITION;
+           float2 uv : TEXCOORD0;
+       };
+   #else
+       struct GrayscaleAttributes
+       {
+           uint vertexID : SV_VertexID;
+       };
+   #endif
    GrayscaleVaryings vert (GrayscaleAttributes IN)
    {
        GrayscaleVaryings OUT;
+       #if SHADER_API_GLES
+           float4 pos = input.positionOS;
+           float2 uv  = input.uv;
+       #else
+           float4 pos = GetFullScreenTriangleVertexPosition(IN.vertexID);
+           float2 uv  = GetFullScreenTriangleTexCoord(IN.vertexID);
+       #endif
-       OUT.positionHCS = IN.positionOS;
-       OUT.uv = IN.uv * _BlitScaleBias.xy + _BlitScaleBias.zw;
+       OUT.positionHCS = pos;
+       OUT.uv = uv;
        return OUT;
    }
    half4 frag (GrayscaleVaryings IN) : SV_Target
    {
-       half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
+       half4 col = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearRepeat, IN.uv);
        half gray = dot(col.rgb, half3(0.299, 0.587, 0.114));
        return half4(gray, gray, gray, col.a);
    }
グレースケールになり、Blitterを使用したポストエフェクトをかけることができました。

対応後コード
最後に対応後のコードを紹介して終わりになります。
RendererFeature
using UnityEngine;
using UnityEngine.Rendering.Universal;
namespace ConvertBlitter
{
    public class ConvertBlitterGrayscaleRendererFeature : ScriptableRendererFeature
    {
        [SerializeField]
        private Shader shader;
        private ConvertBlitterGrayscalePass grayscalePass;
        public override void Create()
        {
            grayscalePass = new ConvertBlitterGrayscalePass(shader);
        }
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            renderer.EnqueuePass(grayscalePass);
        }
        // renderer.cameraColorTargetはSetupRenderPasses内で呼ぶ
        public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
        {
            // cameraColorTarget -> cameraColorTargetHandleにする
            grayscalePass.SetRenderTarget(renderer.cameraColorTargetHandle);
        }
    }
}
RenderPass
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace ConvertBlitter
{
    public class ConvertBlitterGrayscalePass : ScriptableRenderPass
    {
        private const string ProfilerTag = nameof(ConvertBlitterGrayscalePass);
        private readonly Material material;
        private RTHandle cameraColorTarget;
        public ConvertBlitterGrayscalePass(Shader shader)
        {
            material = CoreUtils.CreateEngineMaterial(shader);
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
        }
        public void SetRenderTarget(RTHandle target)
        {
            cameraColorTarget = target;
        }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (renderingData.cameraData.isSceneViewCamera)
            {
                return;
            }
            var cmd = CommandBufferPool.Get(ProfilerTag);
            // Blitterで描画する
            Blitter.BlitCameraTexture(cmd, cameraColorTarget, cameraColorTarget, material, 0);
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }
}
シェーダー
Shader "ConvertBlitter/ConvertBlitterGrayScale"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Renderpipeline"="UniversalPipeline" }
        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"
            #if SHADER_API_GLES
                struct GrayscaleAttributes
                {
                    float4 positionOS : POSITION;
                    float2 uv : TEXCOORD0;
                };
            #else
                struct GrayscaleAttributes
                {
                    uint vertexID : SV_VertexID;
                };
            #endif
            struct GrayscaleVaryings
            {
                float2 uv : TEXCOORD0;
                float4 positionHCS : SV_POSITION;
            };
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            CBUFFER_START(UnityPerMaterial)
            float4 _MainTex_ST;
            CBUFFER_END
            GrayscaleVaryings vert (GrayscaleAttributes IN)
            {
                GrayscaleVaryings OUT;
                #if SHADER_API_GLES
                    float4 pos = input.positionOS;
                    float2 uv  = input.uv;
                #else
                    float4 pos = GetFullScreenTriangleVertexPosition(IN.vertexID);
                    float2 uv  = GetFullScreenTriangleTexCoord(IN.vertexID);
                #endif
                OUT.positionHCS = pos;
                OUT.uv = uv;
                return OUT;
            }
            half4 frag (GrayscaleVaryings IN) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearRepeat, IN.uv);
                half gray = dot(col.rgb, half3(0.299, 0.587, 0.114));
                return half4(gray, gray, gray, col.a);
            }
            ENDHLSL
        }
    }
}
Discussion