5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】はじめてのRenderGraph

Last updated at Posted at 2025-12-23

はじめに

2025年12月4日にUnity 6.3 LTSがリリースされ、さらに12月11日にはU/Day Tokyo 2025が開催されました。新しいレンダーパイプラインのデザインパターンであるRenderGraphの機能についても解説がありました。

IMG_2881.jpg
U/Day Tokyo 2025の様子。登壇:Unity Technologies Japan ブーシェ ロビン晃氏

RenderGprahへの対応機運が高まる中、今後、URPの拡張を行っていく上で必須となるRenderGraphの知識を何から学べば良いか、また取っ掛かりがわからない方も多いかと思います。

ここではRenderGraphの学び方や公開されているサンプルコードを参考に考察したり、自身の体験を踏まえながら解説します。それではRenderGraphの世界を覗いてみましょう。

環境

Unity 6000.3.1f1、Windows 11、URP 17.3.0、DirectX12

学び方

まずはともあれ参考となる情報がないと始められません。現時点ではU/Day Tokyo 2024で行われたUnity社による「URP 17へのアップグレードとRender Graphの活用方法」が一番分かりやすいでしょう。

講演資料

さらにサンプルコードも公開されています。(ロビンさんありがとう!🎁)

学び方としては

  1. 講演動画を視聴して概要を把握(この時点では話の内容があまりわからなくてもOK)
  2. サンプルコードをクローン
  3. サンプルコードを写経、サンドボックス的に改変して触ってみる
  4. 再び動画を見返す(なんとなくわかってくる)
  5. 3に戻る(再びサンプルコードで遊ぶ)

を繰り返していくのが近道です。

途中で他の資料も読むことで補足の強化や別視点からの理解の助けになります。おすすめなのが、おそらく日本語資料では一番充実しているサイバーエージェント社のCORETECH ENGINEER BLOGです。

タイトルの下にタグが設けられていますので「RenderGraph」をクリックするとRenderGraphに関連する記事を確認することができます。
スクリーンショット 2025-12-21 164322.png

ロビンさんのサンプルコードやCORETECH ENGINEER BLOGがある程度理解できたら、次にUnityのPackage ManagerからURP RenderGraph Samplesをインポートしてサンプルコードを眺めるのが良いでしょう。

スクリーンショット 2025-12-21 164914.png

Unity Packageのサンプルコードを眺めてある程度理解できたら、RenderGraphの概要は掴めるようになると思います。あとはひたすらトライアンドエラーで求める機能を実装していきます。

困ったときはUnity公式のドキュメントも参考にします。思わぬヒントが得られるかもしれません。(後述しますが結構大事なことが書かれていたりします)

実装を進めていく中でバグかな?と思ったときは公式ドキュメントやUnityフォーラムを確認するのが近道です。

サンプルコード

講演動画の視聴が終えたら公開されているサンプルコードをクローンします。サンプルコードは以下の項目が用意されていました。

項目 説明
1_Blit RT -> tempRT → RTの2パスでBlit
2_DrawRenderers 指定したRenderQueueTypeやレイヤーの描画
3_Blit_CustomRenderTarget カメラカラーに戻さずカスタムRTにBlit
4_Blit_RenderTexture RenderTextureにBlit
5_Blit_SetCameraColor カメラカラーバッファに直接Blit
6_Blit_FrameBufferFetch フレームバッファフェッチを使ったBlit
7_Blit_UnsafePass UnsafePassを使ったBlit
8_LinearUI UIをLinear空間でレンダリング

スクリーンショット 2025-12-21 170659.png

それぞのフォルダにはシーンとREADMEが用意されています。シーンを開くとすぐに確認できるのでお手軽です。詳しい内容と使用しているRendererFeature/PassはREADMEに記載されています。

プロジェクトの構成としてはURPアセット(Universal Render Pipeline Asset)にRenderGraphで記述された各RendererFeatureが登録されており、各シーン内のメインカメラに使用するRendererを指定する方法が取られていました。

URPアセットへのURP Data登録
スクリーンショット 2025-12-21 175158.png

カメラのRenderer設定
スクリーンショット 2025-12-21 175221.png

URP Packageのサンプルコード

スクリーンショット 2025-12-22 002034.png

項目 内容
Blit テクスチャコピー
BlitWithFrameData FrameDataを活用した複数マテリアルによる連続Blit処理
BlitWithMaterial 最適化されたマテリアルBlit
Compute ComputeShaderとの統合
Culling カスタムカリングによるオブジェクト描画制御
FramebufferFetch FrameBuffer Fetchの使用例
GbufferVisualization Deferred Rendering環境でのG-Buffer可視化
GlobalGbuffers G-Bufferのグローバル化
MRT Multiple Render Targets(MRT)
OutputTexture 特定テクスチャの出力・可視化(OpaqueColor、Depth、Normal、MotionVector等)
RendererList RendererListを使った描画制御
TextureReferenceWithFrameData FrameData内テクスチャ参照管理
UnsafePass UnsafePassによる手動レンダーターゲット制御

コード比較と考察

基本的なRenderGraphを把握するために、公開されているRenderGraphの最小構成に近いそれぞれのサンプルコードを比較して眺めてみます。コードはサンプルコードそのままではなく、可能な限り処理を共通化して差分が分かりやすいように再構成しました。

比較コード 参考元コード
BlitColorRendererFeature01 BlitSetColorBufferRendererFeature.cs
BlitColorRendererFeature02 NegativeRendererFeature.cs
BlitColorRendererFeature03 BlitAndSwapColorRendererFeature.cs

処理としては画面全体に指定した色を乗算する簡単な処理です。

2025_12_21_18_46_11.png

BlitColorRendererFeature01.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;

public class BlitColorRendererFeature01 : ScriptableRendererFeature
{
    private BlitColorPass01 _pass;

    public override void Create()
    {
        _pass = new BlitColorPass01();
    }

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

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _pass.Cleanup();
        }
    }
}

public class BlitColorPass01 : ScriptableRenderPass
{
    private const string PASS_NAME = nameof(BlitColorPass01);
    private const string SHADER_PATH = "BlitWithMaterial";
    private readonly Material _material;

    public BlitColorPass01()
    {
        renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
        _material = CoreUtils.CreateEngineMaterial(SHADER_PATH);
    }
    
    public void Cleanup()
    {
        CoreUtils.Destroy(_material);
    }
    
    private class PassData
    {
        public Material Material;
        public TextureHandle SourceTextureHandle;
    }
    
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        if (_material == null) return;
        
        UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
        if (resourceData.isActiveTargetBackBuffer) return;
        
        TextureHandle sourceTextureHandle = resourceData.activeColorTexture;
        TextureDesc targetDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
        targetDescriptor.name = PASS_NAME;
        targetDescriptor.msaaSamples = MSAASamples.None;
        targetDescriptor.depthBufferBits = DepthBits.None;
        TextureHandle targetTextureHandle = renderGraph.CreateTexture(targetDescriptor);
        
        using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(PASS_NAME, out PassData passData))
        {
            passData.Material = _material;
            passData.SourceTextureHandle = sourceTextureHandle;
            
            builder.UseTexture(sourceTextureHandle);
            builder.SetRenderAttachment(targetTextureHandle, 0);
            
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                Blitter.BlitTexture(context.cmd, data.SourceTextureHandle, new Vector4(1, 1, 0, 0), data.Material, 0);
            });
        }
        
        resourceData.cameraColor = targetTextureHandle;
    }
}

BlitColorRendererFeature02.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

public class BlitColorRendererFeature02 : ScriptableRendererFeature
{
    private BlitColorPass02 _pass;

    public override void Create()
    {
        _pass = new BlitColorPass02();
    }

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

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _pass.Cleanup();
        }
    }
}

public class BlitColorPass02 : ScriptableRenderPass
{
    private const string PASS_NAME = nameof(BlitColorPass02);
    private const string SHADER_PATH = "BlitWithMaterial";
    private readonly Material _material;

    public BlitColorPass02()
    {
        renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
        _material = CoreUtils.CreateEngineMaterial(SHADER_PATH);
    }

    public void Cleanup()
    {
        CoreUtils.Destroy(_material);
    }

    private class PassData
    {
        public Material Material;
        public TextureHandle SourceTextureHandle;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        if (_material == null) return;
        
        UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
        if (resourceData.isActiveTargetBackBuffer) return;
        
        TextureHandle sourceTextureHandle = resourceData.activeColorTexture;
        TextureDesc targetDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
        targetDescriptor.name = PASS_NAME;
        targetDescriptor.msaaSamples = MSAASamples.None;
        targetDescriptor.depthBufferBits = DepthBits.None;
        TextureHandle targetTextureHandle = renderGraph.CreateTexture(targetDescriptor);
        
        using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(PASS_NAME, out PassData passData))
        {
            passData.Material = _material;
            passData.SourceTextureHandle = sourceTextureHandle;
            
            builder.UseTexture(sourceTextureHandle);
            builder.SetRenderAttachment(targetTextureHandle, 0);
            
            builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
            {
                Blitter.BlitTexture(context.cmd, data.SourceTextureHandle, Vector2.one, data.Material, 0);
            });
        }
        
        renderGraph.AddBlitPass(targetTextureHandle, sourceTextureHandle, Vector2.one, Vector2.zero, passName: "BlitNegativeTextureToCameraColor");
    }
}
BlitColorRendererFeature03.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

public class BlitColorRendererFeature03 : ScriptableRendererFeature
{
    private BlitColorPass03 _pass;

    public override void Create()
    {
        _pass = new BlitColorPass03();
    }
    
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        _pass.Setup();
        renderer.EnqueuePass(_pass);
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _pass.Cleanup();
        }
    }
}

public class BlitColorPass03 : ScriptableRenderPass
{
    private const string PASS_NAME = nameof(BlitColorPass03);
    private const string SHADER_PATH = "BlitWithMaterial";
    private readonly Material _material;

    public BlitColorPass03()
    {
        renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
        _material = CoreUtils.CreateEngineMaterial(SHADER_PATH);
    }
    
    public void Setup()
    {
        // 中間テクスチャを要求
        requiresIntermediateTexture = true;
    }

    public void Cleanup()
    {
        CoreUtils.Destroy(_material);
    }
    
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        if (_material == null) return;
        
        UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

        // renderPassEventがAfterRenderingの場合、BackBufferがアクティブになっている可能性があるので弾く
        if (resourceData.isActiveTargetBackBuffer) return;

        TextureHandle sourceTextureHandle = resourceData.activeColorTexture;
        TextureDesc targetDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
        targetDescriptor.name = PASS_NAME;
        targetDescriptor.clearBuffer = false;
        targetDescriptor.msaaSamples = MSAASamples.None;
        targetDescriptor.depthBufferBits = DepthBits.None;
        TextureHandle targetTextureHandle = renderGraph.CreateTexture(targetDescriptor);

        RenderGraphUtils.BlitMaterialParameters para = new(sourceTextureHandle, targetTextureHandle, _material, 0);
        renderGraph.AddBlitPass(para, passName: PASS_NAME);
        resourceData.cameraColor = targetTextureHandle;
    }
}

BlitWithMaterial.shader
// https://github.com/Unity-Technologies/Graphics/blob/master/Packages/com.unity.render-pipelines.universal/Samples~/URPRenderGraphSamples/BlitWithMaterial/BlitWithMaterial.shader
Shader "BlitWithMaterial"
{
   SubShader
   {
       Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
       ZWrite Off Cull Off
       Pass
       {
           Name "BlitWithMaterialPass"

           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

           // Out frag function takes as input a struct that contains the screen space coordinate we are going to use to sample our texture. It also writes to SV_Target0, this has to match the index set in the UseTextureFragment(sourceTexture, 0, …) we defined in our render pass script.   
           float4 Frag(Varyings input) : SV_Target0
           {
               // this is needed so we account XR platform differences in how they handle texture arrays
               UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

               // sample the texture using the SAMPLE_TEXTURE2D_X_LOD
               float2 uv = input.texcoord.xy;
               half4 color = SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearRepeat, uv, _BlitMipLevel);
               
               // Modify the sampled color
               return half4(0, 1, 0, 1) * color;
           }

           ENDHLSL
       }
   }
}

主なコード差分ポイントとしては
BlitColorPass01:RasterPassを使ってカメラカラーと差し替えることでBlitのし直しを回避

using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(PASS_NAME, out PassData passData))
...
resourceData.cameraColor = targetTextureHandle;

BlitColorPass02:RasterPass使ってBlitで元のカメラカラーへ書き戻し

using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(PASS_NAME, out PassData passData))
...
renderGraph.AddBlitPass(targetTextureHandle, sourceTextureHandle, Vector2.one, Vector2.zero, passName: "BlitNegativeTextureToCameraColor");

BlitColorPass03:中間テクスチャと専用BlitPassを使ってカメラカラーと差し替えることでBlitのし直しを回避

requiresIntermediateTexture = true;
...
RenderGraphUtils.BlitMaterialParameters para = new(sourceTextureHandle, targetTextureHandle, _material, 0);
renderGraph.AddBlitPass(para, passName: PASS_NAME);
resourceData.cameraColor = targetTextureHandle;

それぞれのパス状況をRender Graph ViewerとFrame Debuggerで確認してみます。
見方

render_graph_icon.png
参考元
https://www.docswell.com/s/UnityJapan/ZQ8GGV-2024-07-11-155334#p53

BlitColorPass01

2025-12-21_22h45_43.png
2025-12-21_22h44_22.png

BlitColorPass02

2025-12-21_22h45_48.png
2025-12-21_22h44_27.png

BlitColorPass03

2025-12-21_22h45_32.png
2025-12-21_22h44_32.png

まだ理解が追いついていないところもありますが、使い分けとしては以下の感じでしょうか。実際のパフォーマンスはNsight Graphicsを使っての検証が必要そうです。

比較コード 特徴や用途 パフォーマンス
BlitColorRendererFeature01 RasterPassを使った標準的な実装
BlitColorRendererFeature02 元ソースを保持しつつ複数のエフェクトを適用
BlitColorRendererFeature03 最適化BlitでCPU/GPUのオーバヘッドを削減

Unity公式ドキュメントで気になった記述

RenderGraphについて調査をしていたところ、Unity公式ドキュメントで気になった記述を見かけましたのでメモ書き程度に残しておきます。

UniversalRenderer.CreateRenderGraphTextureはバグがあるから使用は避けた方が良いらしい

テンポラリテクスチャを作るときはUniversalRenderer.CreateRenderGraphTextureよりもrenderGraph.CreateTextureが良いのかもしれないですね。
スクリーンショット 2025-12-21 235211.png

Note: Avoid using UniversalRenderer.CreateRenderGraphTexture, because it can introduce subtle bugs.

https://docs.unity3d.com/6000.3/Documentation/Manual/urp/render-graph-create-a-texture.html#:~:text=Note%3A%20Avoid%20using%20UniversalRenderer.CreateRenderGraphTexture%2C%20because%20it%20can%20introduce%20subtle%20bugs.

グローバルテクスチャを使うとレンダリングが遅くなる?

「とりあえずグローバル」ではなく、TextureReference等を使ったリソース管理の設計が大事なのかもしれません。パスマージングも関連していたりするのでしょうか。
スクリーンショット 2025-12-21 235532.png

Setting a texture as a global texture can make rendering slower.

https://docs.unity3d.com/6000.3/Documentation/Manual/urp/render-graph-create-global-texture.html#:~:text=Setting a texture as a global texture can make rendering slower.

おわりに

機能ごとの使い分けには理解と慣れが必要そうであるものの、RenderGraph導入前と比較するとOnCameraSetupやConfigureなどのAPIが簡略化されすっきりしたように感じます。

また、リソース管理が最適化されて扱いやすくなっている点も非常に良さそうですので、積極的に利用していきたいところです。この記事が少しでもRenderGraphの導入や理解の手助けになれば幸いです。🌱

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?