点光源やディレクショナルライトなどのリアルタイムでお馴染みなPunctual LightがPBRにおいてどのような扱いがされているのかまとめた記事です。Punctual Lightによるハイライトが異常に明るくなる原因などについて簡単に書いています。
Punctual Lightとは
Punctual Lightとは点光源(Point Light)、スポットライト(Spot Light)、ディレクショナルライト(Directional Light)といった面積を持たない光源の総称です。Unityなどのゲームエンジンとかやっている人ならお馴染みなあのライト達です。
一般にPunctual LightはIntensityといった何らかの光の強さを表すパラメーターを持っており、例えば点光源からの反射光の輝度はBSDF、点光源の位置、点光源の方向、反射場所の位置を用いると次のような形で書かれます。
一方でディレクショナルライトの場合は次のように書かれます。
疑問
ここでちょっと疑問がありました。BSDFは一見反射率を示す関数の様に見えて値が0から1の範囲に制限されるように思えますが、BSDFは厳密には「放射照度Eに対して輝度Lを返す関数」であり、その範囲は0から∞までの値を取ることが可能です。実際GGXのBSDFでは粗さが0.01だと角度によっては平然と1を超えて非常に高い値を返すこともあります。
この性質を考慮しながら先ほどの点光源やディレクショナルライトの式を思い出してみると、BSDFが1よりはるかに大きければその反射光の輝度はIntensityを超えることが十分にあり得ますし、何なら無限に発散することすら許されます。つまり、Intensityがどんな小さい値でも反射光はいくらでも明るくなりえることが予測されます。
GGXのBSDF(α=0.1)を実装してIntesityが1の平行光源を用意し、反射光の輝度の値が1以上になる場所を赤色で表示してみるとハイライト部分が1以上になっていることが分かります。この例ではハイライトの中心部分は更に10以上の値を持つことが確認されました。
つまるところ、Punctual LightはBSDFによっては自らの明るさを示すIntensityをはるかに超える反射光を生じさせることがあるわけです。Intensityが輝度みたいなものと考えるとなんだかエネルギー保存が破られているように感じます。
Punctual Lightの理論的取り扱い
先ほどの議論ではPunctual Lightの式は一見してエネルギー保存則を破っているように思えました。この疑問に対する答えを先に言いいますとPunctual lightの輝度は無限大であるためです。意味がわからないと思いますが、これを正しいと認めれば反射光の輝度がいくら明るくなろうともエネルギー保存は守られることになります。
輝度が無限ならIntensityってなんなんだと言うことになりますが、ここで一度Intensityという量についてもう少し詳しく知るため、それぞれの物理量としての単位を見てみましょう。輝度の単位は、BSDFの単位はであることが知られていますので、ディレクショナルライトの式の単位について考えてみると、
ということになります。両辺の単位は一致しなければいけないので、Intensityの単位は放射照度の単位に相当することになります。一方で点光源の式では距離の二乗で割れているのでその分単位が変わり、エネルギーの単位を持つことになります。
こうして見てみるとどうやらIntensityは少なくともPunctual Lightの輝度ではないことがわかります。Punctual Lightの式は単純な輝度の掛け算をしている訳ではないようです。
話を戻してPunctual Lightの輝度が無限大になっているというのはどうゆうことか話していきます。そもそも光源の放射輝度とは光源が放つ光の放射束の面積、立体角微分として定義されています。Punctual Lightはそもそも定義として面積を持ちません。従って、なので放射束の面積微分(放射発散度)は無限大へと発散し、それに伴い放射輝度も無限大へと発散することになります。
具体例として点光源を球光源の面積0への極限としてモデル化して考えてみます。放射束の光を一様に放つ半径の球光源を考えて、その放射発散度は面積で
という風に書くことが出来ます。この時、方向における放射輝度は
となります。ここで点光源の極限として半径に対しての極限を考えると放射発散度と放射輝度は無限大へと発散することが分かります。
このようにPunctual Lightはその性質から輝度が無限大であり、反射光の輝度は如何なる値でもエネルギー保存を満たします。なので、実はハイライトがめっちゃ明るくなるのは正常な挙動であると言えます*1。
Punctual Lightの式の導出
Punctual Lightは一見すると単純なように見えて、真面目に考えると結構難しい概念です。シンプルにまとめられているあのPunctual Lightの式もその導出は単純ではありません。ここでは[ Naty Hoffman 2013]の資料を基にPunctual Lightの式の導出を見ていきます。
面積を持つ小さな光源からの光輸送を考えます。照らされている点においてから光源の中心への方向ベクトルとして、光源はそこからの角度の立体角で収められているものとします。
遮蔽とか他の光源とかはないとして、この時における入射光の輝度関数はとの角度がより大きければ0(光源がない)、小さければ何らかの値を持つという形で表すことが出来ます。
のレンダリング方程式は以下の様になります。
ここから光源をPunctual Lightへと変化させる極限を考えてPunctual Lightの式を導出していきます。まずは、Punctual Lightの極限は光源をその中心に向かって無限に小さくする形で考えるとします。この極限は光源の範囲を表すによってという極限として表すことが出来ます。従って、Punctual Lightにおけるレンダリング方程式は
という極限で表現されます。ここで、が0に向かうとするとBSDFとコサインは積分から取り出して次のように書けると述べられています。積分と極限の順序を入れ替えてるみたいですが、正しいのはちょっと追えていません。ここでは正しいとして話を進めます。
一番最後の極限値について求めるため、いったん球光源が真上に存在し、照射点のBSDFがLambertである状況を考えます。この時の反射光の輝度はLambertなので出射方向に依存せず、その値をと定義します。
ここでPunctual Lightの極限を「を保存しながら」という極限で明確に定義することとします。従って、Punctual Lightの極限を適用すると次のような方程式を得られます。
すなわち、輝度の極限は次のように求まります。
急にを保存する極限としてPunctual Lightを再定義しましたが、これは前節の議論の際Punctual Lightの放つ放射束は一定とするところから来ています。
分かりづらいですがは出射光の放射発散度を表す量であり、これが保存されているということは照射されている点への放射束(エネルギー)が一定であることを示します。すなわち、Punctual Lightからの放射束が一定であることを示しているわけです。従って、Punctual Lightの極限はが保存する極限として定義することが出来ます。
この結果を代入すれば
Intesityを定数と定義すれば今までのあのPunctual Lightの式を求めることが出来ます。
今までの導出は立体角表現のレンダリング方程式によって行っていたため、この式が適用可能なライトというのは「輝度を方向から指定できる」ようなものでないといけません。すなわち、これはディレクショナルライトの式となります。
一方で点光源は位置によって指定するものであるため、その式を求めるには3点形式のレンダリング方程式を用いる必要があります。(3.1)式を3点形式に変換すると
という形で書くことが出来ます。ここではを光源上の点の集合、は光源上の点の法線、は可視関数と定義しています。球光源の場合であるため、Punctual Lightの極限を適用すると以下の様になります。
Directional Lightと同様の議論を行えばいいので、点光源の式として次のように導出することができます。
まとめ
Punctual Lightはその式の単純さに反して、理論的な扱いはかなり複雑です。抽象化されたライトであるため、輝度が無限大であったりと非直感的な性質を持っていたりします。また、明るさを表すパラメーターであったIntesityというのは「ランバート面に真上から照射した時の放射照度(放射束)」に相当する量であったことが分かりました。
ここでの話はあくまで「PBRでPunctual Lightを解釈するならこんな感じだよね」というものであり、正直な所ここまで厳密に考える必要はないと思います(ほんとに厳密かはともかく)。実装ではもちろんIntesityの中身がどうこうとかは考えずに設定してもいいですし、理論的にもシンプルにデルタ関数として表現しても結論自体は変わらないと思います(極限がデルタ関数の近似になるし)。
参考文献
[Naty Hoffman 2013] SIGGRAPH 2013 Course: Physically Based Shading in Theory and Practice Background: Physics and Math of Shading
*1:Unityとかだとハイライトに上限値を入れて発散を防いでいたりします