"Call of Duty: Black Ops 2 のより物理ベースなシェーディング(SIGGRAPH 2013)"の説明

概要


参考文献

以前の Black Ops のときの正規化した環境キューブマップの利用

  • Black Ops のときはスペキュラ間接光のために環境キューブマップを利用していました.
  • しかし, そのまま環境キューブマップを参照して使うだけだと下図の左側のように岩の下の部分については法線マップで環境キューブマップを参照するだけなので, 岩の下の部分が予想よりも明るくなってしまいます.
  • 従って, Black Ops では環境キューブマップを正規化しておき, それに対して(岩の)マテリアルのライトマップの平均輝度を乗算することでスペキュラ間接光が馴染むようにしています. それが下図の右側です.


  • 正規化した環境キューブマップの利用手順としては下のようなものになります.
  • (A) オフラインの手順
    • 1. 環境マップを撮影した位置座標(env_pos)での全方向のイラディアンス(env_sh9, SH 係数 float 9 個)を求めます. ここでの env_sh9 とはイラディアンスのインテンシティだけを持った 1 チャンネルの SH 係数(float 9 個)です.
    • 2. 全方向のイラディアンス(SH 係数 9 個)から, その位置座標の平均のイラディアンス(env_average_irradiance)を求めます.
    • 3. 環境マップの各テクセルを平均のイラディアンスで割り算して, 正規化します.
  • (B) ランタイムのピクセルシェーダ
    • 4. リフレクションベクトルを使って計算で環境キューブマップ(env_map)を参照します. それにピクセルの平均のイラディアンス(ライトマップや頂点ベイク, ライトプローブから取得したもの)を乗算して, スペキュラ間接光(env_color)とします.
  • 上の説明の元になった擬似コードを下に貼っておきます.

Black Ops 2 での環境キューブマップの新しい正規化

  • Black Ops のときの環境キューブマップの正規化だと, ライトマップや頂点ベイクしたマテリアルのうち法線が下方向に向いているものが暗くなっていしまうという問題がありました.
  • この不具合が起きる理由について説明します.
  • 下図のようにライトプローブは宙に浮いていて全方向から入射するライティングの情報を持っています.
  • しかし, 下図のようにライトマップや頂点ベイクはマテリアル(例えば, 地面など)の表面の法線に対して半球状に入射するライティングの情報しか持っていないからです.


  • より具体的に言うと, ライトプローブは空の色と地面の色の両方を考慮したライティング情報が入っています.
  • しかし, 地面のマテリアルのライトマップや頂点ベイクについては空の色だけなので, 下からの照り返しの色(グランドカラー)がないので下部分が暗くなってしまいます.
  • 下図がBlack Ops のときの環境キューブマップの正規化のものです.
  • 右側の LightProbe に比べると, 左側 の Lightmap と中央の Vertex bake のうち法線が下方向を向いている部分のスペキュラ間接光(環境キューブマップ)が暗くなっています.

  • そして, 下図が "Black Ops 2 での環境キューブマップの新しい正規化" の結果です.
  • 左側 の Lightmap と中央の Vertex bake のうち法線が下方向を向いている部分のスペキュラ間接光(環境キューブマップ)が暗くなっていた問題が解消されています.


  • Black Ops 2 のライトプローブのデータの持ち方が少し変わっているので先に説明しておきます.
  • まずよくあるゲームでのライトプローブのデータの持ち方は下図の 9 種類のSH の基底関数(赤が正, 青が負)ごとの float の係数を RGB チャネルごとに持つ形です.

  • この 9 種類の関数に対応した数式は以下のようなものになります.


  • SH の float の係数というのは基本的には上の基底関数に対して乗算する係数を示しています.
  • よくあるゲームでのライトプローブのデータの持ち方だと 1 ライトプローブあたり 3 [RGBチャネル] * 9 [9種類の基底関数の 係数] = 27 個の float が必要ということになります.
  • ついでに 法線ベクトルを使って SH 係数を展開する方法についても書いておきます.
  • 例えば 法線ベクトルが(x,y,z) で, 赤成分用の float の SH のパラメータ 9 個: r_sh0, r_sh1, ..., r_sh8 であるときには, 法線ベクトルの方向の Red の値は以下のように展開します.
red =
    red_sh0 * sqrt(  1.0f / (  4.0f * PI ) )                   + 
    red_sh1 * sqrt(  3.0f / (  4.0f * PI ) ) * x               +
    red_sh2 * sqrt(  3.0f / (  4.0f * PI ) ) * z               +
    red_sh3 * sqrt(  3.0f / (  4.0f * PI ) ) * y               +
    red_sh4 * sqrt( 15.0f / (  4.0f * PI ) ) * x * y           +
    red_sh5 * sqrt( 15.0f / (  4.0f * PI ) ) * y * z           +
    red_sh6 * sqrt(  5.0f / ( 16.0f * PI ) ) * ( 3 z * z - 1 ) +
    red_sh7 * sqrt( 15.0f / (  8.0f * PI ) ) * x * z           +
    red_sh8 * sqrt( 15.0f / ( 32.0f * PI ) ) * ( x * x - y * y );
  • 上の式については可読性のために最適化していないものなので, 実際に実用上で使う場合はもっと計算を最適化したものを使っていると思います.
  • あと一旦, 全方向から入射する放射輝度を SH 係数に落とし込んだ場合, 入射している全方向の放射輝度からイラディアンスを求めるのはただの定数の乗算で行うことができるので便利です.
  • 話を戻して Black Ops2 のライトプローブのデータの持ち方についてです.
  • 一方で Black Ops 2 だと下図のように, RGB の色合い(float 3個)とイラディアンスのインテンシティ(env_sh9, float 9 個)だけを持つような形式です.

  • この方式の方がデータ数が減りますが, ライトプローブで表現できる色合いが乏しくなると思います.
  • では, "Black Ops 2 での環境キューブマップの新しい正規化" の一連の手順は下のようになります.
  • (A) オフラインの手順
    • 1. 環境マップを撮影した位置座標(env_pos)での全方向のイラディアンス(env_sh9)を求めます.
  • (B) 頂点シェーダ
    • 2. 頂点シェーダで, 頂点法線の方向の環境マップの全方向のイラディアンス(env_sh9)を参照して, 頂点法線方向のイラディアンス(env_irradiance)をピクセルシェーダに渡します.
  • (C) ピクセルシェーダ
    • 3. 環境キューブマップ(env_map)を参照して, 頂点シェーダから受け取った頂点法線の方向のイラディアンス(env_irradiance)で除算して正規化します.
    • 4. その正規化した結果に対して, ピクセルの平均のイラディアンス(ライトマップやライトプローブから取得したもの)を乗算してスペキュラ間接光(env_color)とします.
  • 上の説明の元になった擬似コードを下に貼っておきます.


  • 以前の Black Ops の環境キューブマップの正規化は平均イラディアンスで一括でキューブ環境マップを正規化していました.
  • 一方で今回の Black Ops 2 だと, 頂点法線単位で環境キューブマップの正規化係数を求めて, ピクセルシェーダに渡して正規化しています.
  • 従って, 環境キューブマップを使ってスペキュラ間接光を求めた際に, 過度に暗くなる現象が解消されました.
  • この方法だと頂点シェーダの計算が増えますが, Black Ops 2 では主にピクセルシェーダネックなので明確に処理が落ちるということはなかったそうです.
  • あと, 精度的に問題がなければ環境キューブマップを事前に全方向のイラディアンスで正規化しておく方法もあるのかな? と思いました.

環境キューブマップの事前フィルタリングの改善

  • 以前の Black Ops では環境キューブマップの事前フィルタリングの結果が正しくなかったので, Black Ops 2 ではきちんと計算するようにしています.
  • 具体的には, 環境キューブマップの各ミップレベルごとに該当する gloss と specular power の値を求めて, それを AMD CubeMapGen の Phong の cosine power フィルタを適用してフィルタリングしています.
  • AMD の CubeMapGen を使う際には Phong specular power (α_phong) が必要なので, α_phong = α_blinn_phong / 4 を使って

Blinn-Phong の specular power (α_blinn_phong)から近似的に求めた値を利用してフィルタリングしています.

  • 下図が以前の Black Ops の結果です.


  • 下図が今回の Black Ops 2 の結果で, 以前の結果と比べると gloss の値に対してリニアにぼけるようになっています.


  • 上の説明の元になったスライドを下に貼っておきます.

Environment BRDF

  • もし環境マップを使って, ある点を正しくシェーディングする際には下のような式になります.

  • 上の数式の Env(l)が環境マップで, BRDF_env(l, v, h)が今回の Black Ops 2 で使っているスペキュラ計算, ∫が半球状に対してこの計算を行うことを意味しています.
  • しかし, これをゲーム向けにリアルタイムに計算するのは重すぎます.
  • 従って, 下図のように 環境マップのライティングの項(Environment map filtering)と 環境BRDF( Environment BRDF) に分けてそれぞれの結果を乗算するという近似をします.

  • 上の左側の Environment map filtering については AMD CubeMap Gen で事前フィルタリングして使った結果を利用します.
  • 一方で Environment BRDF については下図のように Fresnel の Schlick 近似を展開してから, Mathematica で近似式を求めてピクセルシェーダで計算しているそうです(詳しくはコースのノートを参照).


  • 下図が Environment BRDF がなしの場合です. 赤いトラックの暗がりの部分のスペキュラ間接光が不自然に明るいです.


  • 下図が Environment BRDF が有効な場合です. 赤いトラックの暗がりの部分のスペキュラ間接光が不自然に明るいのが解消されています.


  • 補足で, Environment BRDF の近似計算については Unreal Engine4 で別の手法が紹介されていました.

その他 : Mathematica を使ったシェーダ計算の最適化

  • スペキュラシェーディング用の計算のうち, フレネルやマイクロファセットの可視性(V)の計算については Mathematica で近似式を求めて利用したそうです.