BatchRenderGroup (BRG) は、多数のインスタンスの効率的なレンダリングを行うために、DOTS Instancing (DOTS インスタンシング) と呼ばれる新しいシェーダーインスタンシングモードを使用します。BRG が使用する全てのシェーダーは DOTS Instancing をサポートしている必要があります。従来のインスタンス化されたシェーダーでは、シェーダーは、定数バッファあるいはユニフォームバッファ内のインスタンス化されたプロパティごとに 1 つの配列を渡され、各配列内の各要素が、描画内の 1 つのインスタンスのプロパティ値を含みます。DOTS Instanced シェーダーでは、Unity は DOTS Instanced プロパティごとに 1 つの 32 ビット整数をシェーダーに渡します。この 32 ビット整数はメタデータ値と呼ばれます。この整数は何でも表せますが、通常は、シェーダーがレンダリングするインスタンス用にプロパティデータをロードする (ロード元の)、バッファ内のオフセットを表します。
従来のインスタンシングと比較して、DOTS Instancing には多くの利点があります。これには例えば以下が含まれます。
シェーダーで DOTS Instancing をサポートするには、以下が必要です。
#pragma target 4.5
以上を指定してください。DOTS_INSTANCING_ON
をサポートする必要があります。これを #pragma multi_compile _ DOTS_INSTANCING_ON
を使用して宣言してください。ノート: Shader Graph と、Unity が URP と HDRP で提供するシェーダーは、DOTS Instancing をサポートしています。
変換行列などのインスタンスデータをロードするには、シェーダーは DOTS Instanced プロパティを定義する必要があります。以下は、単純な DOTS Instanced プロパティブロックの例です。
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
UNITY_DOTS_INSTANCED_PROP(float4, Color)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
プロパティブロックの開始と終了を示すには、UNITY_DOTS_INSTANCING_START
およびUNITY_DOTS_INSTANCING_END
マクロの後にブロック名を付加します。この例では MaterialPropertyMetadata
という名前を使用しています。使用可能なブロック名は以下の 3 つです。
シェーダーはそれぞれを 1 つずつ宣言できるので、DOTS Instanced シェーダーはこのようなブロックを 0 個から 3 個持つことができます。Unity 定義のシェーダーコードは UserPropertyMetadata を使用しないので、この名前は確実に自由に使用できます。URP と HDRP は、その提供するシェーダーのそれぞれに BuiltinPropertyMetadata を定義し、そのほとんどに MaterialPropertyMetadata も定義します。このため、UserPropertyMetadata を使用することが推奨されます。カスタムシェーダーは、使用可能な 3 つの名前を全て同時に使用できます。
ブロックは、以下のようなフォーマットで、DOTS Instanced プロパティの定義をいくつでも含むことができます。
UNITY_DOTS_INSTANCED_PROP(PropertyType, PropertyName)
PropertyType
は、ブールベクトル以外の任意の HLSL ビルトイン型 (uint、float4、float4x4、int2x4 など) にすることができ、PropertyName
は DOTS Instanced プロパティの名前です。DOTS Instanced プロパティは 通常のマテリアルプロパティ とは完全に別になっており、これには他の通常のマテリアルプロパティと同じ名前を付けることができます。これが可能な理由は、UNITY_DOTS_INSTANCED_PROP
マクロが、Unity によって認識され他のプロパティ名と競合しない、特殊な定数名を生成するからです。Unity 提供のシェーダーは DOTS Instanced プロパティに通常のマテリアルプロパティと同じ名前を付けますが、この規則に従う必要はありません。
内部的には、Unity は、シェーダーが宣言する DOTS Instanced プロパティごとに、32 ビット整数のメタデータ値をシェーダーに提供します。Unity は、コードが (描画に関連するバッチを作成するために) BatchRendererGroup.AddBatch を呼び出す時に、メタデータ値を設定します。メタデータ値は、Unity が設定しなければデフォルトの 0
になります。またシェーダーは、BatchRendererGroup.AddBatch
に引数として渡される GraphicsBuffer に Unity が設定する、ByteAddressBuffer unity_DOTSInstanceData
にもアクセスできます。このバッファは通常、シェーダーがインスタンスデータをロードする (ロード元の) 場所です。複数のバッチで 1 つの GraphicsBuffer を共有することもできますが、バッチごとに個別に unity_DOTSInstanceData
用の GraphicsBuffer を使用することも可能です。
ノート: Unity は DOTS Instanced データを自動的に提供しません。開発者は、各バッチの unity_DOTSInstanceData
バッファに正しいデータが含まれていることを確認する必要があります。インスタンスデータは、Unity がゲームオブジェクト用に通常提供する多数のプロパティ (変換行列、ライトプローブ係数、ライトマップテクスチャ座標など) を含んでいる必要があります。
DOTS Instanced プロパティにアクセスするにあたって、シェーダーは、Unity が提供するアクセスマクロのいずれかを使用できます。アクセスマクロは、unity_DOTSInstanceData
内のインスタンスデータが以下のレイアウトを使用していることを想定します。
unity_DOTSInstanceData
バッファ内のバッチ内の最初のインスタンスのバイトアドレスを含んでいる。0
の場合は、全てのインスタンスがインスタンスインデックス 0 の値を使用する。(これは、各インスタンスがメタデータ値のバイトアドレスから直接ロードされることを意味します。この場合、バッファは、インスタンスごとに 1 つではなく、単一の値を格納するだけで済みます。)1
の場合、アドレスには、AddressOfInstance0 + sizeof(PropertyType) * instanceID
を使用して見つけられるインスタンスインデックス instanceID
の値を持つ配列が含まれている。(この場合、レンダリングされる全てのインスタンスインデックスがバッファ内に有効なデータを持っていることを確認する必要があります。そうしないと、境界外アクセスや未定義の動作が発生する可能性があります。)メタデータの値を直接設定することもできます。これは、テクスチャなどの、上記のレイアウトを使用しないカスタムデータソースを使用する場合に便利です。
Unity は以下のアクセスマクロを提供しています。
アクセスマクロ | 説明 |
---|---|
UNITY_ACCESS_DOTS_INSTANCED_PROP(PropertyType, PropertyName) |
上記のレイアウトを使用して unity_DOTSInstanceData からロードされた値を返します。Unity が提供するシェーダーは、フォールバックするデフォルト値を持たない DOTS Instanced 組み込みプロパティにこのバージョンを使用します。 |
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(PropertyType, PropertyName) |
UNITY_ACCESS_DOTS_INSTANCED_PROP と同じ値を返しますが、メタデータの最上位ビットが 0 の場合はデフォルト値を返します。デフォルト値は、DOTS Instanced プロパティと同じ名前の通常のマテリアルプロパティの値です。Unity 提供のシェーダーが “DOTS Instanced プロパティは通常のマテリアルプロパティと同じ名前を持つ” という規則を採用しているのはこのためです。デフォルト値を使用する場合、アクセスマクロは unity_DOTSInstanceData に一切アクセスしません。Unity 提供のシェーダーはこのアクセスマクロを DOTS Instanced マテリアルプロパティに使用するため、ロードはマテリアルに設定された値にフォールバックします。 |
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(PropertyType, PropertyName, DefaultValue) |
UNITY_ACCESS_DOTS_INSTANCED_PROP と同じ値を返します。ただし、メタデータの最上位ビットが 0 の場合は、代わりに DefaultValue を返し、unity_DOTSInstanceData にはアクセスしません。 |
UNITY_DOTS_INSTANCED_METADATA_NAME(PropertyType, PropertyName) |
何にもアクセスせずにメタデータの値を直接返します。これは、カスタムのインスタンスデータロードスキームに役立ちます。 |
これらのマクロの使用例は、アクセスマクロの例 で参照できます。
Unity は、アクセスマクロと並んで、描画コマンドデータから定数の値を直接ロードするシェーダー関数を提供しています。Unity が提供するシェーダーは、これらの関数を使用します。
Unity は以下のシェーダー関数を提供しています。
シェーダー関数 | 説明 |
---|---|
LoadDOTSInstancedData_RenderingLayer |
描画コマンドの renderingLayerMask を返します。 |
LoadDOTSInstancedData_MotionVectorsParams |
描画コマンドの モーションベクトル生成モード を返します。これは、Unity シェーダーの想定する float4 としてフォーマットされます。 |
LoadDOTSInstancedData_WorldTransformParams |
インスタンスの描画を、反転した三角ワインディングで行うかどうかを返します。FlipWinding を参照してください。 |
LoadDOTSInstancedData_LightData |
シーンのメインのディレクショナルライトがインスタンスに対してアクティブかどうかを返します。メインのライトは、様々な理由で、非アクティブになっている場合があります。例えば、ライトがすでにライトマップに含まれている場合などです。 |
LoadDOTSInstancedData_LODFade |
LODCrossFade フラグ が設定されている場合に、設定した 8 ビットのクロスフェード値を返します。このフラグが設定されていない場合、戻り値は未定義です。 |
このセクションでは、Unity が提供するアクセスマクロの例と、これらのマクロを使用してインスタンス単位のデータや定数データにアクセスする方法を説明します。
以下の例の説明:
Color
のメタデータ値は 0x80001000
です。5
です。最上位ビットはすでに設定されているので、アクセサーマクロはデフォルトをロードしません。つまり、c0
、c1
、c2
は全て、unity_DOTSInstanceData
アドレス 0x1050
からロードされる同じ値になります。
void ExamplePerInstance()
{
// rawMetadataValue will contain 0x80001000
uint rawMetadataValue = UNITY_DOTS_INSTANCED_METADATA_NAME(float4, Color);
float4 c0 = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4, Color);
float4 c1 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4, Color);
float4 c2 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(float4, Color, float4(1, 2, 3, 4));
}
以下の例の説明:
Color
のメタデータ値は 0x00001000
です。5
です。最上位ビットが設定されていないため、デフォルトにフォールバックするアクセサーマクロは unity_DOTSInstanceData
にアクセスしません。これは、以下を意味します。
c0
には unity_DOTSInstanceData
アドレス 0x1000
の値が入ります。c1
は、通常のマテリアルプロパティ Color の値を格納し、Color プロパティが存在しない場合はコンパイルエラーを発生させます。c2
には (1,2,3,4)
が入ります (これが明示的なデフォルト値として渡されたため)。void ExampleConstant()
{
// rawMetadataValue will contain 0x00001000
uint rawMetadataValue = UNITY_DOTS_INSTANCED_METADATA_NAME(float4, Color);
float4 c0 = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4, Color);
float4 c1 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4, Color);
float4 c2 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(float4, Color, float4(1, 2, 3, 4));
}
全ての unity_DOTSInstanceData
バッファの最初の 64 バイトは、ゼロに初期化して未使用のままにすることが推奨されます。なぜなら、バッチ作成中に指定されない全てのメタデータ値に対して Unity が使用するデフォルトのメタデータ値が、ゼロであるためです。具体的には、シェーダーが UNITY_ACCESS_DOTS_INSTANCED_PROP
マクロからゼロのメタデータ値をロードする場合、インスタンスインデックスが無視されるため、この値はアドレス zero
からロードされます。最初の 64 バイト (最大の値型 [float4x4 行列] のサイズ) を確実にゼロにすることで、このようなロードが予測通りにゼロという結果を返すことを確実にできます。そうしないと、アドレスゼロに何があるかによって、シェーダーが、予測されない何かをロードする可能性があります。
DOTS Instancing を使用する場合、Shader Graph や Unity 提供のシェーダーは、変換行列に特殊な規則を使用します。GPU メモリと帯域幅を節約するために、変換行列は、Float を 16 個フルに使用するのではなく 12 個だけ使用して保存されます (4 つの Float は常に一定であるため)。これらのシェーダーは、行列内の各列の x、y、z が順番に格納される形でフォーマットされた Float を想定します。つまり、最初の 3 つの Float は最初の列の x、y、z であり、次の 3 つの Float は 2 番目の列の x、y、z です。行列は各列の w
要素を保存しません。これが影響する変換行列は以下です。
unity_ObjectToWorld
unity_WorldToObject
unity_MatrixPreviousM
unity_MatrixPreviousMI
以下のコードサンプルには、通常の 4×4 行列を “Float 12 個” の規則に変換する構造体が含まれています。
struct PackedMatrix
{
public float c0x;
public float c0y;
public float c0z;
public float c1x;
public float c1y;
public float c1z;
public float c2x;
public float c2y;
public float c2z;
public float c3x;
public float c3y;
public float c3z;
public PackedMatrix(Matrix4x4 m)
{
c0x = m.m00;
c0y = m.m10;
c0z = m.m20;
c1x = m.m01;
c1y = m.m11;
c1z = m.m21;
c2x = m.m02;
c2y = m.m12;
c2z = m.m22;
c3x = m.m03;
c3y = m.m13;
c3z = m.m23;
}
}