UnityでGPUインスタンシングを使う上での基礎知識とシェーダの書き方をまとめました。
- GPUインスタンシングとは
- Standard Shaderで位置やスケールの異なるモデルを一気に描画する
- GPUインスタンシングに対応したシェーダを書く
- 位置やスケール以外のプロパティが異なるモデルを一気に描画する
- GPUが対応している必要がある
- 関連
- 参考
Unity2019.2.6
GPUインスタンシングとは
GPUインスタンシングとは、要は「同じメッシュを一回のドローコールでいっぱい描画できる」機能です。
木や草や岩など、大量に同じメッシュを配置するようなケースで効果を発揮します。
また、GPUの機能を使うので、Static Bachingのように事前にメッシュを結合したデータを持つ必要がありません。
ただしシェーダはこの機能に対応した書き方をしたものを使う必要があります。
Standard Shaderで位置やスケールの異なるモデルを一気に描画する
さてそれではまずStandard Shaderを使って簡単にGPUインスタンシングの効果を体験してみます。
適当に位置とスケールの異なるメッシュを大量に配置しておきます。
今回は下図のようにCubeとSphereの二種類のメッシュを大量に配置しました。
次にこれらにStandardシェーダを設定したMaterialをアサインします。
この時点でStatisticsを見ると、Batchesは780となっており大量のドローコールが発生していることがわかります。
そこでGPUインスタンシングを使ってドローコールを減らします。
GPUインスタンシングを有効にするには、先ほどのMaterialのInspector下部から
Enable GPU Instancingにチェックを入れるだけです。
再度Statisticsを見ると、Saved by batchingが763となりBachesが17まで減っていることがわかります。
Frame Debuggerを見てもメッシュの描画自体はたった4回のドローコールで行われていました。
GPUインスタンシングに対応したシェーダを書く
前節のようにUnityの組み込みシェーダを使うと簡単にGPUインスタンシングできます。
しかし位置やスケール以外のプロパティをGPUインスタンシングに対応させたり、
自分で作ったシェーダでGPUインスタンシングをする場合には対応したシェーダを書く必要があります。
といってもUnityが決めた書き方に従うだけなので難しいことはありません。
まずは位置とスケールをメッシュごとに変えられるシンプルなGPUインスタンシングに対応したシェーダを書いてみます。
Shader "Example" { Properties { _Color("Color", Color) = (1, 1, 1, 1) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // インスタンシング用のpragma #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; // インスタンスIDを頂点情報として受け取る UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; }; half4 _Color; v2f vert(appdata v) { v2f o; // インスタンスIDを元に位置やスケールを反映 UNITY_SETUP_INSTANCE_ID(v); o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return _Color; } ENDCG } } }
わかりやすくするため、ライティングなしの単色を返すシェーダにしました。
GPUインスタンシング用の処理はコメントを書いて部分のみです。
これを適用してEnable GPU Instancingにチェックを入れるとGPUインスタンシングが効いていることが確認できます。
位置やスケール以外のプロパティが異なるモデルを一気に描画する
色など、位置やスケール以外のプロパティが異なるモデルをGPUインスタンシングで描画したい場合にはもうひと手間必要です。
ここでは色が異なるモデルを描画するためのシェーダを書いてみます。
Shader "Example" { Properties { _Color("Color", Color) = (1, 1, 1, 1) } SubShader { 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; // フラグメントシェーダにインスタンスIDを受け渡す 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 { // インスタンスIDに応じた色を取得する UNITY_SETUP_INSTANCE_ID(i); return UNITY_ACCESS_INSTANCED_PROP(Props, _Color); } ENDCG } } }
_ColorというプロパティをGPUインスタンシングに対応させています。
細かい説明はコメントに書いた通りです。
このようにして定義したプロパティの値は、下記のようにMaterialPropertyBlockを通じて設定します。
using UnityEngine; public class Example : MonoBehaviour { private void Start() { var materialPropertyBlock = new MaterialPropertyBlock(); foreach (var renderer in GetComponentsInChildren<MeshRenderer>()) { renderer.GetPropertyBlock(materialPropertyBlock); // MaterialPropertyBlockを使って色を指定 materialPropertyBlock.SetColor("_Color", Random.ColorHSV()); renderer.SetPropertyBlock(materialPropertyBlock); } } }
これにより色違いのメッシュをGPUインスタンシングによって効率的に描画できるようになりました。
もちろんPBRのシェーダに対応させれば下図のようにライティングすることも可能です。
ちなみにMaterialPropertyBlockの詳細については以下の記事にまとめていますので、必要に応じて参照してください。
GPUが対応している必要がある
GPUインスタンシングはGPUの機能を使うため、GPUが対応していないと使えません。
モバイルだとMetal, Vulkanの他、Open GLES3.0以上が対応しているようです。
また、SystemInfo.supportsInstancing
でサポート状況をチェックできます。