VR でも利用できる URP Renderer Feature を用いたフェード処理の実装

はじめに

本記事では URP Renderer Feature を用いたフェード処理の実装について記します.

検証環境

  • Unity 2021.2.15f1
  • Universal RP 12.1.5
  • UniTask 2.3.1
  • DOTween 1.2.420
  • DOTweenPro 1.0.244

Renderer Feature

URP Renderer Feature は 独自の描画パスを追加することが出来る URP の機能です.

Renderer Feature を用いたフェード処理を実装するために以下の手順で進めていきます.

  • ScriptableRenderPass 継承クラスの実装
  • ScriptableRendererFeature 継承クラスの実装
  • フェード用のシェーダを作る
  • Forward Renderer Data に実装した Renderer Feature を追加
  • Renderer Feature を通したフェード処理を実装

ScriptableRenderPass 継承クラスの実装

フェード用に ScriptableRenderPass の継承クラスを実装します.ここで描画処理を行います.

以下 Pseudo Code になります.

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

namespace Deniverse
{
    /// <summary>
    /// スクリーン全体にフェードをかけるようにするカスタムパス
    /// </summary>
    public sealed class ScreenFadePass : ScriptableRenderPass
    {
        readonly FadeSettings _fadeSettings;

        public ScreenFadePass(FadeSettings fadeSettings)
        {
            if (fadeSettings != null)
            {
                _fadeSettings = fadeSettings;
                // RenderPassEvent は FadeSettings の設定に準拠する
                renderPassEvent = _fadeSettings.RenderPassEvent;
            }
        }

        /// <summary>
        /// 独自パスの実行
        /// </summary>
        /// <param name="context">ScriptableRenderContext</param>
        /// <param name="renderingData">RenderingData</param>
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // コマンドバッファの設定
            var command = CommandBufferPool.Get(_fadeSettings.ProfilerTag);

            // Blit する対象の取得(ここではカメラが元テクスチャで,現在アクティブなレンダリングターゲットをBlit先のテクスチャとする)
            var source = BuiltinRenderTextureType.CameraTarget;
            var destination = BuiltinRenderTextureType.CurrentActive;

            // 対象テクスチャにフェード用シェーダーが適用された元テクスチャ情報を適用する
            command.Blit(source, destination, _fadeSettings.RuntimeMaterial);
            // コマンドバッファの実行
            context.ExecuteCommandBuffer(command);

            // コマンドバッファの解放
            CommandBufferPool.Release(command);
        }
    }
}

一応 FadeSettings.cs はこんな感じです.

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

namespace Deniverse
{
    [Serializable]
    public sealed class FadeSettings
    {
        /// <summary>
        /// フェード処理を有効にするか
        /// </summary>
        [SerializeField]
        bool _isEnabled = true;

        /// <summary>
        /// Frame Debugger 用に表示するタグ
        /// </summary>
        [SerializeField]
        string _profilerTag = "Screen Fade";

        /// <summary>
        /// RenderPassEvent タイミング
        /// </summary>
        [SerializeField]
        RenderPassEvent _renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;

        /// <summary>
        /// フェード用のマテリアル
        /// </summary>
        [SerializeField]
        Material _faderMaterial;

        /// <summary>
        /// ランタイム用に利用する
        /// </summary>
        [NonSerialized] public Material RuntimeMaterial;

        public bool AreValid => RuntimeMaterial != null && _isEnabled;
        public string ProfilerTag => _profilerTag;
        public RenderPassEvent RenderPassEvent => _renderPassEvent;
        public Material FaderMaterial => _faderMaterial;
    }
}

ScriptableRendererFeature 継承クラスの実装

先ほど作成した独自パスを生成する ScriptableRendererFeature 継承クラスを実装します.

以下 Pseudo Code です.

using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Deniverse
{
    /// <summary>
    /// ScreenFadePass を生成する RendererFeature
    /// </summary>
    public sealed class ScreenFadeFeature : ScriptableRendererFeature
    {
        [SerializeField]
        FadeSettings _fadeSettings;

        ScreenFadePass _renderPass;

        public FadeSettings FadeSettings => _fadeSettings;

        /// <summary>
        /// 初期化処理
        /// </summary>
        public override void Create()
        {
            _renderPass = new ScreenFadePass(_fadeSettings);
        }

        /// <summary>
        /// 毎フレーム実行
        /// </summary>
        /// <param name="renderer">ScriptableRenderer</param>
        /// <param name="renderingData">RenderingData</param>
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if (_fadeSettings.AreValid)
            {
                renderer.EnqueuePass(_renderPass);
            }
        }
    }
}

フェード用のシェーダを作る

フェード用のシェーダを作ります.以下は Pseudo Code です.
こちらのシェーダが適用された適当なマテリアルを作っておきます.

Shader "Custom/Fader"
{
    Properties
    {
        _FadeColor ("FadeColor", Color) = (1, 1, 1)
        _Alpha ("Alpha", float) = 0.0
    }

    SubShader
    {
        Cull Off
        ZWrite Off
        ZTest Always
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 _FadeColor;
            half _Alpha;

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 color = fixed4(_FadeColor.rgb, _Alpha);
                return color;
            }
            ENDCG
        }
    }
}

Universal Renderer Data に実装した Renderer Feature を追加

現在 Quality と Graphics に適用されている SRP Settings に対応する Universal Renderer Data を開きます.
下に「Add Renderer Feature」のボタンがあるので,押下して「ScreenFadeFeature」を選択します.

f:id:xrdnk:20220317102808p:plain

設定は以下のようにします.Fader マテリアルは上で紹介したシェーダが適用されていればよきです.

f:id:xrdnk:20220317102851p:plain

RenderPassEvent は AfterRenderingPostProcessing そのままでいいと思います.

Renderer Feature を通したフェード処理を実装

実際にフェードイン・フェードアウトさせる処理を実装します.
UniTask と DOTween を利用した Pseudo Code になります.

using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;

namespace Deniverse
{
    /// <summary>
    /// フェードを実行するクラス
    /// </summary>
    public sealed class ScreenFader : MonoBehaviour
    {
        [SerializeField]
        ScreenFadeFeature _fadeFeature;

        [SerializeField]
        string _fadeAlphaProperty = "_Alpha";

        Material _fadeMaterial;
        CancellationToken _cancellationToken;

        const float MIN_FADE_ALPHA = 0f;
        const float MAX_FADE_ALPHA = 1f;

        void Awake()
        {
            _cancellationToken = this.GetCancellationTokenOnDestroy();
            SetupFadeFeature();
        }

        void SetupFadeFeature()
        {
            _fadeMaterial = Instantiate(_fadeFeature.FadeSettings.FaderMaterial);
            _fadeFeature.FadeSettings.RuntimeMaterial = _fadeMaterial;
        }

        /// <summary>
        /// フェードイン処理
        /// </summary>
        /// <param name="duration">フェード間隔(秒)</param>
        public async UniTask FadeInAsync(float duration)
        {
            await _fadeMaterial.DOFloat(MIN_FADE_ALPHA, _fadeAlphaProperty, duration)
                .SetLink(gameObject)
                .ToUniTask(TweenCancelBehaviour.CompleteAndCancelAwait, _cancellationToken);
        }

        /// <summary>
        /// フェードアウト処理
        /// </summary>
        /// <param name="duration">フェード間隔(秒)</param>
        public async UniTask FadeOutAsync(float duration)
        {
            await _fadeMaterial.DOFloat(MAX_FADE_ALPHA, _fadeAlphaProperty, duration)
                .SetLink(gameObject)
                .ToUniTask(TweenCancelBehaviour.CompleteAndCancelAwait, _cancellationToken);
        }
    }
}

動作確認

実際のシーン遷移時に動作確認するとこんな感じになります.
シーン遷移を実行させるボタンを押した時にフェードアウト処理を走らせ,シーン読込完了時にフェードイン処理を起こしています.

gyazo.com

おわりに

VR 用のフェードイン・フェードアウト処理はカメラの前に板のメッシュを利用する方法もあったりします.
今回の実装は URP であれば,2D でも 3D でも VR でも適用できるので,私はこちらを利用してもいいかなと思っています.

参考資料

docs.unity3d.com