こんにちは、Unityエンジニアのオオバです。
Unityの RenderGraph をご存知でしょうか?
Unity6になってURP(Universal Render Pipeleine)がRenderGraphというシステムを導入し新しくなりました。その影響によりAPIが大きく変わり、 URPの拡張方法にも変更 が入りました。
そこで本記事では RenderGraph版URPの拡張方法を紹介 します。今回は始めの一歩ということで最小コードで実装しています。この拡張方法を参考に、より複雑で自分のやりたい表現を実現してもらえればと思います。
ちなみに本記事は過去に執筆した旧バージョンのURP拡張記事のリライト版という位置づけです。
記事後半では具体的なURP拡張としてダウンサンプリングDEMOを作ってみるのでお楽しみに。
では早速紹介していきます。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
- そもそもRenderGraphとは?
- 改めてURPの基本構成を整理
- RenderGraph版ダウンサンプリングするURP拡張を作ってみよう
- 1.ScriptableRenderPassの作成
- 2.Pass実行に必要なデータクラスの作成
- 3.RecordRenderGraphのオーバーライド
- 4.ダウンサンプリングするための準備とRenderTextureの作成
- 5.RenderTextureにカメラ画像の書き込み
- 6.画面にRenderTextureを描画
- 7.パス内の処理を作成
- 8.ScriptableRendererFeatureの作成
- 9.UniversalRendererDataにScriptableRendererFeatureの登録
- まとめ
そもそもRenderGraphとは?
具体的なURP拡張の前に RenderGraph について理解しておく必要があります。そもそもRenderGraphとはUnityが提供するレンダリングシステムの1つで以前から存在はしていました。
URPのコアとなるCore RP Library v10.2からマニュアルに登場していましたが、URPに導入されてはいませんでした。
Unity6以降のURPではRenderGraphを使用するようになったということです。
RenderGraphはレンダリングパイプラインで使用するリソースを効率的に 自動管理 するシステムで、無駄なリソースを減らし 描画処理を最適化 することで パフォーマンス向上 を図っています。
URP 17へのアップグレードとRenderGraphの活用方法より↑RenderGraph初心者の方は一度こちらのU/Day動画を見ておくことをおすすめします。
RenderGraph導入で何が変わる?
RenderGraphによって描画のパフォーマンスが上がるのはわかったけど、旧システムから何が変わるのでしょうか?
結論から言うと めちゃくちゃ変わります。
- 自分でRenderTextureを作らない
- 自分でRenderTextureの管理をしない
- ScriptableRenderPassがRecordRenderGraphだけにシンプル化
- RenderGraph以前のシステムは動かなくなる
上記でリストアップした変化があるわけですが、特にRenderTextureを自分で管理する必要がなくなります。これは非常に嬉しいですね。解放漏れなどを心配する必要がなくなるからです。
今まで Execute
や OnCameraSetup
といったScriptableRenderPassのオーバーライド関数が大量にありましたが、 RecordRenderGraph 関数一本に変更になりました。その他の関数はObsoleteとなり近い将来削除されます。
つまりRenderGraph以前にゴリゴリURPの拡張をしていた機能は、ほぼ全て Unity6では動かなくなる ということです 😇
ただしRenderGraphは 今後URPを使う上で外せない機能 なので、ぜひ本記事を通して基礎力を身に着けておきましょう。
改めてURPの基本構成を整理
URPは複数のファイルが登場するため混乱しやすいです。しかしURPを拡張する上でファイル同士の関係性を知っておくことは非常に重要です。
そこで具体的なURP拡張をする前に、 URPの構成について整理 しておきましょう。これはURP初心者向けの内容です。
RenderGraphに変わることでURPのAPIは大きく変わりますが、基本構成に変化はありません。図にするとこんなイメージです。
登場人物は以下の4人です。
- UniversalRenderPipelineAsset
- UniversalRendererData
- ScriptableRendererFeature
- ScriptableRenderPass
UniversalRenderPipelineAsset
UniversalRenderPipelineAsset が大元のScriptableObjectです。
この UniversalRenderPipelineAsset が
Project Settings > Graphics > Default Render Pipeline
にセットされます。 Default Render Pipeline
に UniversalRenderPipelineAsset がセットされることで URPが動作する ようになります。
UniversalRendererData
UniversalRenderPipelineAsset > RendererList
に UniversalRendererData が格納されます。変数名の通りRendererListは複数の UniversalRendererData を格納可能です。 ScriptableRendererFeature
UniversalRendererData の中に本題であるURPの拡張 ScriptableRendererFeature をセットします。ここでは拡張名として「DownSamplingRenderFeature」という名前になっています。
ScriptableRenderPass
ScriptableRendererFeature の中で実際の描画処理を記述した ScriptableRenderPass を生成します。
繰り返しになりますが、 RenderGraphになったからといって基本的な構成に変化はありません。 (旧システムから呼び名は変わっているものもあります)
URPのファイル構成を理解したうえで、ここから具体的なURPの拡張方法について解説していきます。
RenderGraph版ダウンサンプリングするURP拡張を作ってみよう
ダウンサンプリングを例にRenderGraph版のURP拡張方法を解説していきます。
作業内容を細かく刻んだため全体では9ステップと多いですが、1つずつ確認していきましょう。
①ScriptableRenderPassの作成
②Pass実行に必要なデータクラスの作成
③RecordRenderGraphのオーバーライド
④ダウンサンプリングするための準備とRenderTexture作成
⑤RenderTextureにカメラ画像の書き込み
⑥画面にRenderTextureを描画
⑦パス内の処理を作成
⑧ScriptableRendererFeatureの作成
⑨UniversalRendererDataにScriptableRendererFeatureの登録
1.ScriptableRenderPassの作成
まず最初にやるべきことはScriptableRenderPassの作成です。
public class DownSamplingRenderPass : ScriptableRenderPass
{
}
ScriptableRenderPass
クラスを継承してPassクラスを作成します。ここでは 「DownSamplingRenderPass」というクラス名にしています。
2.Pass実行に必要なデータクラスの作成
パスを実行(ダウンサンプリング処理)するために必要なパラメータを渡すためのクラス(PassData)を作成しておきます。
public class DownSamplingRenderPass : ScriptableRenderPass
{
// パスを実行(ダウンサンプリング処理)するために必要なパラメータを渡すためのクラス
private class PassData
{
public TextureHandle srcTextureHandle;
}
}
PassDataクラスはパス内でしか利用しないためPrivateクラスでOKです。
3.RecordRenderGraphのオーバーライド
次に RecordRenderGraph関数をオーバーライド します。本関数内に描画処理のすべてを記述していきます。
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
// パスの処理を記述るする
}
旧URPでは複数存在していたオーバーライド関数ですが、RenderGraphになって RecordRenderGraph
関数だけとなりました。
4.ダウンサンプリングするための準備とRenderTextureの作成
今回作成するダウンサンプリングの例では、低解像度のRenderTextureにカメラ画像を書き込み、その後引き伸ばして画面に描画するという仕組みです。
↑図にするとこんな感じです。
処理としては次の4ステップです。
- 現在カメラが映しているテクスチャの取得
- カメラデータの取得
- 低解像度の決定
- 解像度を下げた一時利用のRenderTextureを作成
↓コードにするとこんな感じです。
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
// 1.現在カメラが映しているテクスチャの取得
var resourceData = frameData.Get<UniversalResourceData>();
var cameraColorTextureHandle = resourceData.activeColorTexture;
// 2.カメラデータの取得
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
// 3.低解像度の決定
int downSample = 100;
int w = cameraData.scaledWidth / downSample;
int h = cameraData.scaledHeight / downSample;
// 4.像度を下げた一時利用のRenderTextureの作成
var tempRT = UniversalRenderer.CreateRenderGraphTexture(
renderGraph,
new RenderTextureDescriptor(w, h), "_TempRT", true);
}
該当する処理にコメントアウトをしているため確認してみてください。
5.RenderTextureにカメラ画像の書き込み
次にやるべきことは一時的に作成した低解像度RenderTextureへの書き込みです。
- RenderGraphに対してパスを追加
- カメラのテクスチャは読み取り設定
- 一時的なRenderTextureは書き込み設定
- Pass実行に必要なデータをセット
- パス実行の依頼
↓コードに落とし込むとこんな感じです。
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
~~~~~~~~~~~~~~~~ 略 ~~~~~~~~~~~~~~~~
// 1. RenderGraphに対してパスを追加
using (var builder = renderGraph.AddRasterRenderPass(passName, out PassData passData))
{
// 2.カメラのテクスチャは読み取り設定
builder.UseTexture(cameraColorTextureHandle, AccessFlags.Read);
// 3.一時的なRenderTextureは書き込み設定
builder.SetRenderAttachment(tempRT, 0, AccessFlags.Write);
// 4.Pass実行に必要なデータをセット
passData.srcTextureHandle = cameraColorTextureHandle;
// 5.パス実行の依頼
builder.SetRenderFunc(
(PassData data, RasterGraphContext context) => ExecutePass(data, context)
);
}
}
この状態ではまだ低解像度のRenderTextureにカメラ画像を書き出しただけで画面内に表示されていません。
※ ExecutePass
メソッドは後で紹介します
6.画面にRenderTextureを描画
低解像度RenderTextureに書き込んだ画像を画面内に映し出す処理を追加します。
- RenderGraphに対してパスを追加
- 一時的なRenderTextureは読み取り設定
- カメラのテクスチャは書き込み設定
- Pass実行に必要なデータをセット
- パス実行の依頼
コードにするとこんな感じです。
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
~~~~~~~~~~~~~~~~ 略 ~~~~~~~~~~~~~~~~
// 1. RenderGraphに対してパスを追加
using (var builder = renderGraph.AddRasterRenderPass(passName, out PassData passData))
{
// 2.一時的なRenderTextureは読み取り設定
builder.UseTexture(tempRT, AccessFlags.Read);
// 3.カメラのテクスチャは書き込み設定
builder.SetRenderAttachment(cameraColorTextureHandle, 0, AccessFlags.Write);
// 4.Pass実行に必要なデータをセット
passData.srcTextureHandle = tempRT;
// 5.パス実行の依頼
builder.SetRenderFunc(
(PassData data, RasterGraphContext context) => ExecutePass(data, context)
);
}
}
7.パス内の処理を作成
最後にパス内の処理(ExecutePassメソッド)を実装します。パス内の処理はソース画像を書き込み対象にコピー(Blit)するだけです。次のコードで実現できます。
private static void ExecutePass(PassData passData, RasterGraphContext graphContext)
{
RasterCommandBuffer cmd = graphContext.cmd;
Blitter.BlitTexture(cmd, passData.srcTextureHandle, new Vector4(1, 1, 0, 0), 0, false);
}
以上でScriptableRenderPassの実装は完了です。
8.ScriptableRendererFeatureの作成
最後に作成したパスをRendererにわたすためのScriptableRendererFeatureを作成します。行数が少ないので一気にScriptableRendererFeatureのコードを一気に紹介します。
using UnityEngine.Rendering.Universal;
public class DownSamplingRenderFeature : ScriptableRendererFeature
{
DownSamplingRenderPass _renderPass;
public override void Create()
{
// パスの作成
_renderPass = new DownSamplingRenderPass
{
// 処理のタイミング(透明オブジェクト後に実行)
renderPassEvent = RenderPassEvent.AfterRenderingTransparents
};
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
// パスの登録
renderer.EnqueuePass(_renderPass);
}
}
9.UniversalRendererDataにScriptableRendererFeatureの登録
最後にUniversalRendererDataへ作成したScriptableRendererFeatureを登録して完了です。
Add Renderer Feature
ボタンをクリックして、作成したScriptableRendererFeatureを追加しましょう。 このように今回作成した
DownSamplingRenderFeature
を追加します。 追加した瞬間にモザイク状態になったかと思います。成功です。
このような手順でRenderGraph版のURPを拡張してもらえればと思います。
個人的にはRenderTextureを自前で管理しなくてよいのが非常に嬉しいです。慣れたらRenderGraph版のURP拡張の方がしやすい印象でした。
まとめ
今回は RenderGraph版のURP拡張超基本編 ということでダウンサンプリングを作ってみました。最後に記事の内容を簡単にまとめます。
①RenderGraphになってAPIは大幅変更
②無駄なデータが削減されてパフォーマンスアップ
③自前でRenderTextureの管理が不要で作りやすい
④オーバーライド関数はRecordRenderGraphのみ
⑤慣れるとRenderGraph版の方が拡張しやすそう
こんな感じです。
Unity6になってURPがRenderGraphに対応しました。旧来の書き方とは大きく変わってしまいましたが、基本構成は同じです。
URPはRenderGraphがベースになるためこのタイミングでぜひ覚えておいた方がよさそうですね。本記事はある程度URPのことを知っている人向けに執筆しちゃっているため、いきなりすべてを理解するのは難しいかもしれません。
何度も読んで、自分で手を動かして試してみてもらえたらと思います。
全体コードはGistにアップロードしました。
参考になった方はぜひ右上のスターボタンを押しておいてください⭐️
→ ダウンロードはこちら
この記事が気に入ったらフォローしよう
「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!
最後まで読んでいただきありがとうございました!
すばらしいURP拡張ライフをお過ごしください。
- Unity6000.0.32f1
- URP v17.0.3