RTHandle システムの使用
このページでは、RTHandle システムを使用して、レンダーパイプラインでレンダーテクスチャを管理する方法について説明します。RTHandle システムについては、RTHandle システム および RTHandle システムの基礎 を参照してください。
RTHandle システムの初期化
RTHandle
に関連するすべての操作には、RTHandleSystem
クラスのインスタンスが必要です。このクラスには、RTHandle の割り当て、RTHandle の解放、およびフレームの参照サイズの設定に必要な API がすべて含まれています。開発者は、レンダーパイプライン内で RTHandleSystem
のインスタンスを作成して維持するか、このセクションで後述する静的な RTHandles クラスを利用する必要があります。RTHandleSystem
の独自のインスタンスを作成する方法については、次のコードサンプルを参照してください。
RTHandleSystem m_RTHandleSystem = new RTHandleSystem();
m_RTHandleSystem.Initialize(Screen.width, Screen.height, scaledRTsupportsMSAA: true, scaledRTMSAASamples: MSAASamples.MSAA4x);
システムを初期化するときは、最初の解像度を指定する必要があります。上記のコード例では、画面の幅と高さを使用しています。RTHandle システムは、カメラで現在の最大サイズよりも大きい解像度が要求されたときのみにレンダーテクスチャの再割り当て (アロケーション) を行います。このため、RTHandle
の内部の解像度は、ここで指定した値よりも小さくなることはありません。この解像度は、メインディスプレイの解像度で初期化することをお勧めします。これにより、アプリケーションの起動時に、システムでレンダーテクスチャの再割り当てが不必要に行われる (その結果、意図しないメモリの急増が引き起こされる) ことがなくなります。
マルチサンプリングアンチエイリアス (MSAA) を使用する場合は、初期化時に MSAA サンプルモードを宣言する必要があります。上記のコード例では、RTHandle システムで MSAA をサポートし、MSAA4x モードを使用しています。RTHandle システムは、すべてのテクスチャを同じサンプル数で割り当てます。サンプルモードは後で変更できますが、その場合は、自動的にサイズ変更されるすべてのテクスチャのサンプルモードが変更されます。
Initialize
関数は、アプリケーションの起動時に 1 回だけ呼び出す必要があります。その後、初期化されたインスタンスを使用してテクスチャにアロケーションを行うことができます。
大部分の RTHandle
は同じ RTHandleSystem
インスタンスから割り当てられるため、RTHandle システムは、静的な RTHandles
クラスを通じてデフォルトのグローバルインスタンスも提供します。RTHandleSystem
の独自のインスタンスを維持しなくても、これを利用すれば、インスタンスの生存期間を気にすることなく同じ API を使用できます。静的インスタンスを使用する場合の初期化は次のようになります。
RTHandles.Initialize(Screen.width, Screen.height, scaledRTsupportsMSAA:true, scaledRTMSAASamples:MSAASamples.MSAA4x);
このページの以降のコード例では、デフォルトのグローバルインスタンスを使用しています。
RTHandle システムの更新
カメラによるレンダリングを行う前に、RTHandle システムで使用する解像度を参照サイズとして設定する必要があります。そのためには、SetReferenceSize
関数を呼び出します。
RTHandles.SetReferenceSize(width, hight, msaaSamples);
この関数呼び出しには次の 2 つの効果があります。
- 指定した新しい参照サイズが現在のサイズよりも大きい場合、RTHandle システムは、新しいサイズに合わせて、内部ですべてのレンダーテクスチャに対し再割り当てを行います。
- その後、RTHandle システムは、RTHandles をアクティブなレンダーテクスチャとして使用するときのために、ビューポートとレンダーテクスチャのスケールを設定する内部プロパティを更新します。
RTHandle の割り当てと解放
RTHandleSystem
のインスタンスを初期化したら、独自のインスタンスでも静的なデフォルトインスタンスでも、それを使用して RTHandle を割り当てることができます。
RTHandle
を割り当てる方法は、大きく分けて 3 つあります。どの方法でも同様に、RTHandleSystem インスタンスの Alloc
メソッドを使用します。これらの関数のパラメーターのほとんどは、Unity の通常の RenderTexture と同じです。詳細については、RenderTexture API のドキュメント を参照してください。このセクションでは、RTHandle
のサイズに関するパラメーターに焦点を当てて説明します。
Vector2 scaleFactor
: このバリアントには、幅と高さを表す 2D スケールの定数が必要です。RTHandle システムは、この値を使用して最大参照サイズに対するテクスチャの解像度を計算します。例えば、スケール (1.0f, 1.0f) を指定すると、全画面のテクスチャが生成されます。スケール (0.5f 0.5f) を指定すると、解像度が 4 分の 1 のテクスチャが生成されます。ScaleFunc scaleFunc
:RTHandle
のサイズの計算に定数のスケールを使用することが望ましくない場合は、テクスチャのサイズを計算するファンクターを指定できます。ファンクターは、最大参照サイズを表すVector2Int
をパラメーターとして取り、作成したいテクスチャのサイズを表すVector2Int
を返します。int width, int height
: 固定サイズのテクスチャに使用します。この方法でアロケーションを行ったテクスチャは、通常の RenderTexture と同じように動作します。
RenderTargetIdentifier、RenderTexture、または Texture から RTHandle を作成するオーバーライドもあります。これらは、テクスチャが実際の RTHandle
でない場合でも、すべてのテクスチャを RTHandle API を使用して操作する場合に役立ちます。
次のコードサンプルには、Alloc
関数の使用例が含まれています。
// Simple Scale
RTHandle simpleScale = RTHandles.Alloc(Vector2.one, depthBufferBits: DepthBits.Depth32, dimension: TextureDimension.Tex2D, name: "CameraDepthStencil");
// Functor
Vector2Int ComputeRTHandleSize(Vector2Int screenSize)
{
return DoSpecificResolutionComputation(screenSize);
}
RTHandle rtHandleUsingFunctor = RTHandles.Alloc(ComputeRTHandleSize, colorFormat: GraphicsFormat.R32_SFloat, dimension: TextureDimension.Tex2D);
// Fixed size
RTHandle fixedSize = RTHandles.Alloc(256, 256, colorFormat: GraphicsFormat.R8G8B8A8_UNorm, dimension: TextureDimension.Tex2D);
特定の RTHande が不要になったら、そのハンドルを解放できます。そのためには、Release
メソッドを呼び出します。
myRTHandle.Release();
RTHandle の使用
RTHandle を割り当てた後は、通常の RenderTexture とまったく同じように使用できます。RenderTargetIdentifier
と RenderTexture
への暗示的な変換がサポートされるため、関連する通常の Unity API でこれらを使用することができます。
ただし、RTHandle を使用する場合、RTHandle
の実際の解像度は現在の解像度と異なる可能性があります。例えば、メインカメラが 1920x1080 でレンダリングし、セカンダリカメラが 512x512 でレンダリングする場合、すべての RTHandle の解像度は、低解像度でレンダリングするときでも 1920x1080 がベースとなります。このため、RTHandle をレンダーターゲットとして設定する場合は注意が必要です。CoreUtils クラスには、これに対処するために役立つ多くの API が用意されています。例を以下に示します。
public static void SetRenderTarget(CommandBuffer cmd, RTHandle buffer, ClearFlag clearFlag, Color clearColor, int miplevel = 0, CubemapFace cubemapFace = CubemapFace.Unknown, int depthSlice = -1)
この関数は、RTHandle
をアクティブなレンダーターゲットとして設定しますが、ビューポートも、最大サイズではなく、RTHandle
のスケールと現在の参照サイズに基づいて設定します。
例えば、参照サイズが 512x512 であるとすると、最大サイズが 1920x1080 の場合でも、スケールが (1.0f, 1.0f) のテクスチャでは 512x512 のサイズが使用され、512x512 のビューポートが設定されます。スケールが (0.5f, 0.5f) のテクスチャでは 256x256 のビューポートが設定され、その他も同様に処理されます。つまり、これらのヘルパー関数を使用すれば、RTHandle システムで RTHandle
パラメーターに基づいて適切なビューポートを生成できます。
これは、SetRenderTarget
関数のさまざまなオーバーライドの一例です。すべてのオーバーライドの一覧については、ドキュメント を参照してください。
シェーダーでの RTHandle の使用
シェーダーで全画面のレンダーテクスチャから通常の方法でサンプリングする場合、UV は 0 - 1 の範囲全体に及びます。ただし、RTHandle
では常にそうとは限りません。現在のレンダリングがビューポートの一部だけを対象としている場合があるからです。この点を考慮して、スケールを使用する RTHandle
をサンプリングするときは、UV にスケールを適用する必要があります。シェーダー内で RTHandle
の特性を処理するために必要な情報はすべて、RTHandleSystem
インスタンスから提供される RTHandeProperties
構造体に含まれています。この構造体にアクセスするには、次のコードを使用します。
RTHandleProperties rtHandleProperties = RTHandles.rtHandleProperties;
この構造体には次のプロパティが含まれています。
public struct RTHandleProperties
{
public Vector2Int previousViewportSize;
public Vector2Int previousRenderTargetSize;
public Vector2Int currentViewportSize;
public Vector2Int currentRenderTargetSize;
public Vector4 rtHandleScale;
}
この構造体は次の情報を提供します。
- 現在のビューポートのサイズ。開発者がレンダリング用に設定した参照サイズです。
- 現在のレンダーターゲットのサイズ。最大参照サイズに基づく実際のレンダーテクスチャのサイズです。
rtHandleScale
。RTHandle をサンプリングするときに全画面の UV に適用するスケールです。
以前のフレームの値も使用できます。詳細については、カメラ固有の RTHandle を参照してください。通常、この構造体で最も重要なプロパティは rtHandleScale
です。これにより、全画面の UV 座標にスケールを適用し、その結果を RTHandle のサンプリングに使用することができます。例を以下に示します。
float2 scaledUVs = fullScreenUVs * rtHandleScale.xy;
ただし、部分的なビューポートでは常に (0, 0) が開始点となるため、ビューポート内で整数のピクセル座標を使用してテクスチャからコンテンツをロードするときは、再スケールを行う必要はありません。
もう 1 つ考慮するべき重要な点として、全画面のクアッドを部分的なビューポートにレンダリングする場合は、標準の UV のアドレス指定メカニズム (ラップや固定など) を使用することにメリットはありません。これは、テクスチャがビューポートよりも大きい可能性があるためです。この理由から、ビューポートの外部のピクセルをサンプリングする場合は注意が必要です。
カスタム SRP に固有の情報
SRP には、デフォルトで定義されているシェーダー定数はありません。したがって、独自の SRP で RTHandle を使用する場合は、これらの定数をシェーダーに提供する必要があります。
カメラ固有の RTHandle
レンダリングループで使用されるレンダーテクスチャのほとんどは、すべてのカメラで共有することができます。あるフレームから別のフレームへとコンテンツを引き継ぐ必要がなければ問題はありませんが、レンダーテクスチャによっては永続化が必要になることもあります。その良い例が、後続のフレームでメインカラーバッファを Temporal Anti-aliasing (TAA) のために使用する場合です。つまり、カメラの RTHandle を他のカメラと共有することはできません。たいていの場合、このような RTHandle では、少なくともダブルバッファリングを有効にする (前のフレームの間に読み取り、現在のフレームの間に書き込む) 必要もあります。この問題に対処するために、RTHandle システムには BufferedRTHandleSystem
が用意されています。
BufferedRTHandleSystem
は、RTHandle のマルチバッファリングに対応した RTHandleSystem
です。仕組みとしては、一意の ID でバッファを識別し APIを提供して同じバッファのインスタンスを複数割り当てた後、前のフレームからそれらを取得します。これらは履歴バッファです。通常は、カメラごとに BufferedRTHandleSystem
を 1 つ割り当てる必要があります。つまり、各カメラが固有の RTHandle を所有することになります。
すべてのカメラに履歴バッファが必要なわけではありません。例えば、TAA が不要なカメラに BufferedRTHandleSystem
を割り当てる必要はありません。履歴バッファにはメモリが必要なため、履歴バッファを使用しないカメラに割り当てないようにすれば、メモリを節約することができます。また、履歴バッファは、そのバッファの対象となるカメラの解像度のみで割り当てられます。メインカメラが 1920x1080 のとき、256x256 でレンダリングする別のカメラが履歴カラーバッファを必要としているとすると、2 番目のカメラでは 256x256 のバッファだけが使用され、カメラ固有でない RTHandle とは違って 1920x1080 のバッファは使用されません。BufferedRTHandleSystem
のインスタンスを作成する方法については、以下のコードサンプルを参照してください。
BufferedRTHandleSystem m_HistoryRTSystem = new BufferedRTHandleSystem();
BufferedRTHandleSystem
を使用して RTHandle
を割り当てる場合、そのプロセスは通常の RTHandleSystem
とは異なります。
public void AllocBuffer(int bufferId, Func<RTHandleSystem, int, RTHandle> allocator, int bufferCount);
bufferId
は、システムがバッファを識別するために使用する一意の ID です。allocator は、必要時に RTHandle
を割り当てるための関数です (どのインスタンスも事前に割り当てられることはありません)。bufferCount
は必要なインスタンス数です。
その後、次のように ID とインスタンスインデックスを指定して各 RTHandle
を取得できます。
public RTHandle GetFrameRT(int bufferId, int frameIndex);
フレームインデックスは、0 と、バッファ数から 1 を引いた値の範囲です。0 は常に現在のフレームバッファを表します。1 は前のフレームバッファ、2 はその 1 つ前のバッファ、以下同様になります。
バッファリングされた RTHandle を解放するには、BufferedRTHandleSystem
の Release
関数を呼び出して、解放するバッファの ID を渡します。
public void ReleaseBuffer(int bufferId);
通常の RTHandleSystem
の場合と同じように、BufferedRTHandleSystem
のインスタンスごとに参照サイズを指定する必要があります。
public void SwapAndSetReferenceSize(int width, int height, MSAASamples msaaSamples);
これは通常の RTHandleSystem と同じように動作しますが、内部的にバッファのスワップも行われるため、GetFrameRT
のインデックス 0 が引き続き現在のフレームバッファを参照します。通常とはわずかに異なるこのカメラ固有バッファの処理方法は、シェーダーコードを記述するときにも影響します。
このようなマルチバッファリングのアプローチでは、前のフレームの RTHandle
と現在のフレームのサイズが異なる場合があります。この状況は、例えば、動的解像度を使用している場合や、エディターでウィンドウのサイズを変更したときに発生する可能性があります。したがって、前のフレームからバッファリングされた RTHandle
にアクセスするときは、必要に応じて適切に拡大縮小することが必要です。RTHandleProperties.rtHandleScale.zw
には、そのために Unity が使用するスケールが含まれています。Unity では、これを通常の RTHandle の xy
とまったく同じように使用します。RTHandleProperties
に前のフレームのビューポートと解像度が含まれている理由もここにあります。これは、履歴バッファを使用して計算を行う際に役立つ場合があります。
動的解像度
RTHandle システムには、その設計の副産物として、動的解像度のソフトウェアシミュレーションにも使用できるという一面があります。カメラの現在の解像度と実際のレンダーテクスチャオブジェクトは直接相関していないため、フレームの開始時に任意の解像度を指定し、それに応じてすべてのレンダーテクスチャを拡大縮小することができます。
参照サイズのリセット
場合によっては、通常よりも高い解像度へのレンダリングが一時的に必要になることがあります。この解像度がアプリケーションで不要になると、割り当てられた追加メモリは無駄になります。この状況を回避するには、次のように RTHandleSystem
の現在の最大解像度をリセットします。
RTHandles.ResetReferenceSize(newWidth, newHeight);
これにより、RTHandle システムでは、新しく指定されたサイズに合わせてすべての RTHandle が強制的に再割り当てされます。RTHandle
のサイズを小さくするには、これが唯一の方法です。