【Unity】【URP】Swap Bufferを使ってポストエフェクトをお手軽に実装する

UnityのURPでSwap Bufferをつかってポストエフェクトをお手軽にかける方法についてまとめます。

Unity 2021.3.0f1
Universal RP 12.1.6

Swap Bufferとは?

以前放送されたUnityステーションの中で、URP12の新機能としてSwap Bufferというものが紹介されていました。
本記事ではこれについてまとめます。

learning.unity3d.jp

さて、これまでのURPでは、ポストエフェクトを掛ける際に以下の手順が必要でした。

  1. まずカメラのカラーバッファを一時的なRenderTextureに描画しておく
  2. 1.のRenderTextureから、エフェクトを適用しつつカメラのカラーバッファに書き戻す

このより具体的な実装方法は以下の記事にまとめています

light11.hatenadiary.com

これに対して、URP12ではSwap Bufferと呼ばれる仕組みにより、複数のカラーバッファを裏側でいい感じに管理してくれるようになりました。
そのため単純にカメラのカラーバッファを加工するようなポストエフェクトについては、上の記事のような一時的なRenderTextureを自前で用意することなくポストエフェクトをかけることが可能になりました。

ScriptableRenderPassを実装する

手順としては、まず ScriptableRenderPass を以下のように実装します。

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

public sealed class ExampleRenderPass : ScriptableRenderPass
{
    private const string RenderPassName = nameof(ExampleRenderPass);
    private readonly Material _material;

    public ExampleRenderPass(Shader shader)
    {
        if (shader == null)
            return;

        _material = new Material(shader);
        renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData data)
    {
        if (_material == null)
            return;

        var cmd = CommandBufferPool.Get(RenderPassName);

        // 一回Blitするだけでカラーバッファにマテリアルが適用される
        Blit(cmd, ref data, _material);

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

コメント部分を見ると、Blitの回数が一回で済んでいることがわかります。

ScriptableRendererFeatureを実装する

次に ScriptableRendererFeature を以下のように実装します。

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

[Serializable]
public sealed class ExampleRendererFeature : ScriptableRendererFeature
{
    [SerializeField] private Shader _shader;

    private ExampleRenderPass _postProcessPass;

    public override void Create()
    {
        _postProcessPass = new ExampleRenderPass(_shader);
    }

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

ScriptableRendererFeature の実装はこれまでの方法と特に変わりません。

シェーダを実装する

最後にシェーダを実装します。

Shader "Hidden/Example"
{
    SubShader
    {
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            TEXTURE2D(_SourceTex);
            SAMPLER(sampler_SourceTex);

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            Varyings vert(Attributes IN)
            {                
                Varyings OUT;
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN);
                half4 col = SAMPLE_TEXTURE2D(_SourceTex, sampler_SourceTex, IN.uv);
                col.rgb *= half3(1,0,0);
                return col;
            }
            ENDHLSL
        }
    }
}

カラーバッファの色情報は_SourceTex として渡されるため、これをサンプリングして加工します。
今回は単純に赤色を乗算しているだけです。

結果

あとはUniversal Renderer DataRenderer Feature とシェーダを設定するだけです。
カメラの Post Processing プロパティにチェックを入れないと正常に表示されないのでその点だけご注意ください。

描画結果は以下の通りとなります。

結果

正常にポストエフェクトがかかることを確認できました。

参考

docs.unity3d.com