Unity2022 URP14 呟き独歩 その2

2023/12/18に公開

その2で触れる内容

  • RTHandleSystemについて
  • RTHandleの使用方法

この記事の趣旨

パイプラインが大きく変化し、URP14は既存の描画処理から大きな変更が加わっているという。自分はURPの知見がURP10で止まっているため、そろそろ重い腰を上げて学ばないとな…と思う。

想定する対象読者

  • URPを気分で使用してきた人
  • 2,3年ほど昔のURPを触ってきた人

開発環境

  • Unity2022.3.15(現時点 2023/12/15のUnity2022最新バージョン)
  • URP14.0.9

その1はこちら

https://zenn.dev/inpro/articles/70f8fad0df8c08

その2

RTHandle System

RTHandleを操作するには、RTHandleSystemを用いる。RTHandleSystemクラスのインスタンスを生成し、RTHandleSystemからAPIをたたく。RTHandleSystemクラスには、RTHandleの割り当て、解放、Reference Size設定等の全てのAPIが含まれている。

RTHandleSystem m_RTHandleSystem = new RTHandleSystem();
m_RTHandleSystem.Initialize(Screen.width, Screen.height);

初期化時、最初に解像度を設定する必要があるようだ。Unityのおすすめはメインディスプレイの解像度とのこと。前提としてこれ以上のサイズのRenderTextureの生成が必要になり、再割り当てが発生するのを防ぐため。

RTHandleSystemはRTHandlesというStaticクラスを使用して初期化することもできる。

Initialize関数内では動的解像度の使用可否設定もおこなわれている。

初期化後、カメラでレンダリングする前にReference Sizeを決定する必要がある。SetReferenceSize関数を用いて、Reference Sizeを決定する。上記Initialize関数ではSetReferenceSizeは実行されていない。恐らくReference Sizeを使用しない場合を考慮しているのだと思う。

RTHandles.SetReferenceSize(width, height);

RTHandleのアロケート

RTHandleをアロケート(割り当て)するには、RTHandles.Alloc を用いる。RTHandles.Alloc関数を見てみると、様々な方法で割り当てることができる。以下は引数の例。

  • Vector2 scaleFactor : 最大解像度との比率。フルスクリーンの場合(1.0f, 1.0f)
  • ScaleFunc scaleFunc : scaleを使用したくない場合のテクスチャサイズ計算関数を内包したクラス。
  • int width, int height : テクスチャサイズ。通常のRenderTexture割り当て方法と同様。
  • RenderTexture : RenderTexture。RTHandleが参照するRTが直接指定したRTになる。
  • RenderTargetIdentifier : RenderTargetIdentifier。RenderTexture同様。

下2つを見てみると、既存の扱い方(RenderTexture、RenderTargetIdentifier)を引数に含むことができることから、既存のRTもRTHandleを使用して扱うことが可能のようだ。

RTHandleを使用するまでの流れは以下の通りになる。

  • RTHandleSystemの初期化
  • (Reference Sizeを使用する場合)Reference Sizeの設定
  • RTHandleのアロケート

RTHandleの使用

After you allocate an RTHandle, you can use it exactly like a regular RenderTexture. There are implicit conversions to RenderTargetIdentifier and RenderTexture, which means you can use them with regular related Unity APIs.
(RTHandle を割り当てた後は、通常の RenderTexture とまったく同じように使用できます。RenderTargetIdentifier と RenderTexture への暗黙的な変換があります。つまり、これらを通常の関連する Unity API で使用できることになります。)

とのこと。ちょっと一安心。既存の実装でもRTHandleへの移行時に完全に作り直す必要はなさそうだ。ただ、何点か注意する必要がある。注意項目を以下にまとめる。

  • 解像度の異なるカメラが2つ以上存在する場合
  • Shader上のUV値
  • DoubleBufferingが必要な場合

解像度の異なるカメラが2つ以上存在する場合

例:

メインカメラ:1920x1080

セカンドカメラ:512x512

と設定しているプロジェクトがあるとする。この場合、全てのRTHandleの解像度は一番大きい解像度である1920x1080となる。

毎回最大解像度からのReference Sizeを考える必要があるかというと、そうではない。Unity側でRTHandleを使用する際に解像度基準を変更してくれるAPIがCoreUtilsに存在する。セカンドカメラの解像度を基準としてRTHandleをアクティブなRenderTargetとして扱う場合、CoreUtils.SetRenderTargetを使用して、基準サイズを変更することで、512x512がscale=1.0として扱うことができる。

// CoreUtils.SetRenderTarget
public static void SetRenderTarget(
CommandBuffer cmd, RTHandle buffer, 
ClearFlag clearFlag, Color clearColor, int miplevel = 0, 
CubemapFace cubemapFace = CubemapFace.Unknown, int depthSlice = -1)

扱っている際に一度は混乱しそうだなと感じた。現在のRTHandleが定義している解像度がどれなのかは把握しておく必要がある。

Shader上のUV値

通常FullScreen描画をおこなう際、FullScreen用に定義したRenderTextureのUVサンプリングは0~1の範囲に収まるが、RTHandlesの場合必ずしもそうではないという。現在のRTHandlesに適したUVにスケーリングする必要がある。

UVスケーリングするためには、どうすればよいのか?RTHandlesごとの差異を吸収するためのプロパティがRTHandleProperties 構造体に定義されている。

public struct RTHandleProperties
{
    public Vector2Int previousViewportSize; // 1f前のViewport(レンダリング用に指定したRT)のReference Size。
    public Vector2Int previousRenderTargetSize; // 1f前のRTHandleSystemが最大サイズで割り当てているRTのReference Size。
    public Vector2Int currentViewportSize; // 1f前のViewport(レンダリング用に指定したRT)のReference Size。
    public Vector2Int currentRenderTargetSize; // 1f前のRTHandleSystemが最大サイズで割り当てているRTのReference Size。
    public Vector4 rtHandleScale; // RTHandleをサンプリングするために全画面UVに適用するscale値。
}

RTHandlePropertiesの更新は、SetReferenceSize関数内部で処理している。

Shader側でRTHandleProperties.rtHandleScaleを乗算しスケーリングすることで、RTHandlesを使用した場合でも、異なる解像度間でのFullScreen描画が可能となる。

float2 scaledUVs = fullScreenUVs * rtHandleScale.xy;

これはUVを扱っている全てのShaderに対して乗算する必要があるのだろうか…?

There are no shader constants provided by default with SRP. So, when you use RTHandles with your own SRP, you must provide these constants to their shaders themselves.
(SRP ではデフォルトで提供されるシェーダ定数はありません。したがって、独自の SRP で RTHandles を使用する場合は、これらの定数をシェーダー自体に提供する必要があります。)

ということは、URPではrtHandleScaleに該当するShader定数自体は存在しているということなのだろうか?この辺りは実際に実装していく中で見ていこうと思う。

DoubleBufferingが必要な場合

UnityのDocumentが挙げている例ではTemporal Anti-aliasingがあるが、そのカメラで撮影された、前フレームのColorBufferが必要な場合、DoubleBufferingとしてメモリに確保しつつ現フレームの描画をおこなう、ということが発生する。全てのカメラで1枚のRenderTexutreを使いまわす場合、これが利用できなくなるのか?というと、BufferedRTHandleSystems と呼ばれる、RTHandleをMulti-bufferingする機能が存在している。

今回はここまで。次回はBufferedRTHandleSystemsから学んでいこう。開発環境を書いておきながらまだコーディングをしていないので、そろそろ触れていきたいところ…

その3はこちら

https://zenn.dev/inpro/articles/e6fffb5b2da2a4

Discussion