GPU インスタンシングを使うと、少ないドローコールで同一オブジェクトをたくさん描画することができます。ただし、いくらか制限事項があります。
インスタンシングをサポートする Standard Surface Shader があります。プロジェクトに加えるには、 Shader > Standard Surface Shader (Instanced) の順に選択します。
このシェーダーをゲームオブジェクトのマテリアルに適用します。マテリアルの Inspector ウィンドウで、Shader ドロップダウンで Instanced を選び、リストから自分のインスタンスシェーダーを選択します。
インスタンス化したオブジェクトが同じメッシュとマテリアルを共有していても、MaterialPropertyBlock API を使用してインスタンス化したオブジェクトごとにシェーダープロパティーを設定できます。以下の例では、各オブジェクトに _Color プロパティーを使ってランダムな色の値が指定されます。
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);
}
簡単な 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_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_INSTANCE_ID
};
UNITY_INSTANCING_CBUFFER_START (MyProperties)
UNITY_DEFINE_INSTANCED_PROP (float4, _Color)
UNITY_INSTANCING_CBUFFER_END
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 (_Color);
}
ENDCG
}
}
}
追加したコード | 説明 |
---|---|
#pragma multi_compile_instancing | multi_compile_instancing は 2 つのバリアントを 持つシェーダーを生成します。1 つはビルトインのキーワード INSTANCING_ON が決められているもの (インスタンシング可能)、もう 1 つは何も決められていないものです。これにより、GPU でインスタンシングがサポートされていない場合、シェーダーがインスタンシングしていないバージョンにフォールバックできます。 |
UNITY_INSTANCE_ID | 頂点シェーダーの入力や出力構造体でインスタンス ID を定義するために使用します。詳しくは、SV_InstanceID を参照してください。 |
UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END | それぞれのインスタンスのプロパティーはすべて、予約された定数バッファで定義する必要があります。この一組になったマクロを使って、各インスタンスに固有化したいプロパティーをラップします。 |
UNITY_DEFINE_INSTANCED_PROP(float4, color) | それぞれのシェーダープロパティーの型と名前を定義します。この例では、color プロパティーが固有のものです。 |
UNITY_SETUP_INSTANCE_ID(v); | インスタンス ID がシェーダー関数にアクセス可能になります。頂点シェーダーの最初に使用する必要があります。また、フラグメントシェーダーには、オプションとして使用されます。 |
UNITY_TRANSFER_INSTANCE_ID(v, o); | 頂点シェーダーで入力構造体から出力構造体へインスタンス ID をコピーします。フラグメントシェーダーでは、インスタンスごとのデータにアクセスするときのみ必要です。 |
UNITY_ACCESS_INSTANCED_PROP(color) | インスタンスごとのシェーダープロパティーにアクセスします。インスタンス ID を使用してインスタンスデータ配列を表します。 |
注意 マテリアルプロパティーがインスタンス化されている限り、たとえ他のレンダラーに異なるインスタンス化したプロパティーを用いたとしても、レンダラーは常にインスタンス化してレンダリングすることができます。普通の「インスタンスされていない」プロパティーはバッチ処理をすることができません。そのため、それらを MaterialPropertyBlock に使用しないでください。代わりに、ほかのマテリアルを作成してください。
UnityObjectToClipPos(v.vertex) を使用する方が常に好ましいですが、そうでない場合は mul(UNITY_MATRIX_MVP,v.vertex) を使用します。インスタンス化したシェーダーで__UNITY_MATRIX_MVP__ を基準として使用できる一方、UnityObjectToClipPos は頂点位置をオブジェクト空間から頂点シェーダーの出力する位置 (クリップスペース) へ変換する効果的な方法です。
インスタンス化したシェーダーで UNITY_MATRIX_MVP (他のビルトインマトリックス) は、マトリックスの追加を含めるようわかりやすく変更されています。特に、 mul(UNITY_MATRIX_VP, unity_ObjectToWorld) に展開しています。 unity_ObjectToWorld は unity_ObjectToWorldArray[unity_InstanceID] に展開しています。UnityObjectToClipPos は 2 マトリックスベクトル乗算を同時に行うために最適化されています。シェーダーコンパイラーは自動的にこの最適化を行わないため、手動で乗算を行うより効果的です。