こんにちは、Unityエンジニアのオオバです。
結論から話すと Universal Render Pipeline(以下:URP) は描画処理(パス)を簡単に追加することができます。
URPの拡張をこれからやっていきたい人に向けてわかりやすく紹介していきたいと思います。URPの拡張と聞くと「シェーダー」が出てきそうな予感がしますが安心してください。 今回はシェーダーは登場しません。 あくまでURPを拡張する基本的な方法を解説していきます。
ちなみに今回作るものはコチラです。
解像度を徐々に下げていくモザイクのような表現を作っていきます。
この表現をシェーダーを使うことなく実装してきます。
最小コードでURPの描画拡張を作っています。 URP拡張を始めたことがない人にとって有益な記事となるため、ぜひ最後まで読んでみてください。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
【URP拡張の準備】PassとFeatureの用意
まずURPを拡張する上で2つのオブジェクトを用意します。PassとFeatureです。
具体的には「ScriptableRenderPassクラス」と「ScriptableRendererFeatureクラス」を使います。
「ScriptableRenderPass」 は実際の描画処理を実装したクラスです。一方 「ScriptableRendererFeature」 はURPに追加する機能の単位でありPassの生成する役割を担っています。現時点ではよくわからないかもしれません。詳しく後述しますので安心してください。
URP拡張の準備としてこれらのクラスを継承した2つのクラスを用意します。
ScriptableRenderPassクラスを継承した、 DownSamplingRenderPassクラス 。ScriptableRendererFeatureクラスを継承した DownSamplingRenderFeatureクラス を用意しました。
以降の章で詳しく解説していきます。
手順①Passの実装
最初にPass(パス)の実装をしていきます。Passには2つの役割があります。1つは 「描画処理の実装」 。もう1つはその 「描画処理の実行タイミングの定義」 です。
Passの全体像を確認するために処理を割愛したソースコードを確認してみます。
💻ソースコード : DownSamplingRenderPass.cs抜粋
public class DownSamplingRenderPass : ScriptableRenderPass
{
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
// ①描画処理を記述
}
/// <summary>Constructor</summary>
public DownSamplingRenderPass() =>
// ②描画タイミングを決めるところ
renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
}
ソースコード内のコメントアウトの通り、Executeメソッド内で描画処理を、renderPassEventに描画タイミングを設定するのです。
では次の章で、今回作成するモザイクの描画処理を実装していきます。
モザイクの描画処理を実装する
先の解説の通り、ScriptableRenderPass内の Executeメソッド に描画処理を記述していきます。最初に処理のイメージを図にしてみました。
処理の手順は以下です。
- 小さいサイズのRenderTextureを生成
- カメラ画像をRenderTextureにコピー
- RenderTextureを元のカメラのサイズに引き伸ばしてコピー
これらの処理を実装したソースコードがこちらです。ソースコード内にコメントアウトで解説しているため、参考にしてください。
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
// 描画処理を記述
// CommandBuffer作成(描画コマンドです)
var commandBuffer = CommandBufferPool.Get(CommandBufferName);
var cameraData = renderingData.cameraData;
// 現在描画しているカメラの解像度を 「_downSample」で除算
var w = cameraData.camera.scaledPixelWidth / _downSample;
var h = cameraData.camera.scaledPixelHeight / _downSample;
// 小さいサイズのRenderTextureを生成
commandBuffer.GetTemporaryRT(RenderTextureId,
w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
// 現在のカメラ画像をRenderTextureにコピー
commandBuffer.Blit(_currentTarget, RenderTextureId);
// RenderTextureを現在のRenderTarget(カメラ)サイズに引き伸ばしてコピー(モニタに写し出される)
commandBuffer.Blit(RenderTextureId, _currentTarget);
}
表示解像度を小さくしたあと元のサイズに引き伸ばしているためモザイクされた状態に見えるという仕組みです。だから今回の場合はシェーダーは不要なのです。
CommandBufferでさまざまな処理をさせる
ソースコード内に登場した「CommandBuffer」 は描画コマンドの単位です。描画処理を作っていく上で非常に重要な存在です。
CommandBufferに処理を詰め込みGPUにわたすことで描画処理が実行されます。先のソースコードでは以下の2つの処理をCommandBufferに行っています。
- GetTemporaryRTメソッド : 一時利用のRenderTextureの生成
- Blitメソッド : RenderTextureの内容を転送(コピー)
CommandBufferには他にもたくさんの処理が定義されていますので、ぜひチェックしてみてください。
Passの実行タイミングを決める
次にこのモザイク描画処理を実行するタイミングを決めます。このタイミングはとても重要です。
先のソースコード内に 「RenderPassEvent」 が登場しました。描画処理のタイミングはPassのコンストラクタで設定します。
public DownSamplingRenderPass() =>
renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
「RenderPassEvent」とはEnumです。RenderPassEventにはさまざまな描画タイミングが定義されており、変数「renderPassEvent」に指定することでPassの実行タイミングを設定できます。
今回は 「AfterRenderingTransparents」 を指定しました。 「透明オブジェクトを描画した後に実行する」 という処理になります。
以上でPassの実装は完了です。
手順②URP拡張の最小単位「Feature」の実装
次にFeatureを実装していきます。最初にFeatureの2つの役割について紹介します。
2つの役割とは ①Passの生成 と ②生成したPassをScriptableRendererに渡すこと です。新たに「ScriptableRenderer」というキーワードが登場しました。描画処理を記述したScriptableRenderPassをScriptableRendererにわたすことで初めて描画処理が走ります。
FeatureはScriptableRendererとの橋渡しを担っているのです。 ではFeatureの2つの役割を1つずつ確認していきます。
①Passの生成
PassはScriptableRenderFeatureの「Createメソッド」をオーバーライドしてPassを生成します。
DownSamplingRenderPass _renderPass;
public override void Create() =>
// Passの作成
_renderPass = new DownSamplingRenderPass();
作ったパスは後に実行するメソッド内で使用するためインスタンス変数の中に保持しておきましょう。
②生成したPassをScriptableRendererに渡す
次に生成したPassをScriptableRendererに渡します。
public override void AddRenderPasses(
ScriptableRenderer renderer, ref RenderingData renderingData)
{
// ScriptableRendererにPassを渡す
renderer.EnqueuePass(_renderPass);
}
ScriptableRenderFeatureの「AddRenderPassesメソッド」をオーバーライドしてPassをScriptableRendererに渡します。
以上の2つの役割を実装したソースコードは以下です。
💻ソースコード : DownSamplingRenderFeature.cs抜粋
public class DownSamplingRenderFeature : ScriptableRendererFeature
{
DownSamplingRenderPass _renderPass;
public override void Create() =>
// Passの作成
_renderPass = new DownSamplingRenderPass();
public override void AddRenderPasses(
ScriptableRenderer renderer, ref RenderingData renderingData)
{
// ScriptableRendererにPassを渡す
renderer.EnqueuePass(_renderPass);
}
}
つまりFeatureとPassの関係は以下の図で表せます。
Featureの中でPassを生成し、ScriptableRendererにPassを渡します。今回パスは1つしか扱いませんが、 Featureは複数のパスを管理することができます。
FeatureをRendererに登録
作成したFeatureを動かすためにRendererに登録する必要があります。
URPのForwardRendererにFeatureを追加していきます。
Add Renderer Feature をクリックして作成したDown Sampling Render Feature
を追加します。
するとFeatureはRendererに適用されます。
以上で作成したPass、Featureが共に描画されるようになりました。
Passの描画(Execute)前にパラメータを渡す
今回はPass側に 「SetParamメソッド」を定義して描画前にFeatureからパラメータを渡しように実装しています。
💻ソースコード : DownSamplingRenderFeature.csの抜粋
public override void AddRenderPasses(
ScriptableRenderer renderer, ref RenderingData renderingData)
{
// 描画処理前にパラメータをPassに渡す
_renderPass.SetParam(renderer.cameraColorTarget, downSample);
renderer.EnqueuePass(_renderPass);
}
具体的に描画前のパスに渡しているのは次の2つ「カメラ情報」と「ダウンサンプル値」です。
カメラ情報とはカメラが写している画像情報 です。カメラの解像度はExecuteメソッドの引数RenderingDataから取得可能です。「ダウンサンプル値」とはモザイク表現するための値でこの値を毎フレーム変化させることでモザイクアニメーションを表現しています。
「描画前のPassに対してFeatureからパラメータを渡す」 手順は今後も出てくると思うので覚えておきましょう。
手順③モザイクの大きさをアニメーションさせる
Featureとはは「ScriptableObject」なので、事前にシリアライズすることが可能です。つま りコンポーネントにFeatureをシリアライズしてUnity実行中にパラメータを渡すことができる のです。
コンポーネント(Script)からFeature経由でPassにパラメータを渡す流れを以下の図にまとめました。
以下のコードにように更新したパラメータをFeatureに渡すことでモザイクアニメーションを表現できます。
public class DownSampleAnimation : MonoBehaviour
{
// Featureの参照を保持
[SerializeField] private DownSamplingRenderFeature _feature;
void Update()
{
var tmp = (Mathf.Sin(Time.frameCount * Mathf.PI / 180f) + 1f) / 2f;
// ダウンサンプリング最小1/120のサイズまで適用
var value = tmp * 120f;
// Featureのパラメータを更新
_feature.downSample = (int)value;
}
}
するとこのようなこのようなアニメーションが作成できます。
ゲームの状態によるパラメータ調整ってよくありますよね。ダメージを受けたら画面を赤くするとか。
そういう処理もURPの拡張を使えばわかりやすく実装できそうです。
FrameDebuggerで描画タイミングの確認とデバッグ方法
最後に描画処理のデバッグ、確認方法の紹介です。
メニューWindow > Analysis > Frame Debugger
からFrame Debuggerウィンドウを表示してみましょうFrame Debuggerウィンドウを使うと各描画処理のタイミングを確認できる超便利ツールです。
FrameDebuggerの Enableボタン をクリック。
すると「DownSamplingRenderPass」がFrame Debuggerに表示されます。このFrame Debuggerに表示される名前はCommandBufferを生成したときの名前になります。
var commandBuffer = CommandBufferPool.Get("コマンドバッファ名");
DrawTransparentObjects(透明オブジェクトの描画)の後に実行されていることが分かります。意図通りですね。
Frame Debuggerを使うことで、意図した処理になっているのか確認できるためとても便利です。Frame DebuggerはUnityEditorだけではなく実機につないでテストすることもできるためとてもおすすめのツールです。
まとめ
URPの拡張を最小コードで紹介してきました。
①Feature、Passの作成
②Featureの中でPassを生成
③描画前にFeatureからPassにパラメータを渡す
④PassのExecuteに描画処理を書く
⑤FeatureをRendererに登録
URP拡張は5つのステップで大まかな描画の流れは実装できます。そして「Frame Debugger」を使うことで意図したタイミングで処理が走っているか確認可能です。
今後Unityでものづくりするときはビルトインパイプラインではなく、URPに置き換わる未来が来るかもしれません。パフォーマンス的には完全にURPの勝利です。ぜひ、今のうちからURPのキャッチアップをしてゲームのクオリティを上げていきましょう。
こちらの記事ではのシェーダーを使ってURPの拡張をしていますのであわせて確認してみてください。
【超基本編】URPの拡張にシェーダーを使う方法
全ソースコードはこちら
最後にサンプル全ソースを公開しておきます。
サンプルも内包されて分かりづらいですが「Assets/DownSampling」フォルダに今回のサンプルが一式格納しています。
何か参考になれば幸いです。
GitHub - baobao/URP-DownSamplingSample
この記事があなたのゲーム開発に少しでもお役に立てたら嬉しいです。
この記事が気に入ったらフォローしよう
「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!
最後まで読んでいただきありがとうございました!
すばらしいURP拡張ライフをお過ごしください。
- Unity2021.16.f1
- URP v11.0.0