VFX Graph(Visual Effect Graph)は、何十万もの超高密度なパーティクルをGPU(Compute Shader)で描画するための強力なツールです。しかし、その内部的なメモリ管理の性質上、デスクトップPC向けの開発手法のままでモバイル向け(iOS/Android)のプロジェクトを作成すると、瞬く間にVRAM(ビデオメモリ)が枯渇し、OOM(Out of Memory)によるOSからのアプリ強制終了を引き起こす凶器へと変貌します。本記事では、VFX GraphがGPUメモリを消費する詳細な仕組みと、モバイル実機でも安全に動作させるための最適化手法を解説します。

1. VFX GraphのGPUバッファプレアロケーション(事前確保)の罠

Shuriken(従来のCPUベースのParticle System)との最大の違いは、VFX GraphがすべてのパーティクルデータをGPU上の構造化バッファ(StructuredBuffer / GraphicsBuffer)に格納して制御する点にあります。このバッファは、「エフェクトが現在何個のパーティクルを表示しているか」に関係なく、エフェクトのインスタンスが有効化された瞬間に、VFX内の設定値『Capacity』の数だけメモリを事前に確保します。

VRAM上に確保されるバッファの物理的なデータサイズは、以下の計算式によって厳密に定義されます。

確保バッファサイズ(バイト) = Capacity(最大パーティクル数) × 属性データサイズ合計(バイト) × 2(Double Buffering用)

ここで重要なのは「属性データサイズ合計」です。VFX Graphは、パーティクルごとに様々な「属性(Attribute)」を保持します。例えば、位置(`position`:Vector3 = 12バイト)、速度(`velocity`:Vector3 = 12バイト)、色(`color`:Vector4 = 16バイト)、サイズ(`size`:Float = 4バイト)、寿命と経過時間(`lifetime`, `age`:Float × 2 = 8バイト)といったデータです。これらを合計すると、シンプルなパーティクルであっても1粒子あたり約50〜100バイトのメモリが必要になります。

もし、あるエフェクトの `Capacity` を深く考えずに「`100,000`(10万)」に設定していた場合、1つのインスタンスだけで:

100,000 × 80バイト × 2 = 約16,000,000バイト (約16MB)

のVRAMが**ゲームオブジェクトがシーン内に存在するだけで常に消費**されます。これをゲーム内のエネミーの死亡エフェクトや武器のヒットエフェクトなどとして10箇所でインスタンス化すると、それだけで **160MB** ものVRAMがアイドル時でも失われることになります。モバイル端末はCPUとGPUがメモリを共有する「Unified Memory(ユニファイドメモリ)」アーキテクチャを採用しているため、VRAMの過剰消費はシステムメモリ全体の枯渇に直結し、即座にOOMクラッシュを引き起こします。

2. 属性(Attributes)の無駄を徹底的に排除する

メモリ消費を削減するためのアプローチとして、`Capacity` を減らすことと並んで強力なのが、**「1パーティクルあたりの属性バイトサイズを削る」**ことです。VFX Graphは、グラフ内で一度でも `Set Attribute` や `Get Attribute` ノードで呼び出された、あるいは暗黙の計算(PhysicsやCollisionなど)に利用された属性を自動的に検出し、バッファの構造体に組み込みます。

多くの開発者は、実験段階で使用したノードをグラフ上に残したままにしていたり、別の値で代用可能なデータを別個の属性として保存してしまっています。例えば、パーティクルの色を「時間変化」で変更する際、`color` 属性(16バイト)をバッファに保存し続けるのではなく、グラデーション用のテクスチャから `age / lifetime` の比率(0〜1)を用いてシェーダー内のVertex/Fragmentステージで動的に計算(サンプリング)するように設計すれば、`color` 属性そのものをバッファから丸ごと削除でき、1パーティクルあたり16バイトを節約できます。

3. モバイル向けVFX Graph最適化のための3箇条

実務でVFX Graphを使用する際は、以下の最適化設定とチェックリストをパイプラインに導入してください。

① Capacity(容量)の厳密な数式設計

インスペクター上で `Capacity` の値を手動で適当なキリのいい数値(例: 1000 や 10000)で入力するのをやめます。`Capacity` は、パーティクルの生成頻度である `Rate`(1秒あたりの生成数)と、パーティクルの最大寿命 `Lifetime` から、以下の計算式で上限値を求めます。

適正なCapacity = (Rate × 最大Lifetime) × 1.2(予備の安全マージン20%)

例えば、1秒間に50個のパーティクルを生成し、寿命が最長3秒のエフェクトであれば、必要な容量は `50 × 3 × 1.2 = 180` 個です。これをデフォルト値である `1,000` のまま放置せず、インスペクターの `Capacity` に「`180`」と正確に入力します。これだけで、メモリ消費量を約82%削減できます。

② VFX Graphの「Attributes List」の監査

VFX Graphエディターの左上にある設定ウィンドウ(あるいはVFXアセットのインスペクター)から、そのグラフがGPUバッファに要求している「Attributes(属性)」の一覧を確認します。もし、使用していない属性(例: `targetPosition` や `size3D`、`axisX` など)がバッファ確保対象に含まれている場合は、グラフ内でそれらを設定・取得している未使用ノード(グレーアウトしている死にノードなど)を完全に削除します。コンパイル後にバッファの構成バイト数が小さくなっていることを確認してください。

③ Dynamic Spawn Control(動的生成制御)の導入

大量のオブジェクトが同時に死亡するなどして、一時的にVFXの生成数が増えるシーンでは、C#スクリプトからVFXのSpawn Rateを動的にコントロールし、画面全体の総パーティクル上限数(VFX Manager等で一括管理)を超えないように制限します。また、エフェクトの再生が終わった(あるいは画面外で見えなくなった)ゲームオブジェクトは、ただちに `SetActive(false)` にするか `Destroy` し、GPUバッファのプリロケート領域を即座に解放(Metal/Vulkanのメモリプールへ返却)するライフサイクル管理を徹底します。