はじめに
(界隈には今さらな技術かもしれませんが…。)
あるメッシュを大量にレンダリングしたいとき、Unityではいくつか方法がありますがGPU Instancingはその中で強力な手段の1つです。
特に今回はUnityが用意している2つの描画用API Graphics.DrawMeshInstanced()
(以下DMI) と Graphics.DrawMeshInstancedIndirect()
(以下DMI2)の簡単な実装例とそれぞれのメリットデメリットをわかる範囲で整理します。
Graphics.DrawMeshInstanced(DMI)について
主な引数
引数 | 型 | 概要 |
---|---|---|
mesh | Mesh | インスタンスさせたいメッシュ |
submeshIndex | int | そのメッシュのサブセット |
material | Material | 使用するマテリアル |
matrices | Matrix4x4[] | 位置、回転、スケールを評価する行列 |
count | int | インスタンスさせたい数 |
properties | MaterialPropertyBlock | 同マテリアルだがインスタンスごとに変えたいパラメータ |
簡単な使用例を見ていきます。
1000個のQuad Meshを生成します。
DMIの欠点としてComputeShaderの都合、インスタンスが最大で1023個までしか生成できないことがよく挙げられます。
しかし、これも1023個を1束としたバッチ処理で回避することができます。
ためしに10000個のQuad Meshを生成します。
話は横道にそれますが、インスタンスそれぞれの影が出ないのはやはり違和感なので、そこだけSubShaderを追加したのも載せておきます。(影の描画描画周りは少しややこしいです。)
上の実装からもわかるようにDMIは気軽にGPU Instancingさせることが可能な一方で、メッシュのマトリックス情報の更新はCPU側からGPUに毎フレーム送る必要があります。つまり大量のインスタンスをそれぞれ移動させたり回転させたりするのはかなり厳しいです。(バッチ処理するとなると尚更です。)
そこで出てくるのが次項の DMI2 です。
Graphics.DrawMeshInstancedIndirect(DMI2)について
主な引数
引数 | 型 | 概要 |
---|---|---|
mesh | Mesh | インスタンスさせたいメッシュ |
submeshIndex | int | そのメッシュのサブセット |
material | Material | 使用するマテリアル |
bounds | Bounds | 描画範囲 |
bufferWithArgs | ComputeBuffer | (後述) |
最後のbufferWithArgs
についてはメッシュを描画するのに必要な残りの情報をぶち込めるようになっています。公式でこれをまとめている部分が見当たらないのですがネットの先人らの知恵を参考にまとめたのが以下です。
引数 | 概要 |
---|---|
0 | コピーしたいメッシュ1つあたりの頂点数 |
1 | 何個コピーしたいのか |
2 | メッシュの開始インデックス |
3 | メッシュの頂点インデックス |
4 | 生成し始めるインスタンスのインデックス |
DMI2はGPU側にインスタンシングに必要な情報を持たせることができるのでDMIのようにCPUとのやりとりによる負荷はそこまでネックになりません。また1023個の制限もなくなります。
ここでも簡単な例を見て見ましょう。
40000個のQuad Meshを生成します。今度は最初から影付きです。
いい感じです。ちなみにカメラを適当に動かしても100FPSくらい出てます。
インターネットにはGPU InstancingとCumputeShaderの合わせ技でかっこいい演出を施す記事が多いのですが本来これらは別の技術です。1個前の例のようにDMI2を使ってGPU Instancingをさせるだけでも良いし、以前書いたこの記事のようにCompute ShaderだけでプリミティブはCPUでなんとかするでも良いわけです。
おわりに
今回はUnityに搭載されているGPUインスタンス用APIのDMIとDMI2についてまとめました。
とある案件でShaderGraphを使って100KオーダーのGPU Instancingをさせたいとなり調査をしていくと、DMIはできるけどDMI2では難しそうということがわかりました。そもそも僕自身がDMIは使ったことあるけどDMI2を使ったことない状態だったので一度機能について整理してみようというのが記事を書くきっかけでした。
一応このフォーラムをみるに、有志の方がカスタムノードを提供していて、それを使えばShaderGraphでもDMI2を実装できなくはないようです。個人的にはノードとコードを行き来しながら作業するのはなるべく少なくしたいという思いがあるのでせめて公式が専用ノードを出すまではコード書くのでいいかな、と思っています。
それからDMIとDMI2の中間的立ち位置にGraphics.DrawMeshInstancedProceduralというAPIがありますが、今回は時間の都合、触れませんでした。DMI2とそこまで違いはありません。
参考
- Drawing Thousands of Meshes with DrawMeshInstanced / Indirect in Unity
- 【Unity】Graphics.DrawMeshInstancedを使う - Qiita
- Unity Shader Programming Vol.06 (v.1.0.0)【PDF】 - XJINE's
- Graphics.DrawMeshInstancedIndirectまとめ - なおしのこれまで、これから
- 「IDOLY PRIDE」における描画最適化術 - QualiArts engineer blog
- 【Unity】ComputeShader + GPU Instancingで大量のドカベンOPのロゴアニメーションを動かしてみた - Qiita