GLSL
RayTracing

脱・完全鏡面反射~GGXについて調べてみた~

More than 1 year has passed since last update.

概要

この記事はレイトレ Advent Calendar 2016の20日目の記事です。
昨年8月、レイトレ合宿3!!!に初参加してレイトレを始めて実装したのですが、自分が一番最初に実装したマテリアルはランバート、完全鏡面とガラスでした。このような単純なマテリアルでも実際に絵が出力されると楽しいものです。しかし欲が出てくると

Roughnessを持った金属表面を描画したい…。さらに言えばRoughnessをテクスチャで制御してディティールアップを狙いたい…。

という気分に駆られるようになります。しかし、いざ実装しようと調べてみるとGGX, Beckmann, Blinn, Phongなどのあらゆる人名が登場し、初学者の前に立ちはだかります。そこで今回はBlenderのCyclesレンダラーなどでも用いられているGGXという法線分布モデルを例に説明したいと思います。

2017/09/17追記
* Microfacetモデルの詳細については、レイトレ合宿5アドベントカレンダーでtatsyさんが書かれた記事『Microfacetモデルの重点サンプリング』が参考になるかと思います
* 英語ではありますが、Eric Heitz氏による "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" という論文もかなりまとまっていて参考になるかと思います

今回考えるシーン

例として、光源から発せられた光が十分に細かい凹凸を持った平面に反射して観測者まで届く場合を考えます。下図は凹凸を拡大して表したものです。それぞれのミクロな面に当たった光は完全鏡面反射をするとします。

Microfacet_s.jpg

$\def\bm{\boldsymbol}$
$\bm{l}$ : 光が入射してくる方向への単位ベクトル
$\bm{v}$ : 光が反射していく方向への単位ベクトル
$\bm{h}$ : ハーフベクトル(=ミクロな 面の単位法線ベクトル)
$\bm{n}$ : 表面の マクロな 単位法線ベクトル

Torrance-Sparrow model

「Microfacet BRDF」と調べると大体この数式にたどり着きます。分子の各項$D, G, F$については後述しますが、それらの選び方によってシェーディング結果も変わります。

f_r(\bm{v}, \bm{l}) = \frac{D(\bm{h})G(\bm{l}, \bm{v})F(\bm{v}, \bm{h})}{4|\bm{l} \cdot \bm{n}| |\bm{v} \cdot \bm{n}|} \\

ここで、 \bm{h} \equiv \frac{\bm{l} + \bm{v}}{|\bm{l} + \bm{v}|}

D: Microfacet Distribution Functions

Normal Distribution Functions, NDFとも呼びます。
ミクロな面の法線の向きがどのように分布しているかを表す項です。$D(\bm{h})$が何を表しているかをかなり大雑把に言うと、細かい凹凸上の微小な面が$\bm{h}$の向きを向いている割合だと言えるでしょう。

GGX(Trowbridge-Reitz model)

1975年にTrowbridge, Reitzが導出した分布関数で、GGXという名前は2013年にWalterがこの分布を独立して導いた際につけた名前のようです。

D_{GGX}(\bm{h}) = \frac{\alpha^2}{\pi \left[ 1 - (1 - \alpha^2) (\bm{n} \cdot \bm{h})^2 \right]^2}

$\alpha$は物体表面の粗さ(Roughness)を表すパラメータです。

G: Masking-Shadowing Fucntion

Geometry term, Visibility termとも呼びます。
光源からやってくる光がどの程度遮蔽(Shadowing)されるか、また観測者から物体表面がどの程度遮蔽(Masking)されるのかを表す項。

Separable Masking and Shadowing

$G(\bm{l}, \bm{v})$が、光源の向き$\bm{l}$に依存する項$G_1(\bm{l})$と観測者の向き$\bm{v}$に依存する項$G_1(\bm{v})$の積で表すことができると仮定します。$G_1$を Smith Masking Function と呼ぶようです。

G(\bm{l}, \bm{v}) \approx G_1(\bm{l}) G_1(\bm{v}) = \frac{1}{1 + \Lambda(\bm{l})} \cdot \frac{1}{1 + \Lambda(\bm{v})}\\

ただし、\Lambda_{GGX} (\bm{x}) = \frac{-1 + \sqrt{1 + \alpha^2 \left[ \frac{1}{(\bm{x} \cdot \bm{n})^2} - 1 \right]}}{2}

Height-Correlated Masking and Shadowing

Smith Joint Masking-Shadowing Function とも呼びます。上記のモデルではミクロな凹凸による高さを考慮しておらず、$\bm{l}$と$\bm{v}$の向きが近い場合不正確になってしまうようです。それらを考慮した$G$項が次のものとなります。

G(\bm{l}, \bm{v}) = \frac{1}{1 + \Lambda(\bm{l}) + \Lambda(\bm{v})}

F: Fresnel term

水の表面を真上から見た場合と浅い角度から見た場合、浅い角度の方が反射されて写り込んでいる景色が濃く見えたという経験はないでしょうか。このように入射光が物質の境界で反射する場合、反射される光の強さは入射角と物質の屈折率に依存します。それらの効果を表すのがこの$F$項です。一般的に、金属よりも水などの非金属の方が角度による反射率の変化が大きいです。

Schlick近似

F(\bm{v}, \bm{h}) = F_0 + (1 - F_0)(1 - |\bm{v} \cdot \bm{h}|)^5

ここで$F_0$は面に対して垂直に入射した場合の反射率です。

表面の粗さαについて

表面の粗さを示すパラメータ$\alpha$は直接指定してもよいのですが、そのままだと扱いづらい場合があるので他の値を介して決定している場合が多いように思えます。

Siggraph2012における講演『Physically Based Shading at Disney』の内容によると$\alpha$とは別に、アーティストが設定するRoughnessという値を用いて

\alpha = (\mathrm{Roughness})^2

のようの$\alpha$を決定しています。$\alpha$を直接指定せずに上記の式を用いる理由は、見た目がより 感覚的にリニア に変化しているように見えるからだということです。

Unity5.3以降のStandard shaderではGGXが使用されており、Roughnessから$\alpha$へのマッピングは上記のDisneyの講演に基づく式を利用しています。ただし、インスペクターからはSmoothnessという数値を介することでRoughness, $\alpha$を決定しています。

\mathrm{Roughness} = 1 - \mathrm{Smoothness} \\
\alpha=(\mathrm{Roughness})^2

Unityと見た目を合わせたい人は上記の式を使えば良いと思います。

一方、パストレーシングなどについて詳しく解説してある有名な書籍『Physically Based Rendering: From Theory To Implementation』の著者が開発しているレンダラーpbrt-v3にはRoughnessToAlpha()という関数が定義されており、以下の関数でRoughnessから$\alpha$を決定しています。

\alpha = 1.62142 + 0.819955x + 0.1734x^2 + 0.0171201x^3 + 0.000640711x^4 \\
\ \\
ただし、x = \log (\mathrm{Roughness})

グラフ

このRoughnessToAlpha()関数、何かを多項式近似しているような雰囲気はあるのですが書籍内にも詳細な解説が書かれていないのでどこから導かれたのかが分かりませんでした…。どなたか出典元知っている方は教えていただけると幸いです。

計算負荷について

レイトレなどで長く時間をかけてレンダリングできる場合は良いのですが、GGXは比較的計算負荷が高いためリアルタイム用途ではプラットフォームの性能によって切り替えたり、別の関数を用いたりの工夫がなされているようです。自分はあまりリアルタイム関係に詳しくないのでUnityのStandard shader内での実装を例に挙げます。ダウンロードしたBuilt-in shader内にあるUnityStandardBRDF.cginc 内部にマイクロファセットモデルが実際に使用されています。デスクトップやモバイルなどの環境によって使うBRDFが切り替わるようになっており、計算負荷が高く見た目が良い側からBRDF1_Unity_PBS(), BRDF2_Unity_PBS(), BRDF3_Unity_PBS()となっているようです。

BRDF1_Unity_PBS()

D: GGX or Normalized BlinnPhong (UNITY_BRDF_GGXが定義されているかどうかに依存)
G: Smith Joint or Smith Beckmann (UNITY_BRDF_GGXが定義されているかどうかに依存)
F: Schlick approximation

BRDF2_Unity_PBS()

D: BlinnPhong or [Modified] GGX
G: Modified Kelemen and Szirmay-​Kalos
F: Fresnel approximated with 1/LdotH

BRDF3_Unity_PBS()

D: Normalized BlinnPhong in RDF form
G: Implicit Visibility term
F: No Fresnel term

デモ

Shadertoyで実際にライティングしてみました。計算負荷等は考えずほぼ数式そのまま実装していますが、実際に利用する場合は計算順序などを気にしたほうがよいかもしれないです。マウスをドラッグすると光源の向きとRoughnessを変化させることができます。
https://www.shadertoy.com/view/Mt3XWj

参考文献

  • 『Physically Based Rendering: From Theory to Implementation, Third Edition』: p533-p552辺り
  • Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
  • Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs]
  • hanecci’s Blog『フレネル反射率について』: フレネル反射について