シェーディング その2

前回はフォンの反射モデルを用いたシェーディングを説明しました
が、一箇所だけ通常と違う事をしましたね
環境光の材質設定をしない、という

なぜ材質設定をしなかったか、というと
従来の環境光の計算モデル…というか環境光そのものについての捉え方が間違っているためです
今回はその問題点の指摘と、解決策の提示です

問題点その1 材質設定

さて、前回は省きました環境光に対する材質の持つ反射特性…つまりambientカラーの設定ですが
現在主要なリアルタイム3DCGは環境光の色と材質の環境光反射色を設定します
OpenGLもDirectXもレイトレーシングのレンダラーなんかでもきっとあることでしょう
(グローバルイルミネーションのレンダラーは環境光設定はないと思いますが)

なぜそれが必要なのか?先ずは前回の環境光の色だけ設定してそのまま加算した結果を拡大して見てみましょう

多分、皆ちょっと違和感を覚えたと思います
緑のドラゴンに赤い光がついてしまったりして少し変ですね
緑色の表面に当たったら赤い色は吸収されて反射しないはず…
やっぱり材質設定をした方がよさそうです

あれ?そういえば材質設定は既にしているぞ?
拡散反射の方に色を指定しているじゃないか
じゃあこれを適用して描画すればいいんじゃないの?

ここで、環境光にマテリアルカラーを設定した場合の計算式を書きます
ちょっと複雑になってしまうんですが拡散は反射と鏡面反射のマテリアルカラーとの関係も含めて書くと
拡散反射光の強さをD、鏡面反射の強さをS、マテリアルの拡散反射の色Dcolor、鏡面反射の色Scolor、環境光の色をAとすると
反射光の色Bは

 B = D*Dcolor + S*Scolor + A*Dcolor ・・・式8

となります

ということで環境光にマテリアルカラーを乗算した結果がこれです

おお、なんかよさげじゃん
これでいいんじゃないの?と自分も思うんだけど

ここで環境光の明るさを強くしてみます
環境光を真っ白のR:1.0 G:1.0 B:1.0で描いてみます

うわあ、なんだかすごい事になっちゃったぞ
これはこれで面白いですね
さて、こんなにつぶれた色じゃシェーディングの意味がないです

では環境光反射色を設定してみましょう
環境光反射色をAcolorとすると次の式で反射色が決まります

 B = D*Dcolor + S*Scolor + A*Acolor ・・・式9

式9を用いて書いた結果がコレ

環境光は真っ白のまま、反射係数をマテリアルカラーの1/10にしました
良好な見た目ですね・・・
あれ?問題ないんじゃないの?

でも、待って欲しい

結局、既存のマテリアルの色を使っている
しかもわざわざ面倒にも1/10にして設定している
これなら環境光を弱めに設定すればいい
環境光の反射色という新たな数値設定をして、データと手間を増やして、結局同じような結果を得る
要らんわこんな設定

しかもDirectXの環境光反射色の設定の説明見てきたら、通常拡散反射光と同じ色設定をします
だと
要らねえじゃねえか!


一応ね、環境光の反射色を拡散反射と違う設定にすると次の画像のような効果が得られます

全て同じ環境光に照らされてますが反射光の色が違います
うーん何に使えばいいんだろうなコレ
場所によって色を変えるならそもそも環境光の色を変えて描けばいいし
一応拡散反射と色を別に計算するので、たとえばドラゴンは本来反射しない赤の光を帯びています
けど、あんまり必要ないと思う コレ

というわけで、まず環境光の反射色を設定する意味はほぼ有りません
面倒なだけです


とはいえ、フォンの反射モデルの真の完成形として
一応、ソースとサンプルを貼っておきます
サンプルページ

シェーダのソースコード


問題点その2 ベタ塗り

平行光源と環境光を使って球体を描画してみました

うむ、まあそれなりに綺麗ですね
でも光が当たっていないところが全部同じ色です
なんだかおかしいと思いませんか?

中学生の時、多分美術の時間に皆さんデッサンをやったと思います
(やってない方もいるかもしれませんが…)
そこで球体のデッサンでは次のような描き方をした方が良いと習うと思います

(画像は手書きじゃなくてCGですが…)

球体のふち部分を微妙に明るく描け、と
どうして?と思う方やなるほどと思う方、それぞれいると思いますが
このふちの明かり、美術の先生は多分どういうものかは上手く説明してないと思います
見たままを描けばこうなるわけなので、としか

このふちの明かりにはちゃんと物理的に意味があります
なんかそういう風に見える気がするんじゃなくて、実際にそのように見える理由が有ります

正体をズバリ言ってしまうと、照り返しです
球の下のテーブルや地面に当たった光が球を照らします
つまり、周囲の環境に跳ね返った光が照らし出してあのふちの明かりが出るのです

ということは、これは環境光による反射の結果起きるのです
でも、今のシェーディングだとふちは光っていませんね?
なぜか?
ちょっと、式8を思い出して見ましょう

 B = D*Dcolor + S*Scolor + A*Dcolor ・・・式8

これをよーく見てるとなんとなく気付きませんか?
直接光のDとSにはそれぞれ別の色を掛けて足し合わせているのに
環境光はDcolorの一つだけ
そうです 環境光のスペキュラーが足りません
実は式8の状態だと環境光の拡散反射光だけを描いているんです
これが今まで誰も気にもしなかったフォンの反射モデルの決定的な間違いです
じゃあ、環境光にもスペキュラーを入れてみよう!

 B = D*Dcolor + S*Scolor + A*Dcolor + A*Scolor ・・・式10

うnこれで早速球体を描いてみよう!

うn!ダメだ!
やっぱり考え方が違っていたのか?

いいえ、そういうわけではありません
実はスペキュラーの計算方法が間違いなのです


環境光の鏡面反射光計算モデル

まず、式10を吟味してみましょう
環境光の定義は360度ありとあらゆる方向から照らす光です
拡散反射光は入射光が全ての方向に均一に乱反射するので全ての面が同じ光で照らされますからA*Dcolorで問題ありません
同様に、鏡面反射光も全ての方向から照らされるのでやはり全ての面で同じように視点に光が届く…のではないのです 実は

これまでの鏡面反射光の説明では全ての面で同じ強さで反射を起こすものと仮定して計算してきましたが
実は鏡面反射には重要なある特性があるのです
CGに造詣の深い方ならば、もしかしたらピンと来ているかもしれません
フレネルの方程式です

フレネルさんは光の反射についてある事実に気付いたのです
入射光の角度によって反射光の強さが違う!と
屈折率の比率によってその境目で起こる反射のしかたにはある特性があると気付き研究しました

これはどういうことか身近な例で説明しますと、湖なんかを見に行ったとします
すると遠くの景色を見ると湖には山や森が反射して逆さに映っています
綺麗ですね
ちょっと遠くに飽きたので足元を見てみます
わあ、お魚さんが泳いでるかわいい!
・・・
遠くを見てたら水面は鏡のようだったのに近くを見たら自分の顔が映る事はなく水中が見えます(少しだけ映るけどね)
見る角度が違うからです

極端な話、面が視線と平行だと鏡面反射は最も強く、視線が面に垂直だともっとも弱い反射になります
これが俗に言うフレネル反射です
(フレネル反射という言葉は正式にはありません 俗語です)

さて、このフレネルの式ですが
すごく複雑です
説明する気も起きないのでwikipediaなりで調べてください
こんなもんリアルタイムの計算には使いたくないわ!

しかし、見た目にはこだわりたい・・・
昔の人も水面なんかの表現ではそんな風に悩みました
クソまじめにフレネルの式を解くのもやってやれない事は無いんですが、もっといい方法があります
昔の人は考えました(といってもかなり最近だけど)
雑だけど反射光の強さFは次の式で近似できるじゃないか、と

 F = f0 + (1.0 - f0) * e^( -6.0 * N・E ) ・・・式11

ここでf0という数値は面に垂直に光が入射した時の反射光の強さ
eはネイピア数、自然対数の底ですね
Nは今までどおり法線ベクトル、Eは面に対する視線ベクトル

フレネルの式に比べてちょっと単純な変化になってしまうのですがf0が小さいときなどは良好な結果となるようです
ちなみにf0ですが屈折率の比によって決まる値なので実は設定が難しいです
計測も難しいしデータも世の中にほいほい落ちているようなものではありません
まあ、経験的に設定しても良いですし、フレネルで検索すればいいデータがあるかもしれません

式11ですがこれはFSG方式と呼ばれる近似式で、FarCry3というゲームで使われたものだそうで
元々はschlickさんの考え出した近似式を、ガウシアン球というのを用いて近似する形に置き換えたものだそうです
こちらの方がschlickさんの近似式よりも計算コストが低いとのことです

ではこれを使わせていただくとして、環境光のスペキュラに適用します
このFを用いて式10を次のように変形します

 B = D*Dcolor + S*Scolor + A*Dcolor + A*F*Scolor ・・・式12

これで環境光の計算はほぼ完璧です!
(微細凹凸とか無視してますが)

この式12を用いて球体を描画しました結果がこちらです

なんだかふちが明るくなりましたね
現実のそれと比べてまだ少し変な感じがしますが、これはおそらく環境光の定義そのものが問題なのでしょう
やはり360度全ての角度に同じ光で照らされることはありません
まあ、フォンの反射モデルそのまま計算するよりもリアリティは増したと思います
なにせ物理的根拠に基づいて計算されているのでね!
(物理法則の式を導き出したのは全て先人たちですが 知恵をお貸しいただき本当にありがとうございます)

球体だけだとイマイチこの効果がどれほど高いものか掴みにくいと思うのでモデルを描画してみましょう
サンプルページ


シェーダのソースコード

ちなみに、式11で求めたフレネルの値ですが実は平行光源の鏡面反射のほうにも使えます
それだけでなく、全てのライトに使えます
というより、本来的には使わないといけません
フレネルの式は鏡面反射全てに当てはまりますので
でもいちいちライトごとに計算する必要はなく、視線と法線ベクトルの関数として表せるので一度計算してしまえば使い回しが効きます
そういった意味ではフレネルの項を追加しても計算量はさほど以前と変わらないんですね
ライトの数が少ないと若干こちらが高負荷になりますが

どうでしょうか?この新シェーディング
光源は平行光源と環境光のみですが、撮影技法で言う3点照明の様な効果が付いたと思います
画面奥の光が映し出されることでエッジが際立ち、濃過ぎる影を和らげる事も出来ます
今までの環境光は影の上げ底(フィルライト的な効果のみ)に過ぎませんでしたが、鏡面反射光を入れることでフィルライトとリムライトの役目を果たすようになります

ちなみにこのサンプルでは両者の見映えがなるべく近くなるように材質設定を調整しています
フォンの反射モデルでは環境光を拡散反射光と同じ反射係数にすると(この場合赤)、全く青と緑の光を反射しないので環境光の色によっては浮いてしまいます
そのため環境光の反射係数はR:1.0,G:0.18,B:0.18となっています
(新シェーディングは拡散反射光の反射係数と鏡面反射光の反射係数がそのまま使われます)
また、フォンの反射モデルには鏡面反射の計算でフレネルの項を入れてませんので、面に対して視線が垂直な時の減衰がありません
そのため、鏡面反射係数そのものを落としてなじませています


新反射モデル

さて、話はこれで終わりじゃありません
まあ、終わっても良いんですが…終わらせません

この新しいシェーディング手法ですが変わったのは実質環境光のみですが
ここで重要な事実がもう一つあります
式12を良く見てみましょう

 B = D*Dcolor + S*Scolor + A*Dcolor + A*F*Scolor ・・・式12

なんとなく、気になりませんかねDcolorとScolor
環境光にも拡散反射光と鏡面反射があるんですねえ・・・しみじみ
ちょっとこう書き変えてみましょうか

 B = (D*Dcolor + A*Dcolor) + (S*Scolor + A*F*Scolor)

うんん、なんだか纏められそうだぞ
お次は

 B = (D + A)*Dcolor + (S + A*F)*Scolor

うん?拡散反射光と鏡面反射光だけになったぞ
つまり、この反射の計算は

 反射光=拡散反射光+鏡面反射光 ・・・式13

であるわけです
つまり、フォンの反射モデルの間違いは拡散反射光に鏡面反射光を着け忘れていたことではなく
環境光を反射光の1要素だとしてしまった事です
環境光は光の反射に見られる現象ではなくて、ライトの一種として捉えるべきなのです
環境光は平行光源やポイントライト、スポットライトなどと同じライトなのです

そして式13こそ新しい反射モデルそのものです
なんか前よりさらに簡単な形になってますけどね
反射光は拡散反射と鏡面反射だけ考えればいい
実に当たり前の結論ですね
グローバルイルミネーションでもぶっちゃけ環境光なんて入ってないし

もう一つ重要なのは、環境光がライトであるということは専用の材質設定をする必要はない、と改めて断言できますね
平行光源とポイントライトでマテリアルを変える必要があるでしょうか?ないですね
環境光も拡散反射光と鏡面反射光に対する材質設定で計算できますし、性質も一緒です
マテリアルは色二つで充分だ!
(f0という項目が増えましたが)


あとがき

これでとりあえずシェーディングの説明は終わりです
3DCGが具体的にどのように描かれているかお分かりいただけたでしょうか?
まあ、多分まだまだ説明不足なので疑問も多いかもしれませんが
フォンの反射モデルの抱える問題点とその解決法を提示しましたが、実はこの点は一般的には知られていないというか問題点として認識されてないので、このページで世間に広がるとうれしいんだけど
(フォン博士はわざとこういう形にしたのかもしれないけど・・・?どうなのだろうね)

 環境光はライトであり、ライトによって起きる反射光は拡散反射光と鏡面反射光である

この2点を説明するためにこれまで長々と説明してきましたが
実は、環境光の鏡面反射光自体はすごい昔から期せずして実装されていたりするんですよね
環境マッピングとして
環境マッピングはいわばイメージベースドライティング(テクスチャを使って反射を計算する手法)の一つというわけですね
もっともまだイメージベースドライティングという概念のない時代の産物だと思いますが
環境マッピングを使わないチープな表現手法としてこのように環境光を計算するのもありだと思いますので解説しておきました
機会があれば環境マッピングもページを作ってみたい・・・

追記
後から考えてみたけど、もしかしたらフォンの反射モデルにおける環境光およびその反射係数は、拡散反射光と鏡面反射光を混ぜて同時に扱った形なのかもしれない
DirectXの説明だと反射係数は拡散反射光と通常は同じにすると書かれていたけど、最後に示した2匹のウサギのサンプルのように鏡面反射光としての成分を反射係数に混ぜ込んでおくとなじんでるし
おそらく、フレネルの項を扱うのが難しい低スペックな環境下では全体に一様に環境光を取り扱うためにあの形にしたのかも?


2013/11/16掲載 2013/11/21更新

FC2カウンター
inserted by FC2 system