3DGS レンダリングの仕組みとビデオメモリ管理
データがどのような形でビデオメモリに入りレンダリングに参加するか、およびビデオメモリの割り当てルールと容量上限を説明する。画面パラメーターの調整は 画面調節 を、読み込み操作は クイックスタート を参照する。
用語の定義:データを分割した後の最小の読み込み単位かつレンダリング単位をノード (Node) と呼ぶ。各ノードの精度は Level で示し、Level 0 が最も精度が高く、Level が大きくなるほど精度が低くなる。この定義は パフォーマンス最適化パラメーター と一致する。
分割レンダリングと全量レンダリング
データを読み込んだ後のレンダリング方式は 2 種類ある。可視ノードを必要に応じて読み込む(分割レンダリング)か、データ全体を一度に読み込む(全量レンダリング)。既定の動作はパイプラインごとに異なる。
| パイプライン | 形式 | 既定モード |
|---|---|---|
| LCC2 | .lcc2 / .ply / .spz / .sog | 分割レンダリング |
| LCC | .lcc | 全量レンダリング。データ量がしきい値を超えると分割レンダリングにフォールバック |
分割レンダリング
データは空間ノードに分割され、各ノードは複数の Level を持つ。1 フレームあたりの処理の流れは次のとおり。
トラバース(カメラ位置と向きで可視ノードを絞り込み、各ノードの Level を選定)
│
▼
読み込み(選択されたノードのデータをメモリに読み込む)
│
▼
アップロード(データをビデオメモリに送る)
│
▼
レンダリング
近くのノードは低い Level を使い、遠くのノードは高い Level を使い、不可視のノードは読み込まない。視野から外れたノードはすぐには解放されず、再度視野に入ったときにそのまま再利用できるようキャッシュに保持される。ビデオメモリが不足したときにのみ、最終レンダリング時刻などの条件に基づいて最も長く使われていないノードを破棄する。ビデオメモリの使用量は主に現在の可視範囲で決まるため、大規模なシーンにも対応できる。
2 つのパイプラインでは描画の送信方式が異なる。
| パイプライン | 描画方式 |
|---|---|
| LCC | ノードごとに 1 回ずつ描画を送信するため、ノード数が増えるとドローコールも同時に増える |
| LCC2 | すべての可視ノードのデータを統一 Buffer に集約し、1 回だけ描画を送信する |
分割レンダリングに固有のコスト:
- 隣接するノードに異なる Level が割り当てられることがあり、境界部分で密度や鮮明さに差が生じてノードの境界が見えることがある
- カメラの移動中に Level が切り替わり続けるため、ディテールが飛ぶように変化することがある
全量レンダリング
カメラでノードを絞り込む処理をスキップし、データ全体を一度に読み込んでビデオメモリに常駐させる。カメラを動かしてもトラバースと Level の選定をやり直さない。
利点:
- シーン全体で統一された Level を使うため、ノード間の Level 差がなく、ノードの境界が見えない
- データがビデオメモリに常駐するため、カメラ移動時に読み込み、解放、ディテールの飛びが発生しない
- フレームごとのトラバースとアップロードのコストがなくなり、フレームレートが安定する
引き換えにデータがすべてビデオメモリに常駐するため、使用量はシーン規模に比例して増える。したがって小規模なシーンにのみ適する。
全量レンダリングの判定条件
全量レンダリングを使うかは読み込み時に決まり、Use Full Load スイッチと Level 0 の splat 数で決まる。
Load(データファイル)
│
▼
Level 0 の splat 数を読み取る
│
▼
Use Full Load はオン? ── いいえ ──▶ 分割レンダリング
│ はい
▼
splat 数 ≤ Full Load Splat Number? ── いいえ ──▶ 分割レンダリング
│ はい
▼
全量レンダリング
2 つのプロパティはいずれも Actor の Details パネルの Performance カテゴリーにある。
| プロパティ | 説明 |
|---|---|
| Use Full Load | 全量レンダリングの使用を許可するか |
| Full Load Splat Number | 全量レンダリングを許可する Level 0 splat 数の上限(単位:万、既定値 1500) |
既定値は Actor の種類ごとに異なる。
| Actor | Use Full Load の既定値 | 理由 |
|---|---|---|
| ALCCActor | オン | ノード間の Level 差が目立ち、ノードの境界が見え、さらにノードごとにドローコールを 1 回消費するため、小規模シーンでは全量レンダリングのほうが結果が良い |
| ALCC2Actor / APlyActor / ASpzActor / ASogActor | オフ | Level の切り替えが画面上の誤差に応じて連続的に遷移してノードの境界が目立たず、ドローコールも 1 回だけで済むため、既定の必要に応じた読み込みのほうがビデオメモリを節約できる |
LCC2 で全量レンダリングを手動で有効にする場面
LCC2 パイプラインは全量レンダリングが既定でオフになっている。次のような場面では手動で有効にできる。
- シーン規模が限られていてビデオメモリに余裕があり、画質とフレームレートの両方を安定させたい場合
- カメラが高速に移動したり頻繁にテレポートしたりして、ノードの読み込みが追いつかず、穴やディテールの飛びが発生する場合
- 製品レベルの展示、画面収録、シーケンスレンダリングなど、読み込み中の画面変化が許容されない場合
- ノードの境界部分の密度差が依然として見える場合
有効にした結果ビデオメモリが厳しくなったりフレームレートが下がったりする場合、そのシーンは全量レンダリングの適用範囲を超えているので、分割レンダリングに戻す。
注:全量レンダリングでも最大レンダリング距離の制限は受ける。カメラとデータの距離がその値を超えると表示されない。
ビデオメモリ予算と容量上限
LCC2 パイプラインは常駐する splat データを統一 Buffer に集約してレンダリングするため、1 つのモデルに読み込めるデータ量はこれらの Buffer の容量上限で決まる。
Buffer の構造と splat 1 個あたりの使用量
データは 2 つの Buffer に分けて格納される。
| Buffer | splat 1 個あたりの使用量 | 内容 |
|---|---|---|
| ガウス基本データ Buffer | 40 バイト | 位置、回転、カラー、スケール |
| 球面調和 Buffer | 48 バイト(最大 3 次) | 球面調和係数。球面調和を持つデータのみ割り当てられる |
Buffer 1 つあたりのハード上限
Unreal の Buffer サイズは 32 ビット整数で記録されるため、理論上限は 4 GB である。プラグインは実際には 4095 MB(4 GB より 1 MB 少ない)を採用し、残した余裕をアップロード時のバイト計算がはみ出さないようにするために使う。この上限は Buffer ごとに個別に効き、設定で超えることはできない。
ビデオメモリ予算の設定
1 つのモデルが使えるビデオメモリは ProjectSettings > Plugins > LCC4Unreal の LCC2 GPU Memory Budget (MB) で制御する。
| 項目 | 値 |
|---|---|
| 既定値 | 2048 MB |
| 調整範囲 | 2048 ~ 8192 MB |
| 反映方法 | 変更後にエディターの再起動が必要 |
この予算は 2 つの Buffer の合計であり、片方だけの上限ではない。したがって球面調和を持つデータは splat 1 個あたり 88 バイト(40 + 48)として予算に計上され、球面調和を持たないデータは 40 バイトとして計上される。
ソート Buffer の追加コスト
3DGS はレンダリング前に splat からカメラまでの距離でソートする必要がある。ソート結果は視点に依存するため、視点ごとに 1 組のソート Buffer が必要になる。この部分は上記の予算には含まれず、視点ごとに常駐する splat 1 個あたり 16 バイトを追加で消費する(ソートキーとインデックスを各 2 組持ち、ソート処理中の読み書きを交互に行うため)。
予算 2048 MB で球面調和を持つデータの場合、約 2440 万 splat を常駐でき、1 視点あたりのソート Buffer は約 372 MB になる。
ステレオレンダリングの左右の目、分割画面のプレイヤーごと、SceneCapture はそれぞれ 1 視点として数える。視点数が増えるとソート Buffer もそれに応じて増え、視点が減っても元には戻らない。そのためビデオメモリを計画するときは、これまでに発生した最大の視点数を基準に確保する必要がある。
単一ファイル形式の読み込み上限
単一ファイル形式とは .ply / .spz / .sog、つまりデータ全体が 1 つのファイルに保存され、ノード分割と Level の情報を持たない形式を指す。この種のデータは分割して読み込めないため、プラグインは単一のノードとして一度にデコードし、すべてビデオメモリに常駐させるしかない。そのため splat 数に明確な上限がある。.lcc と .lcc2 はエクスポート時にノード分割が済んでいるため、単一ファイル形式には該当しない。
上限の計算方法
読み込み前にプラグインが上限を計算し、上限を超えるデータをブロックする。
| 予算 | 球面調和なし | 球面調和あり |
|---|---|---|
| 2048 MB(既定値) | 約 5300 万 | 約 2440 万 |
| 4096 MB | 約 1.07 億 | 約 4470 万 |
| 8192 MB | 約 1.07 億 | 約 4470 万 |
上限は以下の 3 つの条件のうち最小の値になり、ビデオメモリ予算はそのうちの 1 つに過ぎない。
| 条件 | 説明 |
|---|---|
| ビデオメモリ予算 | 予算を splat 1 個あたりのバイト数で割った値(球面調和なし 40 B、球面調和あり 88 B) |
| Buffer 1 つあたりの上限 | 4095 MB を最大 stride で割った値(球面調和なし 40 B、球面調和あり 48 B) |
| 球面調和配列の容量 | 球面調和がある場合のみ有効。デコード結果が 32 ビットインデックスを使うため、上限は約 4470 万 |
そのため予算を 4096 MB より大きくしても数値は増えない。球面調和がない場合は Buffer 1 つあたりの上限で制限され、球面調和がある場合は球面調和配列の容量で制限される。さらに、グラフィックスカードで実際に使えるビデオメモリによって、読み込める量はより低く抑えられる。
注:既知の問題。予算の設定項目は 8192 MB まで入力できるが、単一ファイル形式で読み込める量は 4096 MB の時点で上限に達しており、それ以上上げても効果がなく、通知も出ない。予算がすでに 4096 MB でも上限を超える場合は、
.lcc2に変換するか、球面調和を持たないデータに切り替える。この制限は今後のバージョンで改善する予定である。
上限を超えたときの対処
単一ファイルのデータが上限を超えると読み込みが拒否され、Output Log に実際の splat 数、現在の上限、および予算を最大にしたときに対応できる splat 数が出力される。対処方法を推奨順に示す。
.lcc2に変換し、一度に常駐させるのではなくプラグインに必要に応じて読み込ませる。- LCC2 GPU Memory Budget を 4096 MB に上げ、再起動して再試行する。現在のボトルネックが予算である場合にのみ有効で、8192 MB に上げても単一ファイル形式には効果がない。
- 球面調和を持たないデータに切り替える。splat 1 個あたり予算に計上されるバイト数が 88 から 40 に下がる。
.sog や .spz に切り替えても上限は超えられない。この種の形式の圧縮はディスク上のファイルにのみ効くもので、デコード後に splat 1 個がビデオメモリで占める量はまったく同じである。
LCC2 形式はこの上限の制約を受けない
.lcc2 はデータを複数のノードに分割し、ノードごとに個別にデコードとアップロードを行う。個々のノードはいずれも上記の上限をはるかに下回るため、データの総量にハードな上限はなく、ビデオメモリ予算を大きく超えることもできる。制約を受けるのは実行時の次の 2 つの量である。
| 制約対象 | 制限の由来 | 超えたときの動作 |
|---|---|---|
| 同時に常駐する splat 数 | ビデオメモリ予算と Buffer 1 つあたりの上限 | 最も長く使われていないものから破棄し、入れ替える |
| 1 フレームでレンダリングする splat 数 | 約 8900 万(4095 MB ÷ 48 B) | 距離に応じて遠くのノードを破棄する |
1 フレームのレンダリング量は Actor の Max Splat Num の制限も同時に受け、両者のうち小さい値が採用される。
ビデオメモリの管理とチューニング
ビデオメモリ不足時の自動解放
実行時、プラグインはグラフィックスカードの実際のビデオメモリ使用量を継続的に監視する。使用量が ProjectSettings の Max GPU Usage Percentage For Release で設定した割合を超えると、最終レンダリング時刻、アクセス頻度、データサイズ、Level を総合してソートし、最も長く使われていないノードから解放する。高い Level のノードは保護され、視点を素早く回したときに広い範囲が抜けるのを避ける。
関連する設定:
| 設定 | 説明 |
|---|---|
| Max GPU Usage Percentage For Release | 自動解放をトリガーするビデオメモリ使用率(50 ~ 100%) |
| GPU Release Percentage | トリガーごとに解放するデータの割合(10 ~ 100%) |
| LCC2 GPU Memory Budget (MB) | 1 つのモデルの splat データのビデオメモリ予算 |
ビデオメモリ予算不足の判断と調整
予算が小さすぎる場合、データの読み込みが失敗するのではなく、入れ替えが繰り返される形で現れる。よく見られる現象は次のとおり。
- カメラを移動または回転させると画面に穴が空き、止めると徐々に埋まっていく
- 視点を行き来させると、同じ領域が鮮明になったりぼやけたりを繰り返す
- 一部の領域が常にぼやけたままで、近づいても鮮明にならない
- 視野内の情報量に応じてフレームレートが変動し、視界が開けた場所で明らかに下がる
- シーンが静止しているときは正常だが、移動するとカクつく
このうち一部領域のぼやけは、データ自体の品質不足だと誤解されやすい。低い Level のデータの読み込みが間に合わない場合、プラグインはまずビデオメモリにある高い Level のデータで埋めて画面が欠けないようにし、低い Level のデータが用意できたら差し替える。ビデオメモリ不足で低い Level のデータが繰り返し破棄されると、その領域は高い Level のまま留まり続ける。判断の目安は近づいたときに鮮明になるかどうかである。正常であれば 1 ~ 2 秒留まれば埋まるので、いつまでも変わらない場合は低い Level のデータがビデオメモリに入れないことを意味する。
確認方法:読み込み時に Output Log に出力されるモデルの予測使用量 (predicted cost) を見て、現在の予算と比較する。1 つのモデルの予測使用量が予算に近い、または予算を超えている場合、常駐領域が不足している。stat LCC で解放回数を観察してもよく、数値が増え続ける場合は頻繁に破棄が起きている。
予算を上げられるのはグラフィックスカードに余裕がある場合に限る。予算はプラグインが要求する上限にすぎず、グラフィックスカードの実際に使えるビデオメモリを超えると、ドライバーがデータをシステムメモリにスワップし、入れ替えよりも深刻にフレームレートが下がる。調整するときは次の順で判断する。
- グラフィックスカードで使えるビデオメモリを確認する。予算にソート Buffer のコストを加えたうえで、エンジン自体と他のリソースのための余裕を残しておく。
- 予算を段階的に上げ(2048 → 4096 → 8192)、毎回再起動して穴とフレームレートが改善するか観察する。
- 改善がはっきりしない場合はボトルネックが予算ではないので、データ量を減らす方向に切り替える。Max Splat Num を下げる、Max Distance の制限を有効にする、または球面調和を持たないデータに切り替える。
注:予算はモデルごとに独立して計算される。シーンに複数の LCC Actor を配置すると、ビデオメモリの使用量はモデル数に応じて積み上がるため、1 つのモデルあたりの予算をそれに合わせて下げる必要がある。