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

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の活用方法」が一番分かりやすいでしょう。
講演資料
さらにサンプルコードも公開されています。(ロビンさんありがとう!🎁)
学び方としては
- 講演動画を視聴して概要を把握(この時点では話の内容があまりわからなくてもOK)
- サンプルコードをクローン
- サンプルコードを写経、サンドボックス的に改変して触ってみる
- 再び動画を見返す(なんとなくわかってくる)
- 3に戻る(再びサンプルコードで遊ぶ)
を繰り返していくのが近道です。
途中で他の資料も読むことで補足の強化や別視点からの理解の助けになります。おすすめなのが、おそらく日本語資料では一番充実しているサイバーエージェント社のCORETECH ENGINEER BLOGです。
タイトルの下にタグが設けられていますので「RenderGraph」をクリックするとRenderGraphに関連する記事を確認することができます。

ロビンさんのサンプルコードやCORETECH ENGINEER BLOGがある程度理解できたら、次にUnityのPackage ManagerからURP RenderGraph Samplesをインポートしてサンプルコードを眺めるのが良いでしょう。
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空間でレンダリング |
それぞのフォルダにはシーンとREADMEが用意されています。シーンを開くとすぐに確認できるのでお手軽です。詳しい内容と使用しているRendererFeature/PassはREADMEに記載されています。
プロジェクトの構成としてはURPアセット(Universal Render Pipeline Asset)にRenderGraphで記述された各RendererFeatureが登録されており、各シーン内のメインカメラに使用するRendererを指定する方法が取られていました。
URP Packageのサンプルコード
| 項目 | 内容 |
|---|---|
| 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 |
処理としては画面全体に指定した色を乗算する簡単な処理です。
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;
}
}
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");
}
}
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;
}
}
// 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で確認してみます。
見方

参考元
https://www.docswell.com/s/UnityJapan/ZQ8GGV-2024-07-11-155334#p53
BlitColorPass01
BlitColorPass02
BlitColorPass03
まだ理解が追いついていないところもありますが、使い分けとしては以下の感じでしょうか。実際のパフォーマンスはNsight Graphicsを使っての検証が必要そうです。
| 比較コード | 特徴や用途 | パフォーマンス |
|---|---|---|
| BlitColorRendererFeature01 | RasterPassを使った標準的な実装 | 中 |
| BlitColorRendererFeature02 | 元ソースを保持しつつ複数のエフェクトを適用 | 低 |
| BlitColorRendererFeature03 | 最適化BlitでCPU/GPUのオーバヘッドを削減 | 高 |
Unity公式ドキュメントで気になった記述
RenderGraphについて調査をしていたところ、Unity公式ドキュメントで気になった記述を見かけましたのでメモ書き程度に残しておきます。
UniversalRenderer.CreateRenderGraphTextureはバグがあるから使用は避けた方が良いらしい
テンポラリテクスチャを作るときはUniversalRenderer.CreateRenderGraphTextureよりもrenderGraph.CreateTextureが良いのかもしれないですね。

Note: Avoid using UniversalRenderer.CreateRenderGraphTexture, because it can introduce subtle bugs.
グローバルテクスチャを使うとレンダリングが遅くなる?
「とりあえずグローバル」ではなく、TextureReference等を使ったリソース管理の設計が大事なのかもしれません。パスマージングも関連していたりするのでしょうか。

Setting a texture as a global texture can make rendering slower.
おわりに
機能ごとの使い分けには理解と慣れが必要そうであるものの、RenderGraph導入前と比較するとOnCameraSetupやConfigureなどのAPIが簡略化されすっきりしたように感じます。
また、リソース管理が最適化されて扱いやすくなっている点も非常に良さそうですので、積極的に利用していきたいところです。この記事が少しでもRenderGraphの導入や理解の手助けになれば幸いです。🌱











