この記事について
- この記事では, コンピュータグラフィックスのマイクロファセットの分布関数について簡単に説明します.
- 具体的には 6 種類のマイクロファセットの分布関数の長所と短所をざっとピックアップします.
- 元ネタは SIGGRAPH 2012 や 2011 の Cources です.
結論 : どのマイクロファセットの法線の分布関数D(m)がゲーム向けに良いか ?
- 無難なのは (a) Blinn-Phong NDF だと思います.
- もしシェーダの計算コストに余裕があるなら (c) Trowbridge-Reitz (GGX) NDF や (d) Generalized-Trowbridge-Reitz が良さそうです.
マイクロファセットの法線の分布関数 D(m) とは ?
- 法線マップのように通常のマクロな法線を n としたときに, それよりも細かい表面をマイクロファセットと言い, その法線を マイクロファセットの法線 m と言います.
- ライトベイクトルを l , 視点ベクトルを v としたときに, そのハーフベクトルを h とします.
- このとき, スペキュラを跳ね返すマイクロファセットは下図のようにその法線がハーフベクトル h に一致したものだけ( m = h )です.
- D(m) はマイクロファセットの分布関数です. 例えばハーフベクトル h を代入した場合には, D(h) はスペキュラを跳ね返すことができるハーフベクトル h と一致する法線を持つマイクロファセットの分布の値を取得していることになります.
- D(m) の値が取り得る範囲は 0 〜無限大で, 単位系は 1/sr です.
- しかし, D(m) は下図のようにマイクロファセットを射影した面積の球積分の和が 1 になる必要があります. (法線の保存則っぽい感じ)
- この拘束条件を満たすために, D(m) の正規化係数を求めて D(m) に乗算する処理を行います.
- つまり, 正規化係数を求めて乗算する理由はマイクロファセットを使ったライティングの計算結果のエネルギー保存則を満たさせるためです.
- D(m) は英語では NDF (Normal Distribution Function)と言います.
- 分布関数を評価する際には実装した際の見た目だけではなく, 数式や BRDF をグラフで可視化した方がより大局的に評価できるのではないか ? と思っています.
- 下図は論文から引用した D(m) の厳密な定義です.
- 上の文章を訳してみます.
- マイクロファセットの法線の分布関数 D(m) は 「マイクロ表面上に分布する表面の法線 m の統計的な分布」を表しています.
- マイクロファセットの法線 m を中心とした微小な立体角を dωm とすると, その立体角に含まれている「表面の法線 m のマイクロファセットの面積の合計」は D(m) * dωm * dA となります.
- 従って, D は 1/ステラジアンを単位系とする密度関数です.
- 有効なマイクロファセットの分布 D(m) は少なくても, 以下の条件を満たす必要があります.
- D(m) は 0 以上の正の値で, 0 <= D(m) <= 無限大 数式(1) を満たします.
- 上の文章を訳してみます.
- マイクロファセットの面積の合計は, 少なくても該当するマクロ表面の面積以上である必要があります. 数式(2)
- (符号付きの)射影されたマイクロ表面の面積は 任意のベクトル方向 v に対して射影した マクロ表面の面積と同じである必要があります. 数式(3)
- v = n の特殊なケースだと 数式(4)のようになります.
マイクロファセットの法線の分布関数の評価の基準
- (鄯) 計算コスト : 数式をシェーダで計算する際の計算コストです.
- (鄱)一様分布を表現できるか ? : 一様分布とは表面が非常に粗い場合に, 一様にスペキュラが反射することです.
- (鄴)スーパーラフを表現できるか ? : スーパーラフとは表面が非常に粗くて, かつ 視線ベクトル(この場合, 表面の点->視点位置へのベクトル)とライトベクトル(表面の点->ライトへのベクトル)の向きがほぼ同じときに, ハーフベクトルと直交ぎみの法線が反射するライティングの量が増えることです. 下図のように, V 字のマイクロファセットが反射する光の量が増える Oren-Nayer のディフューズと挙動が似ています.
- (鄽)BRDF の実測値のデータベース(Matusik database)と近いスペキュラ表現ができるかどうか ?
一様なスペキュラと, スーパラフなスペキュラの見た目
- 下の全ての図には白いディフューズライトを当てています.
- 物体が跳ね返すディフューズライティングの成分が 0 で, スペキュラライティングだけを跳ね返すようにしています.
- またフレネルの値は (0.8, 0.8, 0.8) です.
- 表面が滑らかな場合のスペキュラ(GGX NDF でα = 0.1)
- 表面が粗いので一様になったスペキュラ(GGX NDF でα = 1.0)
- 完全ランバート反射と似た見た目になっていますが, スペキュラなのでディフューズアルベドの色を含まず, ライトの色だけになります.
- スーパラフなスペキュラ(GGX NDF でα = 4.0)
- 視点ベクトルとライトベクトルの向きがほぼ同じときに, ハーフベクトルと直交ぎみの法線が反射するライティングの量が増えています.
(a) Blinn-Phong NDF
- 長所
- 他の分布関数に比べると, 計算コストが低いです.
- 一様分布を表現することができます.
- 短所
- 説明
- αp はラフネスで値の範囲は 0 〜∞です.
- 完全に粗い面だとαp = 0 で, 完全な鏡面の場合は αp = ∞ となります.
- 実際にゲームで使う際には αp の範囲は 0 〜 2048 ぐらいで, [テクスチャの値] = log max_m αp (max_m は αp の上限)として使ったりします.
- 具体的に Call of Duty : Black ops や FarCry3 だと αp = 1-8192 とし, αp = 2^(13g) として テクスチャにグロスの値 g を 0.0-1.0 として書き込んでいました.
- 最後に Blinn-Phong が取る値の範囲について書いておきます. 下図のように表現が粗い場合(αp= 1〜8)にはD(m)の最大値は 0.3〜1.6, 非常に滑らか(αp=256〜2048)のときには最大値は 40.0 〜330 ぐらいになります.
// シェーダの例 (BRDF Explorer の d_blinnphong.brdf から引用 )
analytic
# Blinn's 1977 phong variant
# normalization constant:
# 1/Integrate[Cos[x]^n Cos[x] Sin[x], {x, 0, Pi/2}, {phi, 0, 2 Pi} , Assumptions -> n > 0]
# == (2 + n)/(2 Pi)
# variables go here...
# only floats supported right now.
# [type] [name] [min val] [max val] [default val]
::begin parameters
float n 1 1000 100
bool normalized 1
::end parameters
# Then comes the shader. This should be GLSL code
# that defines a function called BRDF (although you can
# add whatever else you want too, like sqr() below).
::begin shader
const float PI = 3.14159265358979323846;
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
vec3 H = normalize(L+V);
float D = pow(max(0, dot(N,H)),n);
if (normalized)
D *= (2+n) / (2*PI);
return vec3(D);
}
::end shader
(b) Beckmann NDF
- 長所
- 短所
- Blinn-Phong NDF よりも計算コストが高いです.
- 表面がなめらかなときは, Blinn-Phong NDF と結果がほぼ同じになります.
- 説明
- Phong のパラメータを αp とすると, αp = 2 (αb)^( -2 ) - 2 となります.
- ただαb はラフネスではなく, マイクロファセットの傾きの平均値の平方根となります.
// シェーダの例 (BRDF Explorer の d_beckmann.brdf から引用 )
analytic
# Beckmann distribution, from Cook-Torrance
# with added 1/PI normalization factor
# variables go here...
# only floats supported right now.
# [type] [name] [min val] [max val] [default val]
::begin parameters
float m 0.001 8 .1
::end parameters
# Then comes the shader. This should be GLSL code
# that defines a function called BRDF (although you can
# add whatever else you want too, like sqr() below).
::begin shader
const float PI = 3.14159265358979323846;
float Beckmann(float m, float t)
{
float M = m*m;
float T = t*t;
return exp((T-1)/(M*T)) / (PI*M*T*T);
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
// compute the half vec3
vec3 H = normalize( L + V );
float NdotH = dot(N, H);
float D = Beckmann(m, NdotH);
return vec3(D);
}
::end shader
(c) Trowbridge-Reitz (GGX) NDF
- 長所
- 短所
- Blinn-Phong NDF よりも計算コストが高いです.
- 説明
- Blinng-Phong よりもピークが狭く, またピークから離れたときも小さい値(テール)が残っています.
- αtrが小さいほど表面が滑らかで, αtr が大きいほど表面が粗くなります.
- 具体的には αtr = 0.1-0.4 で滑らかなときは, Blinn-Phong NDF のαp=16-128 とグラフが似ます.
- また αtr = 0.4-1.0 で表面が粗くなっていくときには, Blinn-Phong NDF のαp=1-8 とグラフが似ます.
- αtr = 1.0 のときは表現が粗いので一様分布になり, αtr = 1-7 のときにはスーパーラフの状態になります.
// シェーダの例 (BRDF Explorer の d_ggx.brdf から引用 )
analytic
# ggx from Walter 07
# variables go here...
# only floats supported right now.
# [type] [name] [min val] [max val] [default val]
::begin parameters
float alpha 0.001 7 .1
::end parameters
# Then comes the shader. This should be GLSL code
# that defines a function called BRDF (although you can
# add whatever else you want too, like sqr() below).
::begin shader
const float PI = 3.14159265358979323846;
float sqr(float x) { return x*x; }
float GGX(float alpha, float cosThetaM)
{
float CosSquared = cosThetaM*cosThetaM;
float TanSquared = (1-CosSquared)/CosSquared;
return (1.0/PI) * sqr(alpha/(CosSquared * (alpha*alpha + TanSquared)));
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
vec3 H = normalize( L + V );
float D = GGX(alpha, dot(N,H));
return vec3(D);
}
::end shader
(d) Generalized-Trowbridge-Reitz (一般化した Trowbridge-Reitz)
- GGX や Beckmann だと, 実測値のスペキュラに比べてテール(小さな値の伸び)が短いという問題がありました.
- 下図の左のグラフの横軸は N と H の角度(0-360度)で, 縦軸が D(m) の値です. これを見ると, 実測値の黒線は角度が10 度以降でもスペキュラの微弱な値が残っているのに対し, 赤線の GGX は 角度6 度付近で値が 0.0 になり, また青線や緑線の Beckmann や Blinn-Phong は角度 2.5 度付近で値が 0.0 になり, テールが短くなってしまっています.
- そこで 下図の D_Gtr の数式のように GGX(D_TR)の分母の累乗の部分をγとして一般化しています. γ=1 のときは D_Berry になり, γ=2 のときはGGX になります.
- この数式を使うと, 下図のように γ=1 のときはテールが長いスペキュラになり, 一方で γ =10 のときは Beckmann と近い結果になります.
- 長所
- γ=1〜10 と変化させることで, 実測値に近いスペキュラや Beckmann を表現することができ, 表現力が上がります.
- 短所
- Blinn-Phong NDF よりは計算コストが高いです.
- パラメータが ラフネスα と γの 2 つになります.
- 下図が γ=1 で D_Berry のときで, 上図に比べるとスペキュラライティングでテール部分が長くなり, 明るい領域が増えています.
(e) ABC NDF
- 長所
- BRDF の実測値のデータベース(Matusik database)に近いスペキュラ表現ができるそうです.
- 短所
- この式だと, 正規化係数 kabc の計算が複雑なのでシェーダの計算コストが高いです. (テーブルで持っておけばいいのかも)
- パラメータが αabc1 と α abc2 の 2種類あり, αabc2 = 1.0 と αabc2 = 2.0 のときに特異値になっていしまいます.
- スーパーラフを表現することができないです.
- 説明
- (c) の GGX NDF よりもピークの幅が狭く, また小さい値(テール)が残っています.
(f) Shifted Gamma Distribution NDF (略称:SGD)
- 長所
- BRDF の実測値のデータベース(Matusik database)に近いスペキュラ表現ができるそうです.
- スーパーラフも表現できます.
- 短所
- この式だと, 正規化係数 p22[x] の計算が複雑なのでシェーダの計算コストが高いです.
- パラメータが αsgd1 と α sgd2 の 2種類あります.
- 説明
- 実はパラメータが RGB ごとにあるらしく, またこれ用のフレネルは実際のフレネルとは違う挙動をする関数なので, 他の NDF との比較はしづらいとのことです.
参考文献
- "Practical Physically Based Shading in Film and Game Production". SIGGRAPH 2012
- "Advances in Real-time Rendering in 3D Graphics and Games". SIGGRAPH 2011
- BRDF Explorer (Walt Disney)
- The Blinn-Phong Normalization Zoo
- "Microfacet Models for Refraction through Rough Surfaces"
- Bruce Walter, Stephen R. Marschner, Hongsong Li, Kenneth E. Torrance
- Oren Nayer