超雑訳 Maximizing Graphics Performance with Flexible Virtualized Geometry

こんれな~。
Pocolです。
今日は…
[Vaisse 2024] Alexis Vaisse, Marios Michaelides, “Maximizing Graphics Performance with Flexible Virtualized Geometry”, GDC 2024.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳と共に指摘していただけると有難いです。





まずは、なぜ我々がこの技術の開発に踏み切ったのか、その理由から説明しましょう。
一番大きなパートである第2部では、この技術についてより詳しく説明します。
我々がVirtuosで持っている一つの特殊性は、異なるゲームエンジンを扱っていることです。
複数のゲームエンジンに対応する技術を開発することは必須でした。我々がどのようにこれを達成したかを簡単に説明します。
我々は常に技術を改善しています。最後に、我々が現在取り組んでいるテーマについて説明します。






これを改善するためにはどうすればいいのか?
我々は、アーティストが高解像度のメッシュのみを使用して作業することを望んでいます。
そして、ゲームエンジン次第で、良いパフォーマンスを得ることができ、メモリ予算を尊重することができます。
我々は複数のゲームエンジンで作業していることを忘れないでください。
ですから、異なるゲームエンジンと互換性のある技術であってほしいのです。
フォワードレンダリング、ディファードレンダリング、バーチャルシャドウ、カスケードシャドウ、レイトレーシング、さまざまなストリーミングシステムなどに対応していなければなりません。



アーティストがより高解像度のメッシュを使用し、グラフィックエンジン自体も従来のレンダリングに比べてより高解像度を使用するため、我々はより良い品質に到達したいと考えています。
また、従来のレンダリングと比較して、より良いパフォーマンス、そしておそらく重要なことですが、より安定したパフォーマンスを得たいと考えています。
また、メッシュを最適化する必要がなくなるため、制作時間の短縮も期待できます。
そして最後のポイントは、固定メモリバジェットを使用するため、制作中にコンソールで発生するメモリ不足によるクラッシュが減るということです。



OK。我々はかなり野心的な目標を設定しました。それを達成するために何をしたかを見てみましょう。



解決すべきさまざまな技術的課題から始め、それから技術そのもののさまざまな部分に飛び込んでいくつもりです。最初はビルダーステージで、2つ目はトラバーサル、オクルージョンカリング、レンダリング、ストリーミングを含むランタイムステージです。



高さ4.5メートル、重さ200トン、3.5ミリの三角形のこの素晴らしい仏像を見てみましょう。
もっと近づいてみましょう。



どのLODでレンダリングするべきでしょうか?



LODを高くすれば、品質は良くなりますが、パフォーマンスは悪くなります。



また、低いLODを使えば、性能は良いですが品質は悪くなります。



カメラの距離によってLODが異なるようにしたい。
つまり、異なるLODを持つオブジェクトを同時にレンダリングしたいのです。



もっと近づきましょう。
メッシュの上部に低いLODを使うとどうなるでしょうか?
やってみましょう。



おっと、LODの間に穴が開いてしまいました。
目的を言い換えましょう: 異なるLODを持つオブジェクトを同時にレンダリングしたいです … 継ぎ目なしで。



素晴らしい像に戻りましょう。
メッシュの一部が最高のLODを使って表示されても、残りのメッシュには最高のLODを読み込ませたくありません。
つまり、サブオブジェクトレベルでストリーミングしたいのです。



オブジェクトが部分的にしかオクルードされていなくても、パフォーマンスを向上させるために、オクルードされた部分はまったくレンダリングされないようにしたいです。
つまり、サブオブジェクトレベルでのオクルージョンカリングが欲しいということです。



我々が明らかにしたさまざまな課題をまとめてみよう:
異なるLODを持つオブジェクトを、継ぎ目なしで同時にレンダリングしたい。
サブオブジェクトレベルでのデータストリーミングと、サブオブジェクトレベルでのオクルージョンカリングが欲しい。
複数のゲームエンジンとの互換性。



ビルダー段階は通常、インポートプロセスに組み込まれています。



ここで何をするのか?
最初のステップは、メッシュをクラスターと呼ぶ小さなパーツに分割することです。
これにはMETISライブラリを使います。
クラスターのサイズはユーザー定義ですが、通常は128トライアングルが最高のパフォーマンスを発揮します。



以下は、このメッシュで得られたさまざまなクラスターです。



次のステップは、いわゆる間引きです。
このために、連続したクラスターのグループをマージします。典型的な値は32クラスターです。
それから三角形の数をちょうど2で割るように辺を取り除きます。
非常に重要なのは、LOD間の穴を避けるために境界のエッジを残すことです。
それから16個の新しいクラスターを作ります。これらのクラスタは次のLODの一部になります。
ここに、オブジェクトの新しいクラスタを見ることができます。



ここで難しいのは、エッジを崩すフェーズです。
基本的には、削除するエッジを選択するためのエラー関数があります。
我々は、生成されるLODの品質を向上させるために、何ヶ月もかけてこのエラー関数を微調整してきました。



ステップ3はグラフの作成です。
次のレベルの16個のクラスターは、前の32個のクラスターの親です。
クラスターは直接非循環グラフを形成します。
直接とは、エッジが一方向のエッジであることを意味します。
非循環とは、サイクルがないことを意味します。
ノードは複数の親を持つことができるので、これはツリーではありません。
一番上には、より大きな三角形を持つクラスターがあります。
下には小さい三角形のクラスターがあります。
このグラフはとてもシンプルです。
実際にはこのようなグラフがあり、デバッグはもっと難しいです。



最後のステップはチャンクの作成です。
チャンクとは単にクラスターのグループです。
ここでは、最初のチャンク、2番目のチャンク、そして最後のチャンクがある。
なぜこんなことをするのでしょうか?
覚えておいてほしいのは、クラスターは128個の三角形しか含まないので、とても小さいということです。
効率的にするためには、より大きなデータブロックをストリーミングしなければなりません。
すべてのチャンクはディスク上で同じサイズを持っており、チャンクはディスクからストリーミングできる最小のデータ量です。



ビルダー段階のさまざまな部分を要約してみましょう:
まず、メッシュを小さなクラスターに分割します。
次に、異なるLODを作成するためのクラスタの数を減らすために、クラスタが1つになるまで間引きします。
異なるクラスタでグラフを作成します。
そしてクラスターをグループ化して、ディスクからストリーミングされるチャンクを作成します。



ランタイムステージに切り替えましょう。
最初のステップはトラバーサルです。



トラバーサルはコンピュートシェーダで、すべてのグラフをトラバースして、オブジェクトごとに表示するクラスタのリストを計算する。
グラフのルートから開始し、画面上のサイズが十分に小さいクラスタに到達するまで下っていきます。
出力はクラスタIDとオブジェクトIDのペアのリストです。オブジェクトIDは、適用する変換を取得するために必要であり、その他のオブジェクトごとのプロパティも必要です。
このリストは次のコンピュートシェーダで使用されます。
また、ディスクからストリーミングする必要があるクラスタのリストも計算します。
今回はクラスタIDのみが必要です。
このリストは RAM に送られ、ストリーミング要求のジャンル分けに使用されます。
スクリーンに投影された外接球のサイズに基づいてクラスタを選択します。
目標は、スクリーン上にほぼ同じ大きさの三角形を配置することである。



次のステップはオクルージョンのカリングです。



このシェーダーは、トラバーサルフェーズの入力を受け取り、異なるクラスタのフラスタムカリングを行い、新しいクラスタリストを生成します。
次に別のコンピュートシェーダがオクルージョンカリングについて同じことを行います。
これはZ-Prepassの結果を使用するため、Z-Prepassの後に実行する必要があります。
基本的には、各クラスタのバウンディングボリュームを階層Z Bufferに対してテストします。
出力はクラスタIDとオブジェクトIDのペアを持つDrawIndirectコマンドのリストです。



レンダリングフェーズ自体は、DrawIndirectコマンドを実行するだけなので、とてもシンプルです。
ピクセルシェーダーは変更しません。
ハードコードされたものであれ、生成されたものであれ、すべてのマテリアルと互換性を保つ必要があるため、これは非常に重要です。



最後のステップはストリーミングです。



ストリーミングでは、チャンクレベルで作業することを忘れないでください。
チャンクはクラスタのリストであり、すべてのチャンクはディスク上で同じサイズを持ちます。
固定メモリーバジェットを使っているので、同時にメモリー上に存在できるチャンクの数は決まっています。
各チャンクについて、最後に使用された時間を保存し、新しいチャンクをロードする必要があるときにアンロードするチャンクを選択するために、古典的なLRU(Least Recently Used)アルゴリズムを使用します。
この部分の実装はそれほど難しくありませんでしたが、優先順位の概念を追加する必要がありました。
その後、事態はより複雑になりました。




いくつかのゲームエンジンをサポートするために使ったテクニックを紹介します。



主なアイデアは、エンジンコードにアクセスできないC++ライブラリにすべてのコードを置くというものです。
エンジンコードにアクセスできるのは、我々が作成したインターフェイス(基本的には抽象クラス)を通してだけです。
そして、ゲームエンジン側でこれらのインターフェイスを実装します。



これらのインターフェイスのいくつかを見てみましょう。
RAMにメモリーを割り当てるメモリー・アロケーターや、ビデオメモリー・アロケーター、メッセージを表示するロガーなど、とてもシンプルなものもあります。



ストリーミングに関しては、データのブロックをストリーミングするリクエストを送信し、データがロードされたときに警告するコールバックを登録するストリーミング・マネージャーがあります。
ファイルごとに1つのチャンクを置くべきか?それとも、すべてのチャンクを1つのファイルに入れるべきでしょうか?
そうですね。ゲームエンジンによります。
両方をサポートする必要があります。
そこで、ストリームするデータを識別する一般的な方法が必要です。
そのために、データを識別するためのIDを提供する別のインターフェースを作りました。
IDは汎用的でなければならない。実際には、単なるバイトの配列です。
パス、ファイル名、GUIDなど、どんなものでもよいです。



レンダリングフェーズでは、バッファをシェーダにバインドするバインダインタフェースがあります。
残りのレンダリングコードはエンジン固有です。



シェーダーに関しては、ゲームエンジンは通常、HLSLの上に独自のシェーダー言語を持っています。
我々のシェーダーは、ライブラリではHLSLで書かれていますが、ゲームエンジンごとに書き換えられています。
これは通常、簡単な作業です。



レンダリングそのものについては、さまざまなレンダリングパスでシェーダーを組み込みする必要があります。
これには、エンジン固有のコードがかなり必要になります。




このプレゼンテーションの最後に、現在進行中の仕事を紹介しようと思います。



私たちは現在、ジオモーフィングに取り組んでいます。
ジオモーフィングとは?
単純にLOD間のスムーズな移行です。



ジオモーフィングは何に役立つでしょうか?
課題の1つは、LODから別のLODに切り替えるときのポップを最小限に抑えることです。
1つの可能な解決策は、三角形が非常に小さく、通常は1ピクセルよりも小さい場合に、LOD間で遷移することです。
欠点は、三角形が多いことで、パフォーマンスが低下します。
解決策は、ソフトウェアラスタライザーを実装することです。
レンダリングエンジンに大きな影響があり、複数のゲームエンジンと互換性のある実装を行うことは非常に困難であるため、この方法を採用しませんでした。
もう1つの可能性は、LOD間の移行をスムーズにすることです。これはまさにジオモーフィングが行うことであり、三角形が大きい場合 (たとえば、1または1未満ではなく4または5ピクセルの場合) に、LODから別のLODに切り替えることができます。
いくつかのテストを行いました。5ピクセルの三角形と比較して、1ピクセルの三角形を使用した場合の品質の向上はありません。



我々の研究開発チームは、それほど難しくないフォリッジのサポートや、より大きな課題であるアニメーションメッシュのサポートにも取り組んでいます。




覚えておくべき最も関連性の高いトピックは?



ビデオゲームでは、仮想化されたジオメトリがスタンダードになりつつあると思います。
クラスタの作成、間引き、グラフの作成、チャンクの作成など、ビルダー段階でのさまざまなステップをここで思い出しました。
METISは非常に強力なライブラリで、オープンソースなので、必要なときに使ってください。
ビルダーステージのアルゴリズムは、実装がそれほど簡単ではなく、LODの良い品質を達成するのに長い時間がかかることがあります。
また、まだ触れていない点として、メッシュのビルドにかかる時間は非常に長くなります。



レンダリング段階では、単純な実装を書くのはそれほど難しくありません。
ストリーミング部分に関しては、LRUアルゴリズムは良い結果をもたらしますが、優先順位の管理はより複雑です。
現在の作業については、結果が出次第公開する予定です。
ご期待ください!



我々が読んだ、あるいは我々の技術を構築するために使用した論文のリストをここに載せておきます。