CORETECH ENGINEER BLOG

株式会社サイバーエージェント SGEコア技術本部 技術ブログ

Unity6からRenderGraphを使いこなそう!応用実装編2「データの受け渡し」

はじめに

みなさんこんにちは、CyberAgent SGEコアテク所属のチャンユービンです。

前回はRenderGraphで簡単なBlit操作を実装方法について解説しました。

今回はPass間のデータ受け渡し方法について話していきたいと思います。

今までの記事

執筆する時点の環境

  • Unity6 (6000.0.19f1)
  • URP 17.0.3

従来のPass間でのデータ受け渡し方法

RenderGraphの話に入る前に、まず従来よく使われてきたデータの受け渡し方法について紹介します。

1. データを直接渡す (お勧め度:△)

PassクラスのGet、Setなどの関数やプロパティを通してデータのやり取りをする手法です。

サンプルコード

    class PassA
    {
        // PassAの処理結果は_resultRTに出力する
        RTHandle _resultRT;
        public RTHandle GetResultRT()
        {
            return _resultRT:
        }
    }
    
    class PassB
    {
        // PassAのインスタンスへの参照が必要
        PassA _passA;
        // 中略
        void Draw()
        {
            var passAResultRT = _passA.GetResultRT();
            // 後続の処理
        }
    }

分かりやすく実装しやすい方法ではありますが、クラス間で直接参照が必要になり、実装が密結合になりがちです。そのため、テストデモや限定的な範囲内での使用以外には、あまりおすすめしません。

2. ShaderのGlobalパラメータに設定する (お勧め度:○)

Shaderに渡すデータであれば、ShaderのGlobalパラメータに設定して、後続で実行されるShaderから直接アクセスできるようにする手法です。

サンプルコード

    // ShaderクラスのStatic関数で設定できる
    Shader.SetGlobalFloat(propertyID, data);
    Shader.SetGlobalTexture(propertyID, rtHandle);
    
    // CommandBufferでもできる
    // 設定タイミングが決められるため、基本的にこっちがおすすめ
    cmd.SetGlobalFloat(propertyID, data);
    cmd.SetGlobalTexture(propertyID, rtHandle);

一度Globalパラメータに設定しておけば、後続のPassにデータを都度渡す手間が省け、Material.Set処理の時間も節約できるメリットがあります。

URP内部では、CameraDepthやCameraNormal、MotionVector、各種GBuffer、各種カメラ行列など、後続のPassで使用されるデータをGlobalパラメータ化しています。

一方、GlobalパラメータはPropertyIDとデータをバインドしているだけなので、同じPropertyIDに別のデータを設定すると、バインド先が置き換わってしまいます。さらに、URP内部には_ScreenSize_ScreenParams_Timeなど、一般的な単語が使われたGlobalパラメータも存在しているため、うっかりそれらを置き換えてしまうと、後の描画が狂うリスクがあります。

また、無闇に多くのデータをGlobalパラメータ化すると、パフォーマンスやメモリ使用量に悪影響を及ぼすリスクがあります。

そのため、メリットを最大限に発揮し、リスクを抑えるためには、後続の描画処理で複数回使用されるデータのみをGlobalパラメータ化することをおすすめします。また、Globalパラメータとして設定する際には、URPやサードパーティのパッケージで使用されているProperty名と重複しないよう、一度検索をかけて確認すると良いでしょう。

3. データコンテナを介してデータを渡す(お勧め度:◎)

Passクラスと別で、仲介者クラスを作って、どのPassでもそれにアクセスできるようにしてデータを伝達する手法です。

サンプルコード

    // 各種データを格納するコンテナ
    class DataContainer
    {
        public float DataA { get; set; }
        public RTHandle TexA { get; set; }
        // 他色々...
    }
    
    class PassA
    {
        private RTHandle _resultRT;
        private float _data;
            
        public Draw(DataContainer container)
        {
            // 処理略
            // 出力データをholderに渡す
            _dataContainer.DataA = _data;
            _dataContainer.TexA = _resultRT;
        }
    }
    
    class PassB
    {
        public Draw(DataContainer container)
        {
             // holderからデータをとる
            var dataA = container.DataA;
            var texA = container.TexA;
            // 後続処理略
        }
    }
    
    class SampleRenderer
    {
        // Renderer側がコンテナーのインスタンスを持つ
        DataContainer _container;
        PassA _passA;
        PassB _passB;
        
        void DrawPasses()
        {
            // 各Passの実行時にコンテナーを渡す
            _passA.Draw(_container);
            _passB.Draw(_container);
        }
    }

このような実装はいわゆるDTO(Data Transfer Object)パターンといい、Passクラス間の密結合を防げて、データの伝達することができます。

Shader用のデータのみに使えるGlobalパラメータ化の方法と違って、どんなデータにも使えます。Shaderで使わないデータや、Shader用データでも広範囲で使われないデータの受け渡しには、この方法が最もおすすめです。

従来のシステムでURPが用意してくれたデータコンテナ

サンプルコード

    public virtual void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    {}
    
    public virtual void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {}

従来のシステムでのOnCameraSetupやExecuteなどの第二引数であるRenderingDataが、まさにこのデータコンテナに該当します。

この中から、URPが用意しているCameraData、LightData、ShadowData、PostProcessingDataなど、描画に使用できるさまざまなデータを取得することができます。

ただし、そのコンテナはURPデータの取得に限られており、自前のデータを追加することはできないため、データコンテナとしての機能は不完全でした。

後ほど詳しく説明しますが、RenderGraphではURPデータの取得に加えて、自前のデータも自由に追加できる新しいデータコンテナが提供され、Pass間のデータ受け渡しが大変便利になりました。

RenderGraphでデータ受け渡しの実装方法

ここからは、サンプルコードを見ながら、RenderGraphでデータ受け渡しの実装方法について解説していきます。

以下の機能をサンプルとします。

設定された円の内部にだけ、色と上下が反転された画像を描画する。(色やUV反転などの操作は1Passでも実装できるが、2Passに分けたのはPass間のデータ受け渡しを解説するため)

サンプル画像
RendererFeatureインスペクター
RendererFeatureインスペクター
描画結果
描画結果

RenderGraphでShader Globalパラメータに設定する方法

サンプルコード

Shader

Shader "Hidden/Sample/DataTransfer"
{
    SubShader
    {
       Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
       ZTest Always ZWrite Off Cull Off

       Pass     // 色、上下反転のテクスチャ描画
        {
            Name "DrawNegative"

            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

            #pragma vertex Vert
            #pragma fragment Frag

            half4 Frag(Varyings input) : SV_TARGET
            {
                float2 uv = input.texcoord.xy;
                uv.y = 1 - uv.y; // 上下反転
                half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
                half4 negative = half4(1 - color.rgb, color.a); // 色反転
                return negative;
            }
            ENDHLSL
        }

        Pass    // カメラカラーと合成
        {
            Name "Combine"

            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

            #pragma vertex Vert
            #pragma fragment Frag

            TEXTURE2D_X(_NegativeTexture);
            float4 _Params;

            #define CENTER_UV       _Params.xy
            #define RADIUS          _Params.z
            #define ASPECT_RATIO    _Params.w

            half4 Frag(Varyings input) : SV_TARGET
            {
                float2 uv = input.texcoord.xy;
                float2 uvDiff = uv - CENTER_UV;

                uvDiff.x *= ASPECT_RATIO;
                float distSqr = dot(uvDiff, uvDiff);
                float radiusSqr = RADIUS * RADIUS;

                half4 negativeColor = SAMPLE_TEXTURE2D_X(_NegativeTexture, sampler_LinearClamp, uv);
                half4 sceneColor = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);

                // 円内の部分を反転色にする
                half inCircle = distSqr < radiusSqr;
                half4 color = inCircle ? negativeColor : sceneColor;

                return color;
            }
            ENDHLSL
        }
    }
}

RenderPass、およびRendererFeature

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

public class DrawNegativePass : ScriptableRenderPass
{
    private static readonly int NegativeTexturePropertyId = Shader.PropertyToID("_NegativeTexture");
    private static readonly int ParamsPropertyId = Shader.PropertyToID("_Params");

    private Material _material;
    private Vector2 _center;
    private float _radius;

    public void SetData(Material material, Vector2 center, float radius)
    {
        _material = material;
        _center = center;
        _radius = radius;
    }
    private class PassData
    {
        internal Material Material;
        internal TextureHandle SourceTexture;
        //internal TextureHandle NegativeTexture;
        internal Vector4 Params;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var cameraData = frameData.Get<UniversalCameraData>();
        var resourceData = frameData.Get<UniversalResourceData>();

        var sourceTextureHandle = resourceData.activeColorTexture;
        var aspectRatio = cameraData.cameraTargetDescriptor.width / (float)cameraData.cameraTargetDescriptor.height;

        var negativeDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
        negativeDescriptor.name = "_NegativeTexture";
        negativeDescriptor.clearBuffer = false;
        negativeDescriptor.msaaSamples = MSAASamples.None;
        negativeDescriptor.depthBufferBits = 0;

        var negativeTextureHandle = renderGraph.CreateTexture(negativeDescriptor);

        // カメラカラーを反転し、出力用のテクスチャに描画するRasterRenderPassを作成し、RenderGraphに追加
        using (var builder = renderGraph.AddRasterRenderPass<PassData>("DrawNegativePass", out var passData))
        {
            // passDataに必要なデータを入れる
            passData.Material = _material;
            passData.SourceTexture = sourceTextureHandle;
            //passData.NegativeTexture = negativeTextureHandle;
            passData.Params = new Vector4(_center.x, _center.y, _radius, aspectRatio);

            // 描画ターゲット設定
            builder.SetRenderAttachment(negativeTextureHandle, 0, AccessFlags.Write);
            builder.UseTexture(sourceTextureHandle, AccessFlags.Read);
            // 解説 *1
            // ShaderのGlobal変数への設定ができるように
            // 要注意!
            builder.AllowGlobalStateModification(true);
            // 解説 *2
            // negativeTextureHandleが描画された後に、"_NegativeTexture"という名前のGlobalTextureに設定する
            builder.SetGlobalTextureAfterPass(negativeTextureHandle, NegativeTexturePropertyId);
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                var cmd = context.cmd;
                var material = data.Material;
                var source = data.SourceTexture;
                var parameters = data.Params;

                Blitter.BlitTexture(cmd, source, Vector2.one, material, 0);

                // 解説 *3
                // Texture以外の変数は描画関数内でCommandBufferを使ってGlobalに設定する
                cmd.SetGlobalVector(ParamsPropertyId, parameters);

                // 解説 *4
                // Textureでもは描画関数内でCommandBufferを使ってGlobalに設定できるが、RenderGraphViewerに検知されないため非推奨
                //cmd.SetGlobalTexture(NegativeTexturePropertyId, data.NegativeTexture);
            });
        }
    }
}

public class CombinePass : ScriptableRenderPass
{
    private static readonly int NegativeTexturePropertyId = Shader.PropertyToID("_NegativeTexture");

    private Material _material;

    public void SetData(Material material)
    {
        _material = material;
    }
    private class PassData
    {
        internal Material Material;
        internal TextureHandle SourceTexture;
        internal Vector4 Params;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var resourceData = frameData.Get<UniversalResourceData>();
        var sourceTextureHandle = resourceData.activeColorTexture;

        // 現在アクティブのカメラカラーを元に、合成テクスチャを作成
        var targetDesc = renderGraph.GetTextureDesc(sourceTextureHandle);
        targetDesc.name = "_CombineTexture";
        targetDesc.clearBuffer = false;
        targetDesc.depthBufferBits = 0;
        var combineTextureHandle = renderGraph.CreateTexture(targetDesc);

        // 合成したテクスチャをカメラカラーに設定
        resourceData.cameraColor = combineTextureHandle;

        // カメラカラーを反転し、出力用のテクスチャに描画するRasterRenderPassを作成し、RenderGraphに追加
        using (var builder = renderGraph.AddRasterRenderPass<PassData>("CombinePass", out var passData))
        {
            // passDataに必要なデータを入れる
            passData.Material = _material;
            passData.SourceTexture = sourceTextureHandle;

            // 描画ターゲット設定
            builder.SetRenderAttachment(combineTextureHandle, 0, AccessFlags.Write);
            builder.UseTexture(sourceTextureHandle, AccessFlags.Read);
            // 解説 *5
            // GlobalTextureの使用宣言
            builder.UseGlobalTexture(NegativeTexturePropertyId, AccessFlags.Read);
            // 解説 *6
            // すべてのGlobalTextureを使用申請
            // builder.UseAllGlobalTexture(true);
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                var cmd = context.cmd;
                var material = data.Material;
                var source = data.SourceTexture;
                
                // _ParamsとNegativeTextureが前のPassでGlobalパラメータに設定されたので
                // このPassでマテリアルに設定したくても、そのまま使える
                Blitter.BlitTexture(cmd, source, Vector2.one, material, 1);
            });
        }
    }
}

public class DataTransferRendererFeature : ScriptableRendererFeature
{
    [SerializeField]
    private Vector2 center = new(0.5f, 0.5f);
    [SerializeField]
    private float radius = 0.5f;

    private const string ShaderPath = "Hidden/Sample/DataTransfer";
    private Material _material;
    private Material material
    {
        get
        {
            if (_material == null)
            {
                _material = CoreUtils.CreateEngineMaterial(ShaderPath);
            }
            return _material;
        }
    }

    private DrawNegativePass _drawNegativePass;
    private CombinePass _combinePass;

    public override void Create()
    {
        _drawNegativePass = new DrawNegativePass
        {
            renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing
        };
        _combinePass = new CombinePass
        {
            renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing
        };
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
            if (renderingData.cameraData.cameraType == CameraType.Preview)
        {
            // Previewカメラがエフェクトの対象外なので、除外する
            return;
        }

        _drawNegativePass.SetData(material, center, radius);
        _combinePass.SetData(material);

        renderer.EnqueuePass(_drawNegativePass);
        renderer.EnqueuePass(_combinePass);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            CoreUtils.Destroy(material);
        }
    }
}

コード解説

  1. RenderGraphでデータをGlobalに設定する前に、まずbuilder.AllowGlobalStateModification(true); でGlobalStateの修正許可をもらう必要がある。そうしないと、SetGlobalXXXの行為をする時にエラーが出る。
  2. builder.SetGlobalTextureAfterPass(textureHanlde, propertyId); を実行すると、このPassが実行終了のタイミングでtextureHanlde が自動的にpropertyIdというGlobal Slotにセットされる
  3. Textureタイプ以外のデータは、従来システムと似た感じで、描画関数内で、cmd.SetGlobalXXXでGlobal化する
    RenderGraphViewerで確認
  4. Textureタイプのデータも、描画関数内で cmd.SetGlobalTexture を使ってGlobal化できる場合があるが、RenderGraphViewerで検知されず、予期せぬエラーが発生する可能性があるため非推奨。特に、AttachmentにセットされたTextureに対して行うと必ずエラーが発生する。
  5. GlobalTextureも使用申請が必要で、申請を行わないと、Passが実行される時点でTextureが破棄されている場合がある。
    • RenderGraphは描画段階に入る前に、すべての描画Passが申請したTextureをチェックして、各Textureの破棄タイミングを決める。Textureは後続のPassで使われなくなるタイミングで破棄される(RenderGraphはShader内での使用を事前に検知できないため、事前の使用申請で破棄タイミングを決めている)。
      この図のように、GlobalTextureの使用申請をしておかないと、反転テクスチャが破棄され取得できなくなってしまう
  6. 使用したいGlobalTextureが多く、いちいち書く面倒を省きたい場合や、コードの可読性とメンテナンス性を上げたい場合は、builder.UseAllGlobalTextures(true);で全Global Textureに対して使用申請することもできる
    • しかし、そうすると実際使ってないのに、使う予定があると判定され、Textureの破棄タイミングが遅れてしまい、描画性能に悪影響を与える可能性がある。
    • ただ、URP内部でもbuilder.UseAllGlobalTextures(true); が結構使われているため、大した悪影響にはならないと想定。

要注意

AllowGlobalStateModificationについて、公式の注意コメントが以下にあります

Allow commands in the command buffer to modify global state. 
This will introduce a render graph sync-point in the frame and cause all passes after this pass to never be reordered before this pass.
This may nave negative impact on performance and memory use if not used carefully so it is recommended to only allow this in specific use cases.

要するに、PassにGlobalパラメータの変更を許可すると、該当Passが実行されるタイミングでRenderGraphの同期ポイント(CPUとGPU間のデータ転送ポイント)が挿入され、パフォーマンスやメモリ使用量に悪影響を及ぼす可能性があります。本当に必要な時に限ってGlobalパラメータ化を使ってください。

RenderGraphでデータコンテナを使う方法

RenderGraphはContextContainer というすごく便利なコンテナクラスを用意してくれてます。

ContextContainerインスタンスがRenderer側が持っており、RecordRenderGraph関数の第二引数としてPassに渡され、描画順番で各Passに渡ります。

public virtual void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)

そのContextContainerにはURP内部の多くのデータが格納されており、自作のPassで取得して利用することが可能です。また、任意のタイミングで自前のデータを追加することもできます。

使用方法一覧

データの取得、追加は以下の関数で行えます

// コンテナー変数名をframeDataとする
ContextContainer frameData;

// <DataType>タイプのデータの存在チェック
var isContains = frameData.Contains<DataType>();

// <DataType>タイプのデータを取得
// * 存在しない場合はエラーが出る
var data0 = frameData.Get<DataType>();

// <DataType>タイプのデータを新規作成
// * すでに存在している場合はエラーが出る
var data1 = frameData.Create<DataType>();

// <DataType>タイプのデータを取得
// * 存在しない場合は新規作成する
var data2 = frameData.GetOrCreate<DataType>();

URPが用意してくれたデータは、以前の記事にも記載してある通りに、主に以下にあります

// カメラ関連の情報:変換行列、カメラの設定など
var cameraData = frameData.Get<UniversalCameraData>();
// リソース関連の情報:カメラカラーバッファ、カメラ深度バッファなど
var resourceData = frameData.Get<UniversalResourceData>();
// レンダリング関連の情報:RenderCullingResults、RenderLayerMaskなど
var renderingData = frameData.Get<UniversalRenderingData>();
// ライト関連の情報:MainLight、AdditionalLightsの情報など
var lightData = frameData.Get<UniversalLightData>();

サンプルコード

前節のサンプルのデータ受け渡しの方法をコンテナを使用することに変更すると以下になります

  • RendererFeatureとShaderに変更がないので省略

RenderPass

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

// 解説 *1
// ContextItemを継承する
public class NegativeCircleData : ContextItem
{
    // 格納するデータ(どんなタイプでもOK)
    public TextureHandle NegativeTexture;
    public Vector4 NegativeParams;

    // フレーム終了時のリセット処理
    public override void Reset()
    {
        // TextureHandleは毎フレームでリセットする必要がある
        NegativeTexture = TextureHandle.nullHandle;
    }
}

public class DrawNegativePass : ScriptableRenderPass
{
    private Material _material;
    private Vector2 _center;
    private float _radius;

    public void SetData(Material material, Vector2 center, float radius)
    {
        _material = material;
        _center = center;
        _radius = radius;
    }
    private class PassData
    {
        internal Material Material;
        internal TextureHandle SourceTexture;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var cameraData = frameData.Get<UniversalCameraData>();
        var resourceData = frameData.Get<UniversalResourceData>();

        var sourceTextureHandle = resourceData.activeColorTexture;
        var aspectRatio = cameraData.cameraTargetDescriptor.width / (float)cameraData.cameraTargetDescriptor.height;

        var negativeDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
        negativeDescriptor.name = "_NegativeTexture";
        negativeDescriptor.clearBuffer = false;
        negativeDescriptor.depthBufferBits = 0;

        var negativeTextureHandle = renderGraph.CreateTexture(negativeDescriptor);

        // 解説 *2
        // frameDataにNegativeCircleDataを作成する
        // 重複作成の恐れがある場合、frameData.GetOrCreateを使う
        var negativeCircleData = frameData.Create<NegativeCircleData>();
        // 次のPassに渡すデータを入れる
        negativeCircleData.NegativeParams = new Vector4(_center.x, _center.y, _radius, aspectRatio);
        negativeCircleData.NegativeTexture = negativeTextureHandle;

        // カメラカラーを反転し、出力用のテクスチャに描画するRasterRenderPassを作成し、RenderGraphに追加
        using (var builder = renderGraph.AddRasterRenderPass<PassData>("DrawNegativePass", out var passData))
        {
            // passDataに必要なデータを入れる
            passData.Material = _material;
            passData.SourceTexture = sourceTextureHandle;

            // 描画ターゲット設定
            builder.SetRenderAttachment(negativeTextureHandle, 0, AccessFlags.Write);
            builder.UseTexture(sourceTextureHandle, AccessFlags.Read);
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                var cmd = context.cmd;
                var material = data.Material;
                var source = data.SourceTexture;
                Blitter.BlitTexture(cmd, source, Vector2.one, material, 0);
            });
        }
    }
}

public class CombinePass : ScriptableRenderPass
{
    private static readonly int NegativeTexturePropertyId = Shader.PropertyToID("_NegativeTexture");
    private static readonly int ParamsPropertyId = Shader.PropertyToID("_Params");
    private Material _material;

    public void SetData(Material material)
    {
        _material = material;
    }
    private class PassData
    {
        internal Material Material;
        internal TextureHandle SourceTexture;
        internal TextureHandle NegativeTexture;
        internal Vector4 NegativeParams;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var resourceData = frameData.Get<UniversalResourceData>();
        var sourceTextureHandle = resourceData.activeColorTexture;

        // 解説 *3
        if (!frameData.Contains<NegativeCircleData>())
        {
            // NegativeCircleDataがない場合は、このPassは実行しない
            Debug.LogError("NegativeCircleData is not found.");
            return;
        }
        // frameDataからNegativeCircleDataを取得
        var negativeCircleData = frameData.Get<NegativeCircleData>();

        // 現在アクティブのカメラカラーを元に、合成テクスチャを作成
        var targetDesc = renderGraph.GetTextureDesc(sourceTextureHandle);
        targetDesc.name = "_CombineTexture";
        targetDesc.clearBuffer = false;
        targetDesc.depthBufferBits = 0;
        var combineTextureHandle = renderGraph.CreateTexture(targetDesc);

        // 合成したテクスチャをカメラカラーに設定
        resourceData.cameraColor = combineTextureHandle;

        // カメラカラーを反転し、出力用のテクスチャに描画するRasterRenderPassを作成し、RenderGraphに追加
        using (var builder = renderGraph.AddRasterRenderPass<PassData>("CombinePass", out var passData))
        {
            // passDataに必要なデータを入れる
            passData.Material = _material;
            passData.SourceTexture = sourceTextureHandle;
            passData.NegativeTexture = negativeCircleData.NegativeTexture;
            passData.NegativeParams = negativeCircleData.NegativeParams;

            // 描画ターゲット設定
            builder.SetRenderAttachment(combineTextureHandle, 0, AccessFlags.Write);
            builder.UseTexture(sourceTextureHandle, AccessFlags.Read);
            // 解説 *4
            // NegativeTextureにもちろん使用申請が必要
            builder.UseTexture(negativeCircleData.NegativeTexture, AccessFlags.Read);
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                var cmd = context.cmd;
                var material = data.Material;
                var source = data.SourceTexture;
                var negativeTexture = data.NegativeTexture;
                var negativeParams = data.NegativeParams;

                // 必要なデータがGlobal化されてないので、ここでMaterialにテクスチャとパラメータをセットする
                material.SetTexture(NegativeTexturePropertyId, negativeTexture);
                material.SetVector(ParamsPropertyId, negativeParams);

                Blitter.BlitTexture(cmd, source, Vector2.one, material, 1);
            });
        }
    }
}

コード解説

  1. ContextContainerに入れられるデータクラスは、必ずContextItemを継承する必要がある
    • Reset()は必ずオーバーライドする必要があり、毎フレームのリセット操作をその中に記述する
  2. 同じデータタイプで2回以上Create()を呼び出すとエラーになるため、新規データ作成(メモリの確保)のタイミングを厳密にコントロールする必要がない場合は、GetOrCreate()を使用する方が無難かと思う
  3. NegativeCircleDataが存在しない場合は、後続の処理をスキップする
    • CombinePassの実行はDrawNegativePassの実行結果に依存していますが、直接前置Passの実行状況をチェックするのではなく、コンテナ内の必要なデータの有無で実行可否を判定することで、前置条件となるPassへの直接参照を回避してする
    • これにより、Pass間の密結合を防ぐことができる
  4. 見落としがちですが、使用するRenderTextureは由来に関わらず、使用申請を行う必要がある

RenderGraphViewerで確認

最後に

今回の記事では、RenderGraphを使用する際のデータ受け渡し方法について、Globalパラメータとデータコンテナの2つの方法を紹介しました。それぞれの適用状況があるので、使い分けが大事です。

Globalパラメータの使用がおすすめ

  • Shader内で使用し、後続の多数のPassやShaderで共有されるデータ
  • 頻繁に置き換えが必要ないデータ

データコンテナの使用がおすすめ

  • Shader内で使用しないデータ
  • Shader内で使うが、後続の多数のPassやShaderで共有して使用されないデータ
  • 頻繁に置き換えが必要なデータ

適切な方法を選択することで、より効率的で保守性の高いレンダリングパイプラインを構築することができます。状況に応じて両方の方法を適切に組み合わせることが重要です。

次回予告

次回はRender GraphのFrameBufferFetch 機能について紹介する予定です。また次回に会いましょう。