GPU インスタンシングを使うと、少ない ドローコール で、同じメッシュの複数のコピーをいっぺんに描画 (またはレンダリング) できます。 これは、建物、樹木、草などのオブジェクトを描画したり、シーンに繰り返し登場するものを描画する場合に便利です。
GPU インスタンシングは、各ドローコールで同じメッシュをレンダリングするだけですが、各インスタンスに変化を加えるために、パラメーター (例えば、色やスケール) を変えることで、同じパターンの繰り返しの回数を減らすことができます。
GPU インスタンシングを使うと、シーンごとに使用されるドローコールの数を減らすことができます。 これにより、プロジェクトのレンダリングパフォーマンスが大幅に向上します。
GPU インスタンシングをマテリアル上で有効にするには、Project ウィンドウでマテリアルを選択し、Inspector で Enable Instancing をチェックします。
このチェックボックスは、マテリアルシェーダーが GPU インスタンシングをサポートする場合のみ表示されます。これには、Standard、StandardSpecular、そしてすべてのサーフェス シェーダー が含まれます。詳しくは スタンダードシェーダー を参照してください。
以下のスクリーンショットは、複数のゲームオブジェクトを持つ同じシーンを示しています。最初の画像では GPU インスタンシングが有効になっており、2 番目の画像では有効になっていません。 FPS、Batches、Saved by batching の違いに注目してください。
GPU インスタンシングを使用するる場合、以下の制限があります。
Unity は インスタンシングのために、自動的に MeshRenderer コンポーネントと Graphics.DrawMesh
の呼び出しを選択します。SkinnedMeshRenderer はサポートされないことに注意してください。
Unity は、1 回の GPU インスタンシングドローコールで、同じメッシュとマテリアルを共有するゲームオブジェクトだけをバッチ処理します。インスタンシングの効率を上げるために、少数のメッシュとマテリアルを使用するようにします。バリエーションを作成するには、シェーダースクリプトを変更してインスタンスごとのデータを追加します (詳細は次のセクションを参照してください)。
スクリプトから GPU インスタンシングを実行するには、Graphics.DrawMeshInstanced と Graphics.DrawMeshInstancedIndirect の呼び出しを利用することもできます。
GPU インスタンシングは、以下のプラットフォームおよび API で利用可能です。
Windows の DirectX 11 と DirectX 12
Windows、macOS、Linux、iOS、Android の OpenGL Core 4.1+/ES3.0+
macOS と iOS の Metal
Windows、Linux、Android の Vulkan
PlayStation 4 と Xbox One
WebGL (requires WebGL 2.0 API)
デフォルトでは、インスタンス化された各ドローコールで、異なる Transform を持つゲームオブジェクトのインスタンスだけをバッチします。インスタンス化したゲームオブジェクトをさらに相違させるには、シェーダーを変更してマテリアルカラーなどのインスタンスごとのプロパティを加えます。
以下の例は、インスタンスごとに異なるカラー値を持つインスタンス化したシェーダーを作成する方法を示しています。
Shader "Custom/InstancedColorSurfaceShader" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// 物理ベースの Standard ライティングモデル。すべてのライトタイプで影を有効にします
#pragma surface surf Standard fullforwardshadows
//Shader model 3.0 対応を使用します
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
_Color
をインスタンス化したプロパティとして宣言すると、Unity はゲームオブジェクトに設定された MaterialPropertyBlock オブジェクトの _Color
値を集め、1 つのドローコールに取り込みます。
MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;
foreach (GameObject obj in objects)
{
float r = Random.Range(0.0f, 1.0f);
float g = Random.Range(0.0f, 1.0f);
float b = Random.Range(0.0f, 1.0f);
props.SetColor("_Color", new Color(r, g, b));
renderer = obj.GetComponent<MeshRenderer>();
renderer.SetPropertyBlock(props);
}
通常 (インスタンスシェーダーを使用していない場合、または、_Color
がインスタンスごとのプロパティでない場合)、MaterialPropertyBlock には様々な値があるため、ドローコールバッチは分かれています。
この変更を実際に反映させるには、GPU Instancing を使用可能にしなくてはなりません。それには、Project ウィンドウでシェーダーを選択し、Inspector で Enable Instancing をチェックします。
以下の例は簡単な Unlit シェーダーを様々な色でインスタンシング可能にします。
Shader "SimplestInstancedShader"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID // フラグメントシェーダーのインスタンス化したプロパティにアクセスしたい場合にのみ必要
};
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);// フラグメントシェーダーのインスタンス化したプロパティにアクセスしたい場合にのみ必要
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i); // フラグメントシェーダーのインスタンス化したプロパティがアクセスされる場合にのみ必要
return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
}
ENDCG
}
}
}
追加コード | 機能 |
---|---|
#pragma multi_compile_instancing |
Unity にインスタンシングバリアントを生成するように命令するために使用します。サーフェスシェーダーには必要ありません。 |
UNITY_VERTEX_INPUT_INSTANCE_ID |
頂点シェーダー入力/出力構造体で使用してインスタンス ID を定義します。 詳細は SV_InstanceID を参照してください。 |
UNITY_INSTANCING_BUFFER_START(name) / UNITY_INSTANCING_BUFFER_END(name)
|
インスタンスごとのプロパティはすべて、予約された定数バッファで定義する必要があります。この一組になったマクロを使って、各インスタンスに固有化したいプロパティをラップします。 |
UNITY_DEFINE_INSTANCED_PROP(float4, _Color) |
型と名前を使ってインスタンスごとのシェーダープロパティを定義します。この例では、_Color プロパティが固有のものです。 |
UNITY_SETUP_INSTANCE_ID(v); |
インスタンス ID がシェーダー関数にアクセスできるようにします。これは頂点シェーダーの最初に使用する必要があります。また、フラグメントシェーダーでは、オプションとして使用されます。 |
UNITY_TRANSFER_INSTANCE_ID(v, o); |
インスタンス ID を頂点シェーダの入力構造体から出力構造体にコピーします。これは、フラグメントシェーダーでは、インスタンスごとのデータにアクセスするときのみ必要です。 |
UNITY_ACCESS_INSTANCED_PROP(arrayName, color) |
インスタンシング定数バッファーで定義されたインスタンスごとのシェーダープロパティにアクセスするために使用します。インスタンス ID を使用してインスタンスデータ配列を表します。マクロの arrayName は UNITY_INSTANCING_BUFFER_END(name) マクロのそれと一致する必要があります。 |
注意
複数のインスタンスごとのプロパティを使用する場合は、MaterialPropertyBlocks
でそれらすべてを満たす必要はありません。
1 つのインスタンスにプロパティがない場合、Unity は参照されているマテリアルからデフォルト値を取ります。マテリアルが指定したプロパティのデフォルト値を持たない場合、Unity は値を 0 に設定します。MaterialPropertyBlock
にインスタンス化していないプロパティを使用しないでください。なぜなら、インスタンシングを無効にするからです。代わりに、他のマテリアルを作成してください。
バッチを行う場合、Unity はインスタンシングより 静的バッチ を優先します。ゲームオブジェクトの 1 つを静的バッチに指定し Unity がそのバッチ処理を行うと、たとえレンダラーがインスタンシングのシェーダーを使用していても、Unity はそのゲームオブジェクトのインスタンシングを無効にします。この場合、警告メッセージが Inspector ウィンドウに表示され、静的バッチ処理を無効にするよう指示します。静的バッチ処理を無効にするには、Player 設定 (Edit > Project Settings > Player) に移動し、使用するプラットフォームの Other Settings を開き、Rendering セクションの Static Batching チェックボックスの選択を解除します。
Unity は、動的バッチ処理よりもインスタンシングを優先します。Unity がメッシュをインスタンス化できる場合、メッシュの動的バッチ処理を無効にします。
ゲームオブジェクトを自動的にひとまとめにインスタンス化するのを妨げる要因がいくつかあります。これらの要因には、マテリアル変更と深度ソートなどがあります。Unity に GPU のインスタンシングを使用してオブジェクトを描画するよう強制するには、Graphics.DrawMeshInstanced を使用します。Graphics.DrawMesh と同様に、この関数は不要なゲームオブジェクトを作成せずに 1 フレーム分のメッシュを描画します。
インスタンスの数を含む、インスタンシングドローコールのパラメーターを計算バッファから読み込むには、スクリプトで DrawMeshInstancedIndirect
を使用します。これは、GPU からすべてのインスタンスデータを取得する必要があり、CPU が描画するインスタンスの数を知らない場合 (たとえば、GPU カリングを実行するとき) に便利です。詳細な説明とコード例については、スクリプトリファレンスの Graphics.DrawMeshInstancedIndirect を参照してください。
Unity 2018.1 以降、グローバルイルミネーション (GI) レンダリングは、ライトプローブ、オクルージョンのプローブ (Shadowmask モード)、ライトマップ ST の形で、GPU インスタンシングによってサポートされています。スタンダードシェーダーとサーフェスシェーダーは自動的に GI サポートを有効にします。
シーンでベイクされたライトプローブとオクルージョンプローブによって影響を受ける動的レンダラー、そして同じライトマップテクスチャにベイクされた静的レンダラーは、フォワードとディファードのレンダリングループによって、GPU インスタンシングを使用して自動的にまとめて処理されます。
Graphics.DrawMeshInstanced に対しては、LightProbeUsage 引数を CustomProvided に設定し、プローブデータをコピーした MaterialPropertyBlock を与えることで、ライトプローブとオクルージョンプローブのレンダリングを行なえます。詳細な説明とコードサンプルは、スクリプトリファレンスの LightProbes.CalculateInterpolatedLightAndOcclusionProbes を参照してください。
GPU インスタンシングは、Unity のグローバルイルミネーション (GI) レンダリングをサポートします。各 GPU インスタンスは、異なる ライトプローブ、1 つの ライトマップ(ただし、ライトマップの複数のアトラス領域)、1 つのライトプローブプロキシボリューム コンポーネントのいずれかからの GI をサポートできます (すべてのインスタンスを含む空間ボリュームに対してベイクします)。スタンダードシェーダーとサーフェスシェーダーでは、このサポートが有効になっています。
GPU インスタンシングを使用すると、フォワード と Deferred のレンダリングループを使って、ベイクしたライトプローブ (オクルージョンデータを含む) によって影響を受ける動的な メッシュレンダラー や、同じライトマップテクスチャにベイクされた静的メッシュレンダラーを自動的にまとめて処理できます。詳細は、レンダリングパイプライン を参照してください。
Graphics.DrawMeshInstanced に対しては、LightProbeUsage 引数を CustomProvided に設定し、プローブデータをコピーした MaterialPropertyBlock を与えることで、ライトプローブとオクルージョンプローブのレンダリングを使用できます。詳細な説明とコードサンプルは、スクリプトリファレンスの LightProbes.CalculateInterpolatedLightAndOcclusionProbes を参照してください。
代わりに、LPPV コンポーネントの参照と LightProbeUsage.UseProxyVolume を Graphics.DrawMeshInstanced
に渡すこともできます。これを行うと、すべてのインスタンスが Light Probe データの L0 バンドと L1 バンドのボリュームをサンプリングします。L2 データとオクルージョンデータを補うには、MaterialPropertyBlock
を使用します。詳細は、ライトプローブ - 技術的な情報 を参照してください。
Unity 2017.3 以降、シェーダーを初めてレンダリングするときにレンダリングが完全に滑らかになるようにするには、OpenGL でインスタンシングを使用するためにシェーダーをウォームアップする必要があります。シェーダーのウォーミングアップを必要としないプラットフォームでインスタンシングのためにウォームアップしても、何も起こりません。
詳細は ShaderVariantCollection.WarmUp と Shader.WarmupAllShaders を参照してください。
#pragma instancing_options
ディレクティブは以下の switch を使用できます。
Switch | 機能 |
---|---|
forcemaxcount:batchSize と maxcount:batchSize
|
ほとんどのプラットフォームで、ターゲットデバイス上の最大定数バッファーサイズをすべてのインスタンスごとのプロパティを含む構造体のサイズで割ることで、Unity は自動的にインスタンスデータ配列サイズを計算します。 一般的に、バッチサイズを心配する必要はありません。 ただし、一部のプラットフォーム (Vulkan、Xbox One、Switch) では、まだ決められた配列サイズが必要です。 maxcount オプションを使って、これらのプラットフォームのバッチサイズを指定することができます。 このオプションは他のプラットフォームでは完全に無視されます。 すべてのプラットフォームにバッチサイズを強制したいのであれば、 forcemaxcount を使用してください (例えば、DrawMeshInstanced を使って 256 のインスタンス化したスプライトで描画を行うことが分かっている場合)。 デフォルト値は、2 つのオプションとも 500 です。 |
assumeuniformscaling |
すべてのインスタンスが統一されたスケーリング (X, Y, Z 軸すべてに対して) を持つと仮定するように Unity に命令します。 |
nolodfade |
Unity が LOD のフェード値に GPU インスタンシングを適用しないようにします。 |
nolightprobe |
Unity が GPU インスタンシングを ライトプローブ 値 (オクルージョンデータを含む) に適用しないようにします。これは、GPU インスタンシングとライトプローブの両方を使用するゲームオブジェクトがないことが確かである場合に、パフォーマンスの向上に役立ちます。 |
nolightmap |
Unity が GPU インスタンシングを Lightmap ST (アトラス情報) の値に適用しないようにします。これは、GPU インスタンシングとライトマップの両方を使用するゲームオブジェクトがないことが確かである場合に、パフォーマンスの向上に役立ちます。 |
procedural:FunctionName |
Graphics.DrawMeshInstancedIndirect で使用する追加のバリアントを生成するよう、Unity に命令します。 頂点シェーダーステージの最初に、Unity はコロンの後に指定された関数を呼び出します。インスタンスデータを手動で設定するには、インスタンスごとのデータを普通にシェーダーに追加するのと同じ方法で、この関数にインスタンスごとのデータを追加します。フェッチされたインスタンスプロパティのいずれかがフラグメントシェーダーに含まれている場合は、Unity はフラグメントシェーダーの最初にも、この関数を呼び出します。 |
シェーダースクリプトを作成する場合、mul(UNITY_MATRIX_MVP,v.vertex)
ではなく、常に UnityObjectToClipPos(v.vertex)
を使用します。
インスタンス化されたシェーダーでは、通常どおり UNITY_MATRIX_MVP
を使用できますが、UnityObjectToClipPos
は頂点の位置をオブジェクト空間からクリップスペースに変換する最も効率的な方法です。Unity はまた、プロジェクト内のすべてのシェーダーをスキャンし、 mul(UNITY_MATRIX_MVP, v)
が発生すると自動的に UnityObjectToClipPos(v)
に置き換えるシェーダーのアップグレード機能を実装しています。
UNITY_MATRIX_MVP
(UNITY_MATRIX_MV
と共に) がまだ使用されている場合は、コンソールウィンドウ (Window > General > Console) にパフォーマンスの警告が表示されます。
#pragma
サーフェスディレクティブで noinstancing
を指定しない限り、サーフェスシェーダーにはデフォルトでインスタンシングバリアントが生成されます。Standard および StandardSpecular シェーダーはインスタンスシングが適用されるように既に変更されていますが、transform 以外は、インスタンスごとのプロパティは定義されていません。Unity は、サーフェスシェーダーで#pragma multi_compile_instancing
が使用されても無視します。
シーン内のゲームオブジェクトで GPU インスタンシングが有効になっていない場合、Unity はインスタンシングバリアントを削除します。 ストリッピングの挙動を無効にするには、 Graphics 設定 ( Edit > Project Settings > Graphics) を開き、 Shader stripping セクションに移動し、__Instancing Variants__ を変更します。
Graphics.DrawMeshInstanced
を使用するには、スクリプトがこのメソッドに渡すマテリアルの GPU インスタンシングを有効にする必要があります。ただし、Graphics.DrawMeshInstancedIndirect
では GPU インスタンシングを有効にする必要はありません。間接的なインスタンシングのキーワード PROCEDURAL_INSTANCING_ON
はストリッピングに影響されません。
インスタンス化されたドローコールは フレームデバッガー で Draw Mesh (instanced) として表示されます。
インスタンスごとのプロパティを必ずしも定義する必要はありません。ただし、インスタンス ID の設定は必須です。なぜなら、ワールド行列が正しく機能するために必要だからです。サーフェスシェーダーは自動的にインスタンス ID を設定します。カスタム頂点シェーダーとフラグメントシェーダのインスタンス ID は手動で設定する必要があります。これを行うには、シェーダーのはじめにUNITY_SETUP_INSTANCE_ID
を使用します。
フォワードレンダリングを使用する場合、Unity は複数のライトの影響を受けるオブジェクトを効率的にインスタンス化できません。加算パスではなく、ベースパスのみがインスタンス化を有効に活用できます。ライティングパスの詳細については、フォワードレンダリング と Pass 内の Tags を参照してください。
マルチパスシェーダーのパスが 2 つ以上の場合は、最初のパスだけがインスタンス化されます。これは、Unity が強制的に後からのパスをオブジェクトごとに一緒にレンダリングして、マテリアルの変更を強制するためです。
上記の例で使用されているシェーダーのマクロはすべて、 UnityInstancing.cginc で定義されています。 このファイルは、[Unity installation folder]\Editor\Data\CGIncludes ディレクトリにあります。