"Destiny の次世代レンダリング (SIGGRAPH 2013)" の説明

概要

  • これは Bungie の 新規 IP Destiny での次世代シェーディング(SIGGRAPH 2013)"に関する記事です.

  • この講演では下の 3 種類の内容があったのですが, 今回はそのうちの 2 つをピックアップして説明します.
    • Destiny でのディファードレンダリング
    • 半透明用の GPU ライトプローブ
    • パーティクル (今回は保留)

参考文献

Destiny でのディファードレンダリング

最初に
  • Destiny ではディファードレンダリングにはライトプレパスの亜種(初出は ロシアのFPS ゲーム S.T.A.L.K.E.R : Clear Sky)を使っています.
  • また Destiny の資料でもディファードレンダリング系の説明が一通りありました.
  • そこで, かなり今更感がありますが..., フォワードレンダリング, ディファードシェーディング, ライトプレパスについて説明しておきます.
  • ご存知だと思いますが, これらのレンダリングのテクニックはゲーム用のライトのローカルライティング(ディフューズ直接照明やスペキュラ直接照明)のための最適化のテクニックです.
  • あと, ディファードレンダリングやディファードシェーディングについては下のように分類して説明することにします.
    • フォーワードレンダリング
    • ディファードレンダリング
      • ディファードシェーディング
      • ライトプレパス VersionA (標準的なライトプレパス)
      • ライトプレパス VersionB (S.T.A.L.K.E.R : Clear Sky方式)
フォワードレンダリング


  • 内容
    • 1 回のジオメトリのレンダリングで, マテリアル・ライティング・シェーディングの計算を全てシェーダで行った結果をカラーバッファに書き込みます.
  • 長所
    • シェーダでの計算時にマテリアルやライティングの情報を参照できるので, ディファードレンダリングよりは表現力が高いです.
    • 実装がシンプルです.
  • 短所
    • オーバードローされて見えないピクセルに対してもライティングやシェーディングの計算をするので, ピクセルシェーダが重くなります.
    • もしピクセルシェーダの処理を軽くするために, マテリアルごとに使うライトの種類やその数を変えて処理負荷を減らすときでも, シェーダバリエーションが増えて複雑になってしまいます.
  • 今回の Destiny の資料だと下図で説明が行われていました.


ディファードシェーディング
  • ディファードシェーディングは (0) "G-Buffer 構築用のジオメトリパス" と (1) "シェーディング用のライティングパス" の 2 段階の構成になっています.
  • 内容
    • (0) "G-Buffer 構築用のジオメトリパス" : 下図のようにジオメトリパスで MRT を使って複数のテクスチャバッファから構成される G-Buffer を埋めます. G-Buffer にはディフューズアルベド・法線・スペキュラライティング用のマテリアルのプロパティをテクスチャバッファに書き込みます.

    • (1) "シェーディング用のライティングパス" : 下図のようにこのパスで, ローカルライトのジオメトリや全画面の長方形を描画して, G-Buffer を使ったシェーディングを行って結果をカラーバッファに書き込みます.

  • 長所
    • ジオメトリパスとライティングパスが分離できます.
    • ジオメトリパスが 1 回だけで済みます.
    • 最終的に見えるフラグメントだけシェーディングやライティング計算が適用されます.
  • 短所
    • G-Buffer 用のグラフィックスメモリの使用サイズが大きいです.
    • G-Buffer 構築時には MRT がほぼ必須です.
    • 半透明用にはフォーワードレンダリングが必要です.
  • 下図が今回の Destiny でのディファードシェーディングの説明の資料です.


  • 下図が今回の Destiny でのハイブリッドなディファードレンダリングの説明の資料です.


ライトプレパス
  • ライトプレパスは (0) "G-Buffer 用のジオメトリパス", (1) "ライティング計算用のライティングパス", (2) "ライティングをマテリアルと合成するジオメトリパス" の 3 つのパスから構成されています.
  • 内容
    • (0) "G-Buffer 用のジオメトリのパス" : 下図のようにジオメトリを描画して, 法線の情報やスペキュラ計算用のラフネスなどを G-Buffer に書き込みます.
    • 下図の例だと MRT を使っておらず, 1 枚の G-Buffer (法線用のテクスチャバッファ)とデプスバッファに対して描画しています.

    • (1) "ライティング計算用のライティングのパス" : 下図のようにローカルライトのジオメトリや全画面の長方形を描画し, (0) で作った G-Buffer の情報を参照しながら, ディフューズライティングとスペキュラライティングを計算した結果をライトバッファに書き込みます.
    • 下図の例だと使用しているライトバッファは 1 枚で, デイフューズライティングの結果を RGB, スペキュラライティングの結果を A に書き込んでいます.
    • またポイントライトのように画面中のバッファの座標が必要なときには, デプスバッファから座標を復元して利用します.

    • (2) "ライティングをマテリアルと合成するジオメトリパス" : 2 回目のジオメトリパスを描画します. このときにジオメトリのマテリアルのディフューズアルベドテクスチャやスペキュラリフレクタンスを参照して, (1) で作ったライトバッファと合成した結果をカラーバッファに書き込みます.


  • 長所
    • ジオメトリパスとライティングパスが分離できます.
    • ジオメトリパスが 1 回だけで済みます.
    • 最終的に見えるフラグメントだけシェーディングやライティング計算が適用されます
    • MRT が必ずしも必要ないです.
  • 短所
    • ジオメトリパスが 2 回必要です.
    • 半透明用にはフォーワードレンダリングが必要です.
  • まず, Version A は標準的なライトプレパスで上で説明したものです.
  • 一応, 下に Version A に関する説明のスライドを貼っておきます.


  • 次にライトプレパス Version B (S.T.A.L.K.E.R : Clear Sky方式)については GDC2009 で ロシアのFPS ゲーム S.T.A.L.K.E.R : Clear Sky の開発元の GSC Game World 社が公表した方式です.
  • 資料はこれです. → S.T.A.L.K.E.R : Clear Sky GDC2009の資料
  • この方式ではジオメトリパスは 2 回ではなく, 1 回で済ませています.
  • このライトプレパスでは最初の (0) "G-Buffer 用のジオメトリパス" で MRT を使って, ディフューズアルベドテクスチャやスペキュラリフレクタンスの情報もG-Buffer に書き込んでおきます.
  • そして (1) "ライティング計算用のライティングパス" については標準的なライトプレパスと同様に処理をします.
  • 最後の (2) "ライティングをマテリアルと合成するジオメトリパス" でフルスクリーンの長方形などを描画して, そのピクセルシェーダで G-Buffer のアルベド系の情報(ディフューズアルベド, スペキュラアルベド)を参照し, (1) でのライトバッファと乗算して合成した結果をカラーバッファに書き込みます.
  • 一応, 下に Version B に関する説明のスライドを貼っておきます.

Destiny のライトプレパス
  • 今回の Destiny のスライドで下図のようにライトプレパスの説明があったのですが, これは標準的なライトプレパス(VersionA)を示していてジオメトリパスが 2 回あります.

  • 一方で下図のように Destiny のライトプレパスだとジオメトリパスが 1 回だけで, これはライトプレパス Version B (S.T.A.L.K.E.R : Clear Sky方式)に相当しています.


  • また Destiny のライトプレパスの G-Buffer は下図のようになっています.

  • ここでの albedo color RGB は sRGB のカラーで, normal は球を 2次元的に展開してカメラ方向の精度を上げた法線です.
  • またスペキュラ反射計算用のラフネス(biased specular smoothness)も入っています.
  • Ambient Occlusion は事前計算した AO で, Material ID は後述するマテリアルライブラリを参照するためのものです.
  • Destiny は現世代のコンシューマ機にも対応したマルチプラットフォームのゲームなので, G-Buffer が XBox360 の EDRAM に載るのでタイリングが発生しないようになっています.
  • 0. まず, ジオメトリパスで下図のような G-Buffer を作ります.

  • 1. 次に下図の左下の G-Buffer を参照しながら, 下図の右下のようにディフューズライティングの結果をディフューズライティング用のライトバッファに書き込み, またスペキュラライティングの結果はスペキュラライティング用のライトバッファに書き込みます.

  • 2. 最後の合成のパスで, 下図の左下のようにディフューズライティングのライトバッファと G-Buffer のディフューズカラーを乗算して, 下図の右下のようなディフューズシェーディングの結果をカラーバッファに書き込みます.

  • 同様に最後の合成のパスで, 下図の左下のようにスペキュラライティングのライトバッファと G-Buffer のスペキュラカラーを乗算して, 下図の右下のようなスペキュラシェーディングの結果をカラーバッファに書き込みます.

  • 最終的には下図のように, 上のディフューズシェーディング結果とスペキュラシェーディングの結果を加算して, 最後のレンダリング結果が得られます.

マテリアルライブラリ
  • マテリアルごとにいろんなパラメータを持ちたいときに, それらのパラメータを全て G-Buffer に書き込んでいると, G-Buffer のデータサイズが大きくなったり, また GPU のフィルのコストが発生します.
  • 従ってそれを避けるために, 他のディファードレンダリングでも行われているように Destiny の G-Buffer には Material ID が 8bit 分, 割り当てられるようになっています.
  • そして, このマテリアルごとの ID を元にディファードレンダリング時にデザイナーさんが設定したカスタムしたマテリアルの情報を参照できるようになっています.
  • 今回の Destiny の例だと, スペキュラハイライトの強度の変化(Specular Lobe ID)とスペキュラカラー(Specular Tint ID)をカスタムして設定できるようになっていました.
  • 下図は スペキュラハイライトの強度変化(Specular Lobe ID)の例で, ここではスペキュラハイライトの形状(Shape)とラフネス(Roughness)が設定できるようになっています.

  • まず スペキュラハイライトの形状である Shape (正確にはスペキュラの反射特性のローブ)については, 下図のような 1次元のテーブルをデザイナーさんが設定できるようになっています.

  • 一方で, 上のスペキュラハイライト形状がラフネスによる変化のバリーエーションについては事前で自動でバリエーションを作っておき, このときにエネルギーが保存されるようにしている(つまり, 正規化している)らしいです.
  • 例えば, 下図が通常のスペキュラハイライト形状です.

  • そして, 下図がラフネスを変化させて粗くしたときに変化したハイライト形状となります.

  • さらにラフネスが粗い場合は下図のようになります.

  • 最後に スペキュラ Tint Id についてはスペキュラカラーを設定するためのテクスチャによるテーブルです.
  • このテクスチャについては下図のように U 方向は dov(N,V) , V 方向には このスペキュラ Tint Id による変化を表しています.

  • またそのテクスチャ(specular_tint_tex)の RGBA 成分を元に, Destiny ではスペキュラカラーを「スペキュラカラー = ディフューズアルベドカラー * specular_tint_tex.alpha + specular_tint_tex.rgb」と求めています.
スクリーンスペースの SSS
  • Destiny ではライトプレパスでディフューズライティングの結果をライトバッファに書き込んでいたものを, スクリーンスペースでぼかして SSS っぽい表現をしています.
  • 下図がライトプレパスによるディフューズライティングの結果です.

  • これをスクリーンスペースで肌の部分だけぼかすと, 下図のように顔の左半分が柔らかい感じになります.

  • 下図は SSS なしの最終結果です.

  • 一方で, 下図は SSS ありの最終結果です. 肌の色のせいか, ちょっと蝋人形っぽく見えますね...


  • またこの SSS については最適化のために, 適用する箇所をステンシルマスクで限定できるようにしているらしいです.
異方性のスペキュラハイライト
  • Destiny では例えば髪の毛のスペキュラハイライト用に異方性のスペキュラハイライトを実現したいという要望がありました.
  • ただスペキュラハイライトに異方性を持たせるには何らかの方法で方向をしている必要があります.
  • そこでこれをピクセル単位(G-Buffer)で持つことはせずに, 一様に太陽のライト方向やキャラクターごとに設定した異方性の方向を指定できるようにしていました.
  • そしてその方向を元に, G-Buffer の構築時にそのディフューズアルベドカラーやスペキュラのラフネスを異方性を持たせるように加工しています.
  • 髪の毛のシェーディングモデルには Marshner モデルを単純化したものを使っていました.
  • 下図のキャラクターの黒い髪の毛には, 髪の毛の流れに沿った異方性のスペキュラハイライトが当たっています.



半透明用の GPU でのライトプローブ

概要
  • Destiny では半透明のライティングやシャドウも, 他の不透明な物体とできるだけ一貫させたいという要望がありました. (下図のスライド参照)


  • そこで, 半透明物体用のライトプローブを準備して, それらを GPU で動的に更新するということを行っています.
  • 具体的には半透明物体があるところの位置情報を書き込んだテクスチャを CPU で作成し, それを GPU で参照し, GPU でライトプローブの位置座標や半径を元にライトやシャドウを考慮したライトプローブの直接光のライティングデータの更新を行っています. ( 下図のスライド参照 )


内容
  • カメラの範囲に半透明物体があったときには, CPU でライトプローブの位置座標(float xyz)と半径(float w)を 64 x 16 ピクセルの RGBA F32 のテクスチャに書き込みます.
  • ここでのライトプローブの半径は恐らく半透明物体の半径に相当し, 後述するシャドウの PCF に利用します.
  • ここでのテクスチャが 64 x 16 ピクセルなのは プローブの最大数は 1024 個(64 x 16)と限定しているからです.
  • またこのプローブ用のテクスチャへの書き込みはスレッドセーフでロックフリーなバッファにしておき, またダブルバッファリングにしておきます. (下図のスライド参照)


  • 次に, GPU でのライトプローブ用のライティングデータを作る処理についてです.
  • まず, 1 個のライトプローブ用のデータとしては float を 12 個割り当てています.
  • なぜ 12 個なのか ? というと, 今回は SH 係数は 1 チャンネルのつき float 4 個 割り当てるようにして, それが RGB チャネルごとにあるので, 合計 4 * 3 = float 12 個 となっています.
  • まとめると 今回のケースだと 1024 個のライトプローブがあり, 1 個のライトプローブについて float 12 個分のデータがあります. ( float の総数は 1024 * 12 個 = 12 K 個 )
  • これを GPU で 1回のドローキックで更新するには, 64 x 16 ピクセルの大きさの長方形を 3 枚のテクスチャバッファ(RGBA F32)に MRT で書き込んでいます. ( 64 * 16 * 3 * 4 個 = 12 K 個 )

  • GPU でディレクショナルライトの直接ディフューズ光とスペキュラ光のライティング(シャドウ込み)をライトプローブごとに GPU で計算する場合についてです.
  • ここでは恐らく, ディレクショナルライトの結果をライトプローブの SH のテクスチャバッファに書き込むために SH 係数への変換します.
  • そしてそれを書き込む際のシャドウ領域に入っているかどうか ? の判定については, ピクセル単位ではなくライトプローブの位置座標の 1 点をもとにカスケードシャドウマップを参照して行います.
  • また PCF を適用するときには ライトプローブごとに指定した半径(半透明物体の半径に相当?)に応じて PCF のサンプリング半径が調整できるようになっています. (下図のスライド参照)


  • ただこのように 1 点の座標のライトプローブの結果を元にシェーディングを行うと, ビューフラスタム用に領域を最適化してあるカスケードシャドウマップを使う場合や, 半透明物体が大きい時にシャドウ判定のずれが起きてしまいます.
  • なので, 大きい半透明物体に使う場合には複数のライトプローブを別々のマテリアルごとに利用しています.
  • 上でのディレクショナルライト以外にアーティストが明示的に指定したアンビエントライト(半球ライトとか?)も GPU でライトプローブに書き込むことができるようになっています.
  • その際には, アンビエントライトごと SH 係数に変換して, ライトプローブ用の SH のテクスチャバッファに書き込みます.
  • あとは静的なポイントライトや動的なポイントライトにつても SH 係数に変換して, ライトプローブ用の SH のテクスチャバッファに書きこんでいるようです. (下図のスライドを参照)


  • 半透明のレンダリングパスについてです.
    • このパスはディファードレンダリングのパスの後のものです.
    • またライトプローブを使ったレンダリング時には半透明物体の法線方向を元に, ライトプローブの SH のライティングをサンプリングしてシェーディングに利用します. (下図のスライド参照)


  • 最後にパフォーマンスについてです.
  • この半透明物体用のライトプローブの更新は毎フレーム行ってますが, 結構処理は軽く 1024 個のライトプローブを 50 個のライトで更新するのに, XBox360 で 0.15 msec 以内で収まっていました.
  • このうち, ライトプローブにディレクショナルライトのライティング結果を書き込むのはカスケードシャドウごとに 3 usec, 1 つのライトの結果をライトプローブのテクスチャバッファに書き込むのは 3 usec 以下です.