物理ベースレンダリング(1)

物理ベースレンダリングを考察するにあたって、事始めに、ガンマ補正とLambert拡散反射モデルに関して考察してみます。

物理ベースレンダリング?

Physically Based Renderingとは、たぶん物理現象に基づいたレンダリングのことを指す言葉だと思うのですが、この「物理現象に基づいた」というのは、とても恐れ多い言葉だと思います。物理現象に正確に準拠するのは、ほぼ不可能に近いです。一番写実的な表現である、写真ですら既に物理的に正しくない状態になっています。半導体受光素子やフイルムの感光特性によって色は歪んでいますし、ノイズもたっぷりです。写真の鑑賞に使う、LCDや印画紙も然りです。では、この世にあるほぼすべてのレンダリングは物理的に正しくないわけですが、なにをもって物理ベースレンダリングと言うのでしょうか。おそらくその境界にあるのは、光をエネルギーとして解釈するか、単なるRGBという数字の3要素として解釈するかの違いではないかと思います。あとは本人の心がけ次第(?)のような気がします。ですから、HDRを導入したから、ガンマ補正を正しく行ったから、BRDFを導入したから、レンズの光学特性に基づいた被写界深度を導入したからなどは、一切関係ないと思います。

一番簡単な物理ベースレンダリング

上記の序章でいきなりハードルを下げた感じがしますが、物理ベースレンダリングの要素で一番簡単なものは何かなと考えてみましたが、まずは、ディスプレイやテクスチャのガンマを、正しく理解して扱うということではないかと思います。光をエネルギーとして考えるならば、その量の表現は線形であるのが一番扱いやすいと思います。そうすれば、例えば複数のライトが存在するときに、そのシェーディング結果を加算で合成しても辻褄が合います。このガンマを考慮したレンダリングについて、丁寧に解説してあるのが、GDC2010のNaughty DogのJohn Hableさんによる、Uncharted 2: HDR Lightingのセッションではないかなと思います。このセッション自体はHDRレンダリング全般について触れられていますが、HDRレンダリングを行う際に不可欠な、ガンマ補正の考慮について詳しく触れられています。ガンマ補正の考慮は大きく分けて二つあります。

ディスプレイに出力する際に考慮するガンマ

ディスプレイはそれぞれ規定されたガンマ値を持っています。私が使っているディスプレイはsRGB準拠です。従ってガンマ補正値は約2.2です。つまり入力された値color:(0.0~1.0)をpow(color, 2.2)して出力します。なぜこうなっているかというと、人間の目が敏感に反応する、暗部のコントラストの分解能を保つためです。このガンマ補正を考慮して、線形空間で表現されたレンダリング結果をそのままの形で出力するには、逆補正をかけて出力する必要があります。具体的には、pow(color, 1/2.2)‏として出力することで、この補正の後も、こちらが意図した値(エネルギー量)をディスプレイに出力させることが出来ます。これはアナログテレビ規格であるNTSC-Jの、テレビ側のガンマを2.2と仮定して、放送局の送信側でpow(1/2.2)とした信号を放送していたのによく似ています。ちなみにsRGBの正確なガンマ補正のプロファイルは、pow(2.2)とは若干異なります。sRGB準拠モニターが使用するべきガンマ補正のプロファイルは別途詳しく定義されています。

アーティストが作成したテクスチャの持つガンマ

たとえば、Lambertシェーダーで使用される、Diffuseテクスチャを物理的に定義するならば、Albedo(geometric albedo)という、入射した光をどれだけ反射するかを定義した、反射能という物理量になります。この反射能を用いてシェーディングの計算するならば、反射能は線形で表現されるのが一番自然でしょう。このAlbedoテクスチャをアーティストに作成してもらうとしますが、アーティストが作成したテクスチャにも、ガンマ補正が必要なケースがあります。8bitカラーを用いているケースでは、ほとんどのケースで、ガンマ補正を前提として格納されています。理由はディスプレイのケースと同様で、暗部の分解能を保つためです。これを線形に補正するわけですが、テクスチャにガンマに関する情報が正確に記載されれている場合はそれに従うのが一番です。記載の無いケースでは、そのアーティストが使用しているディスプレイのガンマにあわせて補正する必要があります。それも不明なケースでは、事実上大多数であるsRGBだと仮定して、テクスチャから読み出した値をpow(color, 2.2)して利用するしかありません。

ここまでで

ここで、伝統的なLambertシェーダーを考えてみます。

albedo = tex2D(sampler, UV);
lambert = albedo * dot(N, L);
return lambert;

伝統的なLambertシェーダーを、線形空間でシェーディングして、ディスプレイのガンマを考慮して出力すると、下記の様になります。

albedo = pow(tex2D(sampler, UV), 2.2f);
lambert = albedo * dot(N, L);
return pow(lambert, 1.0f/2.2f);

次にNdotLを考える

NdotLは何を計算しているのでしょうか。これを考える前に、まずLambertの余弦則を考えてみます。これは拡散反射をする平面を定義しています。この平面は入射した光を完全に拡散反射します。つまり入射光の向きは関係なく、入射光の量によってのみ、その放射光の量が変わります。放射光の強度はその平面の法線と、観測者の成す角度の余弦(cos)に比例します(下の1枚目の図)。加えて、観測者に届く放射光の量は、観測者から見た単位立体角あたりでは、その平面と観測者の成す角度に関係なく一定であるというものです(下の2枚目の図)。

ピクセルシェーダーが処理するピクセルは、まさに、とある平面を単位立体角で観測したものとみなすことが出来ます。ということは、Lambertの余弦則に従えば、観測される光の量は、平面の法線方向に関係なく一定ということになります。ではNdotLは何を計算しているかというと、単位面積当たりに届く入射光の量になります。この計算は簡単な三角関数の演算です。NdotLは、実は入射光の光束密度の計算をしているに過ぎず、放射光の量を計算しているわけではないのです。
ちなみに、このシェーディングモデルは、完全に拡散反射をする平面が理想的に平らであるケースを想定したシェーディングモデルということになります。理想的な拡散反射をするのに、理想的に平らな平面というのは、なんだか大きな矛盾をはらんでいる気がします。

Lambert散乱平面が放射するエネルギー

NdotLの由来が入射するエネルギー量というのはわかりました。この入射したエネルギーの幾らかは、平面の拡散反射で放射されるわけですが、その総量はエネルギー保存の法則により、入射したエネルギーの量を超えることは許されません。つまり、放射光を平面上の半球で積分したときに、入射光を超えてはならないのです。放射光は法線と成す角θで、その強度がcos(θ)で減衰することは先ほどのLambertの余弦則で示されました。従ってcos(θ)を半球積分すれば、Lambert散乱平面が放射する光の総量がわかるはずです。

cos(θ)を半球で積分したらπになりました。ということは、入射光を1とするとき、平面のAlbedoが1の場合、法線方向の放射光は1/πということになります。つまりNdotLはπで割る必要があるということです。そうしないと、入射光のπ倍の光を放射することになり、明らかに物理法則に反することになります。
最終的に先のシェーダーは以下の様になります。

albedo = pow(tex2D(sampler, UV), 2.2f);
nomalizedLambert = albedo * dot( N, L ) * 1.0f/PI;
return pow(normalizedLambert, 1.0f/2.2f);

これで、最近なにかと目の敵にされるLambertシェーディングですが、物理ベースレンダリングにすることが出来ました。

物理ベースレンダリングの功罪

この程度の考察で、物理ベースレンダリングを語るなと先輩諸氏に怒られそうですが、なんでもかんでも、「物理法則に従うこと即ち正義」のような考え方は、要らぬ苦労をする羽目につながると思います。最後の項で示したLambertの正規化は、要はライトの強度をπ倍してしまえば、正規化前と同じ結果が待っています。ガンマ補正に関しても、補正を考慮して、線形空間でシェーディングを行うと、いくらか現実のものに近い結果が得られるようになりますが、それが、ゲームのグラフィックスで求めている結果かどうかは全くの別次元の話です。またその一方で、HDRテクスチャを導入すると、LDRテクスチャのガンマ補正を行わないと、明らかに辻褄が合わなくなってしまいます。その他のケースでも、何らかの明確な基準がないと、複雑化するシェーディングの多様な要素の間で、お互いの辻褄を合わせることが困難になります。その「折り合いを付ける基準」をたまたま物理法則が担っていると捉えるのが、ゲームグラフィックスには一番幸せかもしれません。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中