CORETECH ENGINEER BLOG

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

Unity6 RenderGraphでcameraColorを差し替えて戻しBlitを回避する手法

皆さんこんにちは、コアテクグラフィクスチーム所属のチャンユービンです。
RenderGraph応用紹介記事の連載が終了してからしばらく経ちましたが、今回からはRenderGraph開発に関する小さなTipsを順次ご紹介していきます。

今回は、自作RenderPassの中からcameraColorを差し替えることで、戻しBlitを省く手法について紹介します。

サンプルプロジェクト

https://github.com/S20817/CoreTechBlogSample
SettingCameraColorSampleシーンをご覧ください

今後紹介するTipsのサンプルもそのリポジトリに入れていく予定です

あらすじ

  • ContextContainerから取得できるUniversalResourceDatacameraColor自作テクスチャに差し替える方法
  • Blit 1回削減の考え方と、使用できないケース
  • うまくいかないケースでのフォールバック処理

前提と用語

  • 実行環境:Unity 6 / URP 17+、RenderGraph有効
  • RenderGraphの基本:パスはRecordRenderGraph入出力(依存)を宣言し、SetRenderFunc実行
  • UniversalResourceData
    • activeColorTexture … 現在のアクティブなカラー。読み取り起点に使いやすい
    • cameraColor以降のURPが参照する“カメラカラー”get/set可能
    • isActiveTargetBackBuffer … 直接バックバッファに描いているかの判定に使える
      • BackBufferは読み込み不可の仕様になっており、Blitのソースとしては使用できない
  • TextureHandle:RenderGraphの仮想テクスチャハンドル
    • renderGraph.CreateTexture(desc)で内部専用の一時テクチャが作成できる
    • RTHandleなど外部のリソースを使う場合はImportしてTextureHandle化できる

なぜ差し替えるとBlitを減らせるのか

典型的なポストエフェクトは次の2ステップになりがちです。

  1. cameraColortemp にBlit(エフェクト適用)
  2. tempcameraColor戻しBlit

2.の戻しをやめて、自作パスの最後でresourceData.cameraColor = temp;とすると、以降のURPパスはtempを“ActiveColorTexture”として参照します。結果、Blitは1回で済みます

サンプルコード

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

public class TakeOverCameraColorPass : ScriptableRenderPass
{
    private Material _material; // 全画面エフェクト用

    public TakeOverCameraColorPass(RenderPassEvent evt)
    {
        renderPassEvent = evt; // 例:RenderPassEvent.AfterRenderingTransparents 等
    }

    public override void RecordRenderGraph(RenderGraph rg, ContextContainer frameData)
    {
        if (_material == null) 
            return;

        var res = frameData.Get<UniversalResourceData>();
        var cam = frameData.Get<UniversalCameraData>();

        // バックバッファ直書き中は読み戻しを避けます
        if (res.isActiveTargetBackBuffer)
            return;

        // 1) 入力(現在のカラー)
        TextureHandle src = res.activeColorTexture;

        // 2) srcと同仕様の一時テクスチャを確保
        TextureDesc desc = rg.GetTextureDesc(src);
        desc.name = "_MyEffect_Temp";
        desc.depthBufferBits = 0;
        TextureHandle temp = rg.CreateTexture(desc);

        // 3) src -> temp へBlit(エフェクト適用)
        using (var builder = rg.AddRasterRenderPass<PassData>("MyEffect_Blit", out var passData))
        {
            passData.material = _material;
            passData.src = src;

            builder.UseTexture(src);              // 読み取り
            builder.SetRenderAttachment(temp, 0); // 書き込み先
            builder.SetRenderFunc((PassData data, RasterGraphContext ctx) =>
            {
                // 全画面描画(マテリアルの0番パスを使用)
                Blitter.BlitTexture(ctx.cmd, data.src, Vector4.one, 0, false, data.material, 0);
            });
        }

        // 4) 戻しBlitの代わりに“以降のカメラカラー”を差し替え
        res.cameraColor = temp;
    }

    private class PassData
    {
        public Material material;
        public TextureHandle src;
    }
}

この手法がうまくいかないケース

使いやすい手法ではありますが、使用制限があります
CameraStack有効時、以下の状況では使用できません(出力に反映されない)

  • カメラにRenderPassを実行させている
  • そのカメラのポストプロセスが無効
  • そのカメラのポストプロセスが有効だが、AfterPostProcessing以降に実行させている

うまくいかない原因

とあるカメラでRenderGraphで作られたTempTextureは、次のカメラに引き継がれないので、cameraColorを差し替えても意味がありません。
ポストプロセスが有効な場合、UnityのUberPostPassがcameraColorCameraColorTargetへBlitします。そのため、BeforePostProcessing以前に実行すれば問題はありません。
また、最後に実行されるカメラの場合は、最後にFinalBlitがcameraColorをバックバッファにBlitするので、それも問題ありません。

フォールバック処理

上記のケースに当てはまる場合は、cameraColorを差し替える代わりに、戻しBlitを実行するようにしましょう

サンプルコード

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

public class TakeOverCameraColorPass : ScriptableRenderPass
{
    private Material _material; // 全画面エフェクト用

    public TakeOverCameraColorPass(RenderPassEvent evt)
    {
        renderPassEvent = evt; // 例:RenderPassEvent.AfterRenderingTransparents 等
    }

    public override void RecordRenderGraph(RenderGraph rg, ContextContainer frameData)
    {
        if (_material == null) 
            return;

        var res = frameData.Get<UniversalResourceData>();
        var cam = frameData.Get<UniversalCameraData>();

        // バックバッファ直書き中は読み戻しを避けます
        if (res.isActiveTargetBackBuffer)
            return;

        // 1) 入力(現在のカラー)
        TextureHandle src = res.activeColorTexture;

        // 2) srcと同仕様の一時テクスチャを確保
        TextureDesc desc = rg.GetTextureDesc(src);
        desc.name = "_MyEffect_Temp";
        desc.depthBufferBits = 0;
        TextureHandle temp = rg.CreateTexture(desc);

        // 3) src -> temp へBlit(エフェクト適用)
        using (var builder = rg.AddRasterRenderPass<PassData>("MyEffect_Blit", out var passData))
        {
            passData.material = _material;
            passData.src = src;

            builder.UseTexture(src);              // 読み取り
            builder.SetRenderAttachment(temp, 0); // 書き込み先
            builder.SetRenderFunc((PassData data, RasterGraphContext ctx) =>
            {
                // 全画面描画(マテリアルの0番パスを使用)
                Blitter.BlitTexture(ctx.cmd, data.src, Vector4.one, 0, false, data.material, 0);
            });
        }

        // 4) 条件チェック
        if (ここで使えるかどうかの条件チェック) 
        {
            // 使える場合は差し替えで
            res.cameraColor = temp;
        }
        else
        {
            // 使えない場合は戻しBlit
            renderGraph.AddBlitPass(temp, res.activeColorTexture, _material);
        }
    }

    private class PassData
    {
        public Material material;
        public TextureHandle src;
    }
}

まとめ

  • cameraColorを差し替えると、戻しBlitを回避できる
  • CameraStackを使用する場合は要注意です!

うまくいかない場合は、RenderGraphViewerやFrameDebuggerで、cameraColorの描画状況を確認しましょう