見出し画像

パーティクルの軽量化について調べてみた

 昨日は、hattori ∞さんの「モンスター AI」の記事でした。
 なんと、モンハンライクなゲームを作っているとのこと。完成が楽しみです。

 それでは、Cluster #1 Advent Calendar 2024の6日目の記事になります。
 パーティクルの軽量化について調べてみたのですが、長文になってしまったので、「7.まとめ」だけ眺めてもらえれば十分です💦。


1.はじめに

(1)自己紹介

 はじめまして。Clusterの2024年10月のお題企画「音楽イベント会場をつくろう!」で、「パーティクルしかないところ」というワールドで、スワンマン賞を受賞した人です。

(2)記事の内容

 この受賞したワールドは、50種以上のパーティクルを使って演出することができます。
 しかし、パーティクルは綺麗だけど重くなりやすいので、パーティクルの軽量化について調べてみました。
 これから、パーティクルを使ったワールドを作成する人の参考になれば、幸いです。

2.負荷の確認方法

 ワールドが重い場合、「ワールド容量」の問題と、「描画負荷」の問題があります。

(1)ワールド容量の確認方法

 Cluster上で確認するには、ワールド説明欄にあるデータサイズが、ワールド容量になります。
 このデータサイズが大きいと、入室するまでのロード時間が長くなり、メモリの消費量が大きくなるので落ちやすくなります。

データサイズ 10MB

 今回は、詳細に検討したいので、Unity上でワールドの容量を確認します。
 まず、ワールドアップロード後に、Consoleビューの右上にある三本線メニューから、OpenEditorLogを選択します。
 すると、Editor.logというファイルが開くので、このファイルの中で「Bundle Name:」という文字列で検索して、以下の該当するログを見つけます。(ファイルの後ろの方にあるので、一番後ろからスライドさせて、該当するログを見つけることもできます。)
 このログの中のCompressed Sizeが、データサイズに相当します。

Compressed Size: 10.8mb

(2)描画負荷の確認方法

 Unityの再生中に、ゲームビューの右上にあるStatsをクリックすると、レンダリング統計ウィンドウ(Statistics)が表示されます。

Statisticsの主な指標を示します。
 FPS:Unity が 1 秒間に描画可能な現在のフレーム数。
 Batches:1 フレームで Unity が処理する ドローコールバッチ の総数。
 SetPass Calls:そのフレームでゲームオブジェクトのレンダリングに使用するシェーダーパスを Unity が切り替えた回数。
 Tris:Unity が 1 フレーム中に処理する 三角形 の数。
 Verts :Unity が 1 フレーム中に処理する 頂点 の数。

 描画工程をざっくりと説明すると、CPUがGPUに描画を指示し、GPUが描画することによりフレームが描画されます。
 このため、描画負荷は、CPUの負荷と、GPUの負荷に分けられます。
 上記指標のうち、BatchesとSetPass Callsの値が大きくなるほど、CPUの負荷が大きくなります。
 また、TrisとVertsの値が大きくなるほど、GPUの負荷が大きくなります。
 そして、CPUの負荷とGPUの負荷が大きくなると、最終的に、1 秒間に描画可能なフレーム数:FPSが小さくなり、画面がカクカクすることになります。

3.比較するワールドの構成

 比較用のワールドとして、次のワールドを比較します。

(1)基本ワールド

 Partile Systemのないワールド

 なお、ワールドの最小構成として、Spawn Point、Despawn Height、Plane(Collider)があれば十分ですが、パーティクルの演出を考慮して、Bloomを設定したPost-process Volumeを加えています。

(2)パーティクルワールド

 Particle Systemが1つあるワールド

4.ワールド容量の比較

 2.(1)の方法で、各比較用のワールドの容量を比較します。
 下表で示されるように、パーティクルが1つあることによって、約0.02MBだけ容量が増えました。
 したがって、パーティクルによるワールド容量への影響は、非常に少ないといえます。

ワールド容量

5.描画負荷の比較

 2.(2)の方法で、各比較用のワールドの描画負荷を比較します。

 まず、基本ワールドの描画負荷を示します。
 なお、FPSは、ご使用の端末に依存します(以下、同様)。

Statistics

 次に、パーティクルワールドの描画負荷は、Particle Systemのパラメータに依存するので、様々なパラメータを変えたときの描画負荷を測定します。

(1)パーティクルの発生数

 パーティクルの発生数を変えたときの描画負荷を測定します。
 下図のように、初期のParticle Systemから、次のパラメータを変更します。
 Mainモジュール
  Start Lifetime: 1      パーティクルの寿命[秒]
  Max Particles: 100000  パーティクルの最大数[個]
 Emissionモジュール
  Rate over Time: 1000 1秒あたりに発生するパーティクルの数[個/秒]

 このとき、Start Lifetimeが1[秒]なので、Rate over Timeが、発生しているパーティクルの数を表します。
 なお、Particle Systemの位置は、観察者から20[m]離れた位置に配置しています。

 Rate over Timeを、0, 1000, 10000, 100000と変化させた結果を示します。

Statistics

 BatchesとSetPass callsは、基本ワールドと比較して、パーティクルの発生数にかかわらず、1だけ増えました。
 TrisとVertsは、基本ワールドと比較して、それぞれパーティクルの発生数×2、パーティクルの発生数×4増えました。

 BatchesとSetPass callsは、1だけ増えましたが、Particle Systemを多数配置しない限り、CPUの負荷は少ないことがわかります。

 一方、TrisとVertsは、パーティクルの発生数に比例して増えるので、パーティクルの発生数が増えると、GPUの負荷は多くなります。
 このため、GPUの負荷が多くなることによって、FPSが下がるものと考えられます。

(2)トレイルの有無

 トレイルの有無に対する描画負荷を測定します。
 (1)と比較して、Trailsモジュールを追加しました。

Statistics

 BatchesとSetPass callsは、基本ワールドと比較して、パーティクルの発生数にかかわらず、パーティクルとトレイルの、2だけ増えました。
 TrisとVertsは、基本ワールドと比較して、パーティクルの発生数×25~30も増えました。

 BatchesとSetPass callsは、2だけ増えましたが、Particle Systemを多数配置しない限り、CPUの負荷は少ないことがわかります。

 一方、TrisとVertsは、パーティクルの発生数に比例して増えるので、パーティクルの発生数が増えると、GPUの負荷は多くなります。
 このため、GPUの負荷が多くなることによって、FPSが下がるものと考えられます。
 また、TrisとVertsの増え方は、(1)のトレイルがない場合と比べて、10倍程度大きいため、パーティクルの発生数が少なくても、FPSは急激に下がります。

注釈
 パーティクルを表示せずに、トレイルのみを表示する場合(RendererモジュールのRender Mode: None)は、基本ワールドと比較して、Batchesは、1増えますが、SetPass callsは、2増えます。
 パーティクルのマテリアルと、トレイルのマテリアルとで、同じマテリアルを使うと、基本ワールドと比較して、Batchesは、2増えますが、SetPass callsは、1増えます。

追記
 hkさん(X: @livevr3)から情報提供して頂いたので、追記します。hkさん、ありがとうございました。

 Tris(三角形 の数)とVerts(頂点 の数)は、(パーティクルの発生数)×(トレイルの長さ)/(トレイルの頂点間距離)に、概ね比例して増加します。
 そして、TrisとVertsが増加すると、FPSが下がります。

 ここで、パーティクルの速度が一定のとき、
 (トレイルの長さ)=(メインモジュールのStart Lifetime)×(メインモジュールのStart Speed)×(Trails モジュールのLifetime)
 (トレイルの頂点間距離)=(Trails モジュールのMinimum Vertex Distance)
で表されます。

(3)パーティクルの大きさ

 パーティクルの大きさを変えたときの描画負荷を測定します。
 (1)と比較して、パーティクルの大きさを1[m]から、0.1[m]に小さくしました。    
 Mainモジュール
  Start Size: 1 → 0.1      パーティクルの大きさ([m])

FPS

 (1)のStart Sizeが1[m]の場合と比較して、Batches、SetPass calls、Tris、Vertsの値は変わりませんでした。
 一方、Start Sizeが0.1[m]の場合、FPSは大きくなりました。
 したがって、パーティクルの大きさが小さい方が、描画負荷は小さくなることがわかります。

(4)パーティクルと観察者との距離

 パーティクルと観察者との距離を変えたときの描画負荷を測定します。
 (1)と比較して、パーティクルと観察者との距離を20[m]から、10[m]に近づけました。 

FPS

 パーティクルと観察者との距離が20[m]の場合と比較して、Batches、SetPass calls、Tris、Vertsの値は変わりませんでした。
 一方、パーティクルと観察者との距離が10[m]の場合、FPSは小さくなりました。
 したがって、パーティクルに近づくことによって、パーティクルの見かけの大きさが大きくなると、描画負荷は大きくなることがわかります。

(5)Culling Mode

 MainモジュールのCulling Modeによって、パーティクルがオフスクリーンのときにパーティクルシステムのシミュレーションを一時停止するかどうかを選択するとされています。
 しかし、私の環境では、パーティクルがオフスクリーンのとき(例えば、背後にパーティクルがあって、見えていない場合)には、どのCulling Modeを選択しても、パーティクルのない基本ワールドと、ほぼ同じ描画負荷を示しました。

注釈
 パーティクルの一部がオンスクリーンのとき(パーティクルの一部が見えているとき)は、パーティクルの全部がオンスクリーンのときと、ほぼ同じ描画負荷を示しました。

(6)シェーダー

 Particle Systemは、デフォルトでParticle/Standard系シェーダーが用いられています。
 一方、シェーダーとして、Mobile/Particles系シェーダーを用いると、処理 が軽くなるとされています。
 しかし、私の環境では、Mobile/Particles系シェーダーを用いても、描画負荷に大きな変化はありませんでした。

(7)その他のパラメータ

 パーティクルの色・濃淡を変えても、描画負荷に大きな変化はありませんでした。
 パーティクルの動きを変えても、描画負荷に大きな変化はありませんでした。
 その他のParticle Systemのパラメータを変えても、描画負荷に変化はありませんでした。

(8)描画負荷のまとめ

・パーティクルの発生数が多いほど、描画負荷が大きくなる。
・トレイルがあると、描画負荷が大きくなる。
・パーティクルの大きさが大きいほど、描画負荷が大きくなる。
・パーティクルと観察者との距離が近く、パーティクルの見かけの大きさが大きいほど、描画負荷が大きくなる。
・パーティクルのすべてがオフスクリーンのときは、パーティクルの描画負荷はなくなる。
・その他のパラメータを変化させても、描画負荷は変わらない。

 描画負荷は、主に、Tris(1 フレーム中に処理する 三角形 の数)とVerts( 1 フレーム中に処理する 頂点 の数)とが、パーティクルの発生数に比例して増加することにより、GPUの負荷が増大することが原因と考えられます。
 FPSの減少は、ご使用の端末によりますが、1つの目安としては、パーティクルの数を10,000~100,000個以下に抑える必要がありそうです。

6.パーティクルの軽量化

 以上の結果を踏まえると、ワールド容量への影響は、非常に少なく、パーティクルによるCPUの描画負荷は、Particle Systemを多数配置しない限り、少ないことがわかりました。
 一方、パーティクルによるGPUの負荷が大きくなって、FPSが下がっていると考えられるので、GPUの負荷の軽量化について検討します。

(1)パーティクルの発生数を少なくし、大きさを小さくする

 5.(1)、5.(3)、5.(4)によれば、パーティクルの発生数が多いほど、パーティクルの大きさが大きいほど、描画負荷が大きくなります。
 このため、パーティクルの発生数を少なくし、パーティクルの大きさを小さくするかわりに、パーティクルの輝度を上げることによって、見た目の印象をあまり変えないまま、軽量化することが可能と考えられます。

参考:
パーティクルの輝度を上げ方
 下図のように、パーティクルのマテリアルを設定します。
 Shader: Particles/Standard Unlit
 Rendering Mode: Additive
 Color Mode: Muliply

 次に、Albedoの左に、テクスチャ画像をドラッグ&ドロップし、Albedoの右のHDRを選択して、HDR Colorウィンドウを立ち上げて、(R,G,B)=(4,4,4)など、1より大きな白色とします。
 これにより、パーティクルの輝度が上がり、Post-process VolumeのBloomにより、光り輝くようになります。

注釈
 マテリアルのEmissionを用いて、光り輝かせることもできますが、このとき、パーティクルは、マテリアルのEmissionで設定した色に光り輝くため、Particle Systemのパラメータでは、色の変更が難しくなります。
 このため、Emissionを使わずに、AlbedoのHDRで設定することをお勧めします。

(2)トレイルの頂点間距離を長くする

 5.(2)によれば、トレイルは、パーティクルと比較して、描画負荷が大きくなります。
 原因は、TrisとVertsの増加によるものですが、TrisとVertsは、概ね(トレイルの長さ)に比例し、(トレイルの頂点間距離)に反比例します。
 したがって、(トレイルの頂点間距離)を長くすることによって、すなわち、(Trails モジュールのMinimum Vertex Distance)の値を大きくすることによって、パーティクルを軽量化することができます。

 ただし、トレイルの頂点間は、直線で表示されるので、(トレイルの頂点間距離)を長くしすぎると、カクカクしたトレイルになります。
 したがって、見た目の印象をあまり変えない範囲内で、(Trails モジュールのMinimum Vertex Distance)の値を大きくします。

(3)LOD

 LOD を用いて、カメラからの距離に応じて、パーティクルの発生数を変えたときの描画負荷を測定します。
 以下では、次のようにLODを設定しました。
LOD 0:パーティクルの発生数が100,000のParticle System
LOD 1:パーティクルの発生数が10,000のParticle System
LOD 2:パーティクルの発生数が1,000のParticle System

 なお、パーティクルと観察者との距離を変えると、描画負荷が変わってしまうので、LOD間のTransitionを変更することにより、パーティクルと観察者との距離を20[m]のまま、描画負荷を測定しました。

FPS

 LODを設定した場合は、パーティクルの発生数が同じのLODを設定していない場合と、ほぼ同じ描画負荷を示しました。
 これにより、遠くから見たときの描画負荷を自動的に減らし、パーティクルを軽量化することができます。

(4)Occlusion Culling

 Occlusion Culling を用いて、Staticなオブジェクトに隠れたパーティクルを表示しないときの描画負荷を測定します。
 Staticなオブジェクトに隠れるパーティクルの発生数を、1000, 10000, 100000と変化させた結果を示します。

FPS
FPS
FPS

 パーティクルがStaticなオブジェクトに隠れていて、パーティクルを表示しないときは、TrisとVertsは小さいままであるにもかかわらず、パーティクルがない場合と比較して、FPSは小さくなります。
 また、パーティクルがStaticなオブジェクトに隠れておらず、パーティクルが表示されているときも、Occlusion Culling が設定されていない場合と比較して、FPSは小さくなります。
 結論としては、Occlusion Culling を設定しても、パーティクルを軽量化することはできません。

7.まとめ

(1)パーティクルの負荷

・ワールド容量への影響は、非常に少ない【4.】
・CPUの描画負荷は、Particle Systemを多数配置しない限り、少ない【5.(1)】
・以下の場合、GPUの描画負荷は、多い
 パーティクルの発生数が多い【5.(1)】
 トレイルを用いる【5.(2)】
 パーティクルの大きさが大きい【5.(3)】
 パーティクルの見かけの大きさが大きい(観察者との距離が近い)【5.(4)】
・その他のパラメータを変化させても、GPUの描画負荷は変わらない【5.(7)】

(2)パーティクルの軽量化

・有効な軽量化
 パーティクルの発生数を少なくし、大きさを小さくする【6.(1)】
 トレイルの頂点間距離を長くする【6.(2)】
 LOD【6.(3)】

・有効でない軽量化
 Culling Mode【5.(5)】
 Mobile/Particles系シェーダー【5.(6)】
 Occlusion Culling【6.(4)】

8.あとがき

 以上、パーティクルの軽量化について、書かせていただきました。
 長文にお付き合いくださり、ありがとうございます。

 明日のCluster #1 Advent Calendar 2024は、今年、多くのイベントで大活躍された焔さんが、イベントのスタッフについて記事にされる予定です。
 楽しみにして待ちましょう。

いいなと思ったら応援しよう!