PostProcessingStack v2を使い、前フレームの画像と合成するmotion blurを実装する


1フレーム前の画像を取っておき、それをレンダリング中の画像と合成するpost effectを実装します。

shader

やっている事は単純で、流し込まれた前フレームの画像をアルファ合成しているだけです。

Shader "MotionBlurLegacyHLSL"
{
    HLSLINCLUDE

        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"

    TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
    TEXTURE2D_SAMPLER2D(_PrevFrameTex, sampler_PrevFrameTex);
    float _Alpha;

    #define SAMPLES_INT 6

    float4 Frag(VaryingsDefault i) : SV_Target
    {
            half4 dst = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            half4 prevColor = SAMPLE_TEXTURE2D(_PrevFrameTex, sampler_PrevFrameTex, i.texcoord);
            half4 color = prevColor * _Alpha + dst * (1 - _Alpha); 
        return color;
    }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

            #pragma vertex VertDefault
            #pragma fragment Frag

            ENDHLSL
        }
    }
}

c#

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;

namespace White
{
    [Serializable]
    [PostProcess(typeof(MotionBlurLegacyRenderer), PostProcessEvent.AfterStack, "Custom/MotionBlurLegacyHLSL",
        false)]
    public sealed class MotionBlurLegacy : PostProcessEffectSettings
    {
        [Range(0f, 0.99f)] public FloatParameter alpha = new FloatParameter {value = 0f};

        public override bool IsEnabledAndSupported(PostProcessRenderContext context)
        {
            if (enabled.value)
            {
                if (alpha == 0)
                {
                    return false;
                }

                return true;
            }

            return false;
        }
    }

    internal sealed class MotionBlurLegacyRenderer : PostProcessEffectRenderer<MotionBlurLegacy>
    {
        Shader shader;
        private RenderTexture _prevFrameRt;
        private RenderTargetIdentifier _rtId;

        public override void Init()
        {
            float scale = 1;
            int width = Mathf.CeilToInt(Screen.width * scale);
            int height = Mathf.CeilToInt(Screen.height * scale);
            _prevFrameRt = new RenderTexture(width, height, 24, RenderTextureFormat.ARGBHalf);
            _prevFrameRt.useMipMap = false;
            _prevFrameRt.filterMode = FilterMode.Bilinear;
            _prevFrameRt.name = "prev_frame";
            _prevFrameRt.Create();
            _rtId = new RenderTargetIdentifier(_prevFrameRt);

            shader = Shader.Find("MotionBlurLegacyHLSL");
        }

        public override void Render(PostProcessRenderContext context)
        {
            PropertySheet sheet = context.propertySheets.Get(shader);
            sheet.properties.SetFloat("_Alpha", settings.alpha);
            sheet.properties.SetTexture("_PrevFrameTex", _prevFrameRt);
            context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
            context.command.Blit(null, _rtId);
        }
    }
}

どうやって前フレームのキャプチャを渡すのか、というのが分かると簡単です。

まず、Initでキャプチャ用のRenderTextureを生成しておきます。 RenderTargetIdentifierを使ってcommandbufferにidで渡せるようにするのがポイント。

そして

 context.command.BlitFullscreenTriangle()

した後に、

context.command.Blit(null, _rtId); 

してレンダリング結果を保存します。後は

sheet.properties.SetTexture("_PrevFrameTex", _prevFrameRt); 

でshaderに渡すだけです。