Skip to content
Go back

Adventures with Deferred Texturing in 'Horizon Forbidden West'

· Updated:

slides

拙訳

  • Foliage:木々や草花を効率的にレンダリング
    • アルファテスト付きジオメトリなので、大量のオーバードローが発生する
    • アニメーションありなので、正しいモーションベクトルを生成する必要がある
  • 前作では:
    • アルファテストありでdepth pre-passを行い、その後、アルファテストなしでデプステストをequalでレンダリングを行う
      • Gバッファへの書き込みが多くても1回で済む
    • この方法には問題がある:
      • すべてで座標変換が2度行われる
      • ピクセルに寄与しない頂点処理があるので、GPUをいっぱいまで使い倒すのが難しい
      • quadのオーバードローがつらい
        • derivativesを計算するためにhelper laneが生成されるので、PSは2x2のquadをひとまとまりとして処理が行われる
        • 描かれるピクセルが少なくても、その周囲に大量のhelper laneが動いている
  • Deferred Texturing:
    • Primitive IDで構成されるVisibility Bufferを作って、そこからGバッファを作る
    • 利点:
      • quadがないので、(理論上)オーバードロー問題から解放される
      • 後段のパスで、すべてのジオメトリを座標変換しなくて良くなる
      • 素のdeferred方式と比較して、帯域幅を節約できる
    • 欠点:
      • quadがないので、derivativesを自前で計算する必要がある
      • シェーディングのディスパッチを効率的にやる必要がある
  • Visibility Bufferのバリエーション:
    • オリジナル論文 [Burns and Hunt 2013Burns, C. A. and Hunt, W. A. 2013. The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading. Journal of Computer Graphics Techniques (JCGT) 2, 2, 55–69. https://jcgt.org/published/0002/02/04/.]
      • トライアングルIDとインスタンスIDをエンコードした32ビット整数を書き出す
      • それをもとに各マテリアルを含むタイルのセットを構築する
      • 間接ディスパッチでコンピュートシェーダーを起動して、マテリアルごとにそれが記録されたタイルのセットを処理する
      • ピクセルごとに、プリミティブIDを使って頂点情報を読み出し、barycentricsをタンジェント空間で手動計算し、頂点属性の変換を行う
      • シェーディングは中間バッファを介さずに最終結果が出力されるので、帯域幅を節約できる
    • Dawn Engine [Doghramachi and Bucci 2017Doghramachi, H. and Bucci, J.-N. 2017. Deferred+: Next-Gen Culling and Rendering for Dawn Engine. https://www.eidosmontreal.com/news/deferred-next-gen-culling-and-rendering-for-dawn-engine/.]
      • UV、derivatives、マテリアルID、タンジェント空間をパックしてVバッファに格納する
      • Vバッファを調べて、マテリアルIDを16ビットのデプスバッファにコピーする
      • そのデプスバッファをequalでデプステストするよう設定して、指定のマテリアルを使うオブジェクトすべてを含むquadをそのマテリアルIDと同じデプス値で描画することで、指定のマテリアルのピクセルのみを処理する
    • Tomasz Stachowiak の deferred material rendering system [Stachowiak 2015Stachowiak, T. 2015. A deferred material rendering system. h3r2tic's grimoire. https://h3.gd/a-deferred-material-rendering-system/.]
      • プリミティブIDと一緒にbarycentricsも書き出す
      • マテリアルごとのピクセルのリストを作る
    • Nanite [Karis et al. 2021Karis, B., Stubbe, R. and Wihlidal, G. 2021. A Deep Dive into Nanite Virtualized Geometry. Advances in Real-Time Rendering in Games course. ACM SIGGRAPH. https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf.]
      • works with compressed software rasterized geometry clusters
      • 64ビット整数にデプスとともにパックしたクラスタおよびトライアングル情報による比較的薄いVバッファを生成する
    • Activision の ジオメトリ・パイプライン [Drobot 2021Drobot, M. 2021. Geometry Rendering Pipeline Architecture at Activision. Rendering Engine Architecture Conference. https://enginearchitecture.org/2021.htm.]
      • ジオメトリ・クラスタ式
      • ほぼすべてのジオメトリでは、薄いVバッファに書き出す
      • Foliageでは、法線、テクスチャLOD、UVをエンコードしてUINTレンダーターゲット2つにわたって書き出す
  • 要件
    • アニメーションするジオメトリのサポート
    • 複数のマテリアルの効率的なサポート
    • PS4とPS5の両方でのサポート
    • 数十MBで動作する
    • Vバッファは32ビットくらいで
    • 出力はGバッファに
  • CSでピクセルをどう処理するか
    • タイルごとにまとめる?
      • ハードウェアが上手いことしてくれない限り、各タイルで何もしないlaneが大量に発生することになる
    • ピクセルごとにまとめる?
      • 対象のピクセルがあっちこっち行くと、L2のキャッシュ効率が悪くなる
      • リソースやシェーダの組み合わせごとにバッチがたくさんあると、それぞれに別個のディスパッチが必要になるので、非同期コンピュートで問題になる
    • タイルごとにゆるやかにまとめる?
      • タイルの空間局所性も欲しいし、ピクセルのまとめ方の柔軟性も欲しい
      • 大きめの128x128のタイルを使う
  • ゆるやかタイル方式
    • CPUセットアップ:
      • シーングラフから可視オブジェクトを収集してバッチを構築する
        • バッチはリソースやシェーダを共有するインスタンスの集合
      • バッチをソートしてパスを構築する
        • シェーダーのハッシュ値、バッチ毎データ、位置の量子化モートンコードをキーとする
      • 頂点数が多くなりすぎないようにバッチをマイクロバッチに分割する
        • インデックスは16ビット、プリミティブは最大64k個、インスタンスは最大64k個
        • マイクロバッチを複数のパスに分割することもできる
      • パスごとにCSで頂点を変換してリングバッファに書き出す
    • Vバッファの構築:
      • depth pre-passがすでにあったので、それを改造する形で追加した
      • トライアングル、インスタンス、マイクロバッチから成る32ビット整数を格納する
        • ハードウェアが頂点の順番をいじることがあるので、頂点3つそれぞれにエンコードした値からXORでプリミティブIDを再構築できる
        • マイクロバッチIDは16ビット、プリミティブIDはジオメトリが必要とする分、インスタンスIDは残りのビットを使う
    • Vバッファの分類:
      • Vバッファ構築のファイナライズ:
        • Vバッファを読んで、パスごとにタイルごとにどのバッチグループが使われているかのマスクを書き足す
        • Vバッファが有効なピクセルをUAV経由でステンシルに記録する
        • 水用キューブマップ処理と並列に行う
      • (Gバッファ生成):
        • ステンシルを上書きして、Vバッファが有効なピクセルのみを残す
        • 出力結果の分類を開始する前まで並列に行う
      • バッチグループのビットの数え上げ:
        • パスごとにタイルごとに使われているバッチグループの数を数える
      • Prefix Sum:
        • Prefix Sumでカウンタのオフセットを得る
      • バッチグループの数え上げ:
        • パスごとにタイルごとにバッチグループにピクセルがいくつあるかを数える
        • プリミティブIDから使用される頂点をデコードして、vertex cull bufferに記録する
      • Prefix Sum:
        • Prefix Sumでパスごとのタイルごとのバッチグループごとにピクセルコマンドをどこから記録し始めるかのオフセットを得る
      • 可視Vertx Chunkの識別:
      • 出力結果の分類:
        • Gバッファ生成が終わった後、有効なピクセルのコマンドを書き出す
          • Pixel Wave Command(64ビット):タイル座標、バッチグループID、ピクセルコマンドのオフセットと個数
          • Pixel Commands(16ビット):タイル内のピクセル座標
      • Pixel Wavesへの丸め:
        • コマンド数を丸めて、パスごとのタイルごとのバッチグループごとに必要なwave数を計算する
      • Prefix Sum:
        • Prefix Sumでオフセットを得る
  • タイムライン:
    • GFX:Depth Prime → Depth+Visibility → Water Cubemap? → Geometry Pass → Cascaded Shadows → Custom Deferred
    • Vバッファ生成のファイナライズ処理はWater Cubemapと並列に行い、分類処理はその後中段まで行う
      • 分類処理をGeometry Pass後まで後らせることもできる
    • Geometry Passが終わった後、分類処理の後段を開始する
    • 分類処理の中段が終わり次第、頂点の変換処理を行い、分類処理の後段が終わった後、対応する部分が完了次第シェーディングを行う
  • Variable Rate Shading:

Vertex Wave Commandsの生成方法の概要

  • vertex cull bufferを取り出し、uber chunkと呼ぶもの(頂点64個から成るchunkの64個の集まり)のなかに可視であるvertex chunkがどれだけあるかを数え上げる
  • さらに、どのchunkがsetされているかを示す64ビットのマスクをuber chunk毎に作り出す
  • uber chunkをまとめることで、ワークアイテムの線形なインデックスからuber chunkやその内部にある子vertex chunkのサブインデックスへ容易にマップすることが可能になるであろうヒストグラム・ピラミッドを構築することができる
  • uber chunkごとに構築される64ビットマスクを用いることで、この子vertex chunkのインデックスをvertex chunkのグローバルなインデックスに容易に変換し直すことができる
  • マイクロバッチ全体をディスパッチして、マイクロバッチ情報テーブルを読み、先述のマッピングを用いてパスごとに必要になるvertex wave数を割り出す
  • CPUでセットアップするマイクロバッチ順序バッファを読む。これは、マイクロバッチが処理される順序を記述する
  • これはマイクロバッチIDが変化するvertex wave command streamの各位置でマイクロバッチIDの差分をアトミックに書き出すのに使われる
  • そして、Prefix Sumはその差分から実際のマイクロバッチIDへ変換するのに使われる
  • そして、必要とするvertex wave commandsすべてを間接ディスパッチを行い、各wave commandがどのvertex chunkを変換するべきかを探し出し、すでに記録されたマイクロバッチに沿ってコマンドをこの情報に追加するためにマイクロバッチ情報テーブルに沿ってヒストグラム・ピラミッドやマスクを読み出すことでことを終える

Batch Groups

  • パスごとにバッチグループは最大で32個まで
  • バッチグループはシェーダやバッチ毎データを同じくするバッチの集合であり、インスタンスは異なる
  • 理想的にはこの32個制限はない方がいいのだが、現時点では、Vバッファ分類処理のいくつかの構造に起因するものである