テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】パーティクルをGPU Instancingで描画してみる & 対応シェーダーを自作してみる

f:id:tsubaki_t1:20180130011142g:plain

Unity 2018.1では、Instancing関係の機能が大幅に使い勝手良くなりました。

特にメッシュパーティクルの描画はCPUバッチングではなくGPU Instancingで行えるようになり、幾つかのケースでパフォーマンスが大幅に向上する事があります。

 

例えば、パーティクルを描画するとレンダースレッドのGfx.ProcessCommandsが凄い事になっているような場合、この項目を見直すと劇的に改善するかもしれません。*1

f:id:tsubaki_t1:20180130103133j:plain

f:id:tsubaki_t1:20180130103136j:plain

 

他にもUnity 2018ではInstancingに多くの改良点がありますが、今回はパーティクルの内容に絞って紹介します。

 

目次

 

 

パーティクルとバッチング

パーティクルシステムは大量にParticle(粒子)を描画するシステムです。その描画量は十や百ではなく、千、一万といった量です。
この量のメッシュを毎回する事は出来なくはないですが非常にパフォーマンスに悪いです。GPUにDrawCallを送るだけで大仕事になってしまいます。

f:id:tsubaki_t1:20180129232214j:plain

そのため、Unityではメッシュをバッチング(CPUでメッシュを結合)したものをGPUへアプロードするアプローチを使用していました。
このアプローチであれば数万回のDrawCallは一回に抑えられます。

f:id:tsubaki_t1:20180129232222j:plain

このバッチングのアプローチですが、あまり高いポリゴンに対して使用出来ないという問題があります。なにせCPUでメッシュを結合する関係上、あまりポリゴンが多いとかなり高い負荷がかかります。

特にパーティクルとして出力するオブジェクトがMeshの場合、驚くほど高い負荷を出す事があります。

f:id:tsubaki_t1:20180129230403j:plain

これを確認するために、CubeとSphereのパーティクルを出力してみます。自分の環境ではSphereのパーティクルが3000個程度で30FPSを割る所を、Cubeならば12000個を超えても30は割りません。

f:id:tsubaki_t1:20180129231121j:plain

毛玉?いいや、パーティクルだヨ

 

GPU Instancingとバッチング

Unity 2018.1から、Mesh ParticleはバッチングではなくGPU Instancingによるバッチングが可能になりました。

GPU Instancingは同じメッシュの複数のコピーを一度に描画する機能です。機能には幾つかの制約がありますが、バッチングとくらべてポリゴンの多いモデルを大量に描画するのに向いています。

バッファと描画するモデルを渡して、GPU側で座標等をデコード、羅列する感じです。

f:id:tsubaki_t1:20180130011845j:plain

実際に試してみると、特にハイポリを量産した際に驚くほど高速になります。

モバイルやWebGLでも普通に使えるみたいです。

 

この機能を先のSphereのメッシュを使用したパーティクルに適応してみると、先程は3000程度で30FPSを割っていたSphereのパーティクルで試した所、26000個のパーティクルを描画しても超余裕で描画できることが確認できます。

f:id:tsubaki_t1:20180129232941j:plain

なお、あくまでバッチング処理をGPU側で行っているに近いので、描画自体の負荷は存在します。画面にミッシリとハイポリモデルを描画すればやはり重いです。

またQUADのような最低限のモデルの場合、逆に重くなるかもしれません。

さらに言えば、移動処理やライフタイム判定はCPUなので、沢山モジュールを使用すると重くなります。特にノイズは非常に重いので注意が必要です。

 

パーティクルのGPUInstancingを使用してみる

パーティクルのGPU Instancingを使用してみます。

 

まずパーティクルが使用するマテリアルを用意します。
シェーダーはParticles > Standard SurfaceもしくはStandard Unlitです。

f:id:tsubaki_t1:20180129233304j:plain

 

次にParticleSystemで表示するモデルを設定します。

Particle SystemのRendererのRenderModeをMeshに変更し、Meshを適当なものに変更します。

f:id:tsubaki_t1:20180130011509j:plain

RendererのMaterialを先程作成したMaterialに変更し、Enable GPU Instancingにチェックを入れておけば準備は完了です。

ゲームを再生・プロファイラーで確認した際、Instancingの項目のBatched Draw Callsが増えていれば成功です。

f:id:tsubaki_t1:20180129233434j:plain

f:id:tsubaki_t1:20180129233530j:plain

なおMaterialのEnable GPU Instancingの項目は無視されるっぽいです。チェックを入れていても外していても動作しました。

 

自作のシェーダーを使用する

せっかくInstancingが出来たので、今度は自作のシェーダーに試してみます。
例えば、パーティクルのシェーダーを、Textureを元にスキニングアニメーションするタイプのもにに差し替えたいといった場合や、単純なエフェクトを行うモデルを羅列表示するといった場合に有効かもしれません。

 

やることは超簡単です。

 #pragma instancing_options procedural:vertInstancingSetup#include "UnityStandardParticleInstancing.cginc"をシェーダーに追加するだけでOKです。

 後はInstancingが有効になるように、#pragma multi_compile_instancingUNITY_VERTEX_INPUT_INSTANCE_IDUNITY_SETUP_INSTANCE_ID(v);を設定するだけです。

 

実際のコード

頂点ベースで回転するだけのシェーダーを用意しました。これをパーティクルのGPU Instancingに対応させます。

最初の状態では、単純にモデルが回転します。これをパーティクルにて廻してみると、モデル単体ではなくパーティクル全体が回ってしまいます
これはバッチングされたメッシュが回転してしまっているためです。

f:id:tsubaki_t1:20180130005325g:plain

f:id:tsubaki_t1:20180130005831g:plain

コードを変更してGPU Instancingに対応させます。
日本語のコメントが付いている項目が追記した項目です。

 

 

gist.github.com

対応させると、パーティクルが生成されるようになります。またProfilerで確認するとInstancingから生成されていれば成功です。

f:id:tsubaki_t1:20180130005428g:plain

 

関連

今回使用したStandard Particle Syaderについて

Unity 2018には標準搭載されています。

tsubakit1.hateblo.jp

シェーダーでアニメーションを行うアプローチです。単純なものにしか使用できませんが、パフォーマンスはAnimatorよりも良い事が多いです。

tsubakit1.hateblo.jp

回転シェーダーはコチラに含まれているアセットを参考に作成しました。

tsubakit1.hateblo.jp

*1:この項目を計測する時はvsynkは無効にする