ポイントライト

今回はポイントライト(点光源)のお話
シェーディングの解説では平行光源を使ってライティングしました
でも3DCGでは平行光源、環境光のほかにも点光源やスポットライトなんかがあります
(グローバルイルミネーションでは面光源・・・つまりポリゴンをライトにすることも可能です)
今回は点光源のシェーディングがどのようなものか解説します

平行光源は無限遠にある光源と仮定して計算しますが、対してポイントライトでは有限な距離に置かれている光源である事になります
なんていうと難しい言い方になりますが、よーするにライトベクトルの取り扱いが少し変わるってだけの話です

拡散反射光や鏡面反射光の計算式自体は平行光源とさほど変わらず、ランバートの余弦則とBlinn-Phongモデルの鏡面反射がそのまま使えます
ただし、平行光源では全てのライトベクトルが平行な向きで長さが1として扱われますが、ポイントライトではライトベクトルの向きが頂点ごとに異なり、ベクトルの長さ(つまり頂点から光源までの距離)によって明るさが変わります

ポイントライトはどんなところで使うのか?
たとえばロウソクや蛍光灯といった部屋の明かりや街灯、蛍みたいな発光する虫だとかに使ってみても良いかもしれません
そして応用方法として環境光として配置する、なんて使い方もあります
使いどころの多い便利なライトなんですね

ところがこのライト、大抵の3DCGの中でもかなりの嘘っぱち野郎なのです
大抵の場合、計算式は物理的な法則を無視します
なぜなのかは後ほど

ライトベクトルの向き

平行光源では無限遠の距離に光源があるために、頂点と光源を結んだベクトル(つまりライトベクトル)の向きは全て同じ向きとなってしまいますが
ポイントライトは有限な範囲に光源があるので、頂点の位置によって光源との位置関係が変わるため、ライトベクトルの向きに差が生じます
つまり↓の画像のような感じです

(逆に言うと、平行光源は無限遠に置かれたポイントライトです)

とりあえず、距離による減衰を無視してポイントライトを実装すると次のようになります
サンプルページ

めっちゃくちゃ明るいですね
多分これを見てリアルな映像だなって思う人は居ないはず・・・多分
やはり距離減衰を入れなくてはポイントライトらしくないですね
計算式が気になる方はサンプルページでソースを表示して確認してください
今回は計算も簡単なのでソースの解説はしないで行こうと思いますので

ちなみに、今回のサンプルはグーローシェーディングではなくフォンシェーディングになっています
どういうことか素人さんに簡単に説明すると、グーローシェーディングは頂点毎の反射光だけを計算して面の色は頂点の色で補間します
フォンシェーディングは頂点だけじゃなく表面全部で反射光計算をします
フォンの反射モデルとは別の言葉です 紛らわしいですが
同じ人が考えたので同じ名がついてますが、フォンの反射モデルは反射光の計算モデルのことで、フォンシェーディングは補間法のことです (グーロー補間とかフォン補間って呼べば良いのにね)
さらに、今回使われているお部屋(四角い箱)、俗にコーネルボックスといいます
厳密にはコーネルボックスっぽいただの箱ですが
アメリカのコーネル大学の研究室でCGの実験のために使われた極単純な形の3Dモデルで、光の反射による相互作用の実験に使われるものです
現実での再現が簡単なのでレンダリング結果と現実の撮影結果の比較なんかをしたりしてます
コーネルボックスで画像検索すると出てくる画像は大抵グローバルイルミネーションの実験なのですっごくリアルですよ
ウチでやるのはウソの塊たるリアルタイムレンダリングなのであんまり意味ないんですけどね

距離減衰

ポイントライトのもう一つの重要な要素、それは距離減衰です
ポイントライトから遠ざかるほどに明るさが弱くなるんですね
これが起こる理由はちょっとだけ難しい話になりますが逆2乗の法則というものが出てきます

逆2乗の法則とは

ポイントライトは全方位に向かって光を出します
なので光源に近い物体ほど多くの光が当たり、遠くに行くほど当たる光が少なくなります

では実際に距離によってどれぐらい光が減っていくのかを考えて見ましょう
まず光源中心点が最も明るい光エネルギーを持っています
そこから全方位に均等に光を放つので、中心からある一定の距離はなれた地点の光を全て集めると中心の光エネルギーと一致します
一定の距離はなれた地点というのを結ぶと、球面になります
つまり、光エネルギーは球面状に拡散するわけですね
球面の光の総和が中心のエネルギーと一致するので、距離が離れるほど球面の表面積が大きくなり、その分球面上のある一点の明るさは減るわけです
そして距離によってその表面積がどのように変化するのかというと、球の表面積Sは半径rに対してS=4πr^2で表されます
球の半径=中心からの距離であるので、面積は距離の2乗で増えていくわけですね
逆に光の強さは面積の増加によって弱まっていくので、距離の2乗に反比例していきます
距離の2乗に反比例するので、逆2乗の法則といいます

ただし、この距離減衰を考慮すべきなのは拡散反射光のみで良いようです
説明が難しくなるのでちょっと上手く説明できるか分からないんですが、理解できそうにない人は読み飛ばしてください
詳しい人は間違ってたら指摘してくれると嬉しいです
これはパストレーシングなんかで考えると分かりそうだけど、パストレだと光の側からではなく視点側からレイを飛ばし、面とぶつかってレイが反射した方向に光源があるかどうかで明るさを計算します
拡散反射光の場合乱反射するのであらゆる方向へレイが飛んでいきますが、光源との距離が離れるほど光源に向かう確率が減ります
逆説的ですが、そのせいで距離減衰が起きるわけです
対して鏡面反射光は、微細凹凸によってブレが生じますが、基本的に鏡面反射方向です
微細凹凸がない場合、鏡面反射の光の強さは距離に依らず、材質設定の鏡面反射する確率に依って常に一定です
ただし、理想的な鏡面反射(表面がツルツル)によって照らされている場合には表面上の1点のみでしか光源は映りませんが、微細凹凸によって反射方向にブレが生じるのでハイライトが拡散して見えます
つまり鏡面反射の拡散は距離によって変わるのではなく、視線と光線の角度によって決まるわけです
その反射方向のブレを表したのがBlinn-Phongモデルの近似計算であるので既に鏡面反射光は問題なく計算できているわけです
・・・多分

では、拡散反射光の強さを距離の2乗に反比例させれば距離減衰するんだね
じゃあ光の減衰をAとして、光源からの距離をDとすれば次の式でいいわけだ

 A=1.0/D^2 ・・・式1

このAを拡散反射光に掛けると光の減衰を表せるわけです
これで早速描画してみよう!
サンプルページ

うーん・・・それっぽいけどなんとなくどこか変だ
それもそのはず
これ距離1.0以内の場合、光が増幅されてるもん
D=0.0の場合無限大だし・・・

というわけで、逆2乗の法則とは言うけどそのまま2乗に反比例させちゃダメ
明るさを増幅させないためには距離0の時最大値たる1.0にしなくちゃいけない
というわけで次の式で実装すれば光源の明るさを変えずに逆2乗になります

 A=1.0/(1.0+D)^2 ・・・式2

これで描画した結果がこちら
サンプルページ

うーん、暗いね
でもこれが物理現象を(なるべく)正しくシミュレートした場合の描画結果です
ライトの明るさがR:1.0,G:1.0,B:1.0の白色で最大値になってます
つまりこれ以上明るくできません
(というか、これ以上明るくすると反射光がRGBの有効範囲を超えてしまい正しく表示できなくなります)

ちなみに、この計算式の場合論理的な距離1.0が現実の1㍍に相当します
箱は2メートルの立方体、ウサギちゃんは高さ1メートルです(デカい
ライトが白色の場合明るさは・・・何ルーメンになるんでしょう?分かりません・・・

さて、こんな明るさじゃあ使い物になりません
でも明るさをこれ以上上げるわけにも行きません
じゃあどうしよう?ってなるわけですね

そこで、距離減衰がない場合を思い出してみるとすごく明るかったですね
で、距離減衰を入れたら暗すぎる
ならば、適度な減衰を入れればいいんじゃない?って話になったわけです

線形な減衰

とりあえず反比例な減衰では減衰しすぎるので、比例をつかって直線的な減衰を起こしちゃおう
というわけで、減衰係数Rという数値を設けて次の式で表します

 A=1.0-D/R ・・・式3
 (ただし0.0<A)

このRという係数は減衰の傾きを表していて、A=0のときD=Rになります
つまり光が届く距離を示すことになります
ということは光源との距離がRより離れている頂点は拡散反射光の計算をスキップできますね
光が届く最大距離を設定できるのは計算機的には有意義なのです

とりあえず今回はライトも一つで照らす範囲も広くないので拡散光計算のオン・オフは設定せずに実装しました
こんな形になります
サンプルページ

なかなか悪くない形ではあると思います
部屋全体が照らされるのであればさほど違和感はありません
ただ、照らす範囲が狭いと境界付近がくっきりしすぎてたり、なんとなく明るさの変化が単調なのが露呈します
線形な減衰では計算負荷が軽く照らす範囲も自由な変わりに、やはり少しだけ不自然です
曲線的な減衰を起こす方がより逆2乗の法則に近くて良いかもしれませんね

一般的な減衰計算式

線形な減衰ではやはり少し違和感が出ます
OpenGLやDirectXでも線形減衰はありますが、それだけでは不自然なので曲線的な減衰もサポートします
逆2乗に近いカーブほど自然な光を表現できますが、逆2乗では光が弱い
ならば直線と逆2乗の中間的なカーブを作ってみよう、といった形の式ですね
減衰係数としてa,b,cという値を設けて次のように表します

 A=1.0/( a + b*D + c*D^2) ・・・式4

aを小さくするとライトの最大の明るさが増え、bやcが大きくなるほど光が遠くへ届きにくくなる
(ただし、極わずかな光が無限遠まで届く)
使い方の感覚としてはaで全体の明るさの調整、bで光源近くの減衰を調整、cで遠方の減衰を微調整、といった感じな気がします
b,c共に反比例になるので減衰は結構急激です

これを用いてレンダリングした結果がこちら
サンプルページ


これがリアルタイムレンダリングで最も一般的な計算式です
DirectXでは更に計算の打ち切り範囲を設定しているそうですが、DirectXについて詳しくないのでどのような形か分かりません
単純にぶつ切りで一定範囲以上を計算しないという形だとすると、Aが充分に小さい値となるところで打ち切らないと違和感が出そうですね

さて、この計算式ですがあまり直感的ではないですよね
aが1.0未満だと光が増幅してしまうし、aを大きくするってことは明かりが暗くなるだけなのでライトの色を暗くすればOKです
bやcによる減衰の効果は実際に適用しながら設定してみないと効果が分かりにくいです
反比例の式だから0になることがなく無限遠に届くので、厳密には計算を打ち切ることができない
(ただ反比例ゆえ急激に値が落ち込むので、ある程度離れていれば打ち切っても誤差は少ないかもしれない)
更に言えば、個人的な意見ですがbとcの設定による効果はどちらも反比例なので、どちらか一方だけ使っても効果はあまり変わらないように思います

独自の減衰計算

というわけで、もっと使いやすくて効果的な方法はないのかとちょっと試しました
正直なところ、どうせ逆2乗の法則の計算以外は全部偽者なのでお好みでかまわないんですが
自分は次の計算で減衰させてみます

 A=(1.0-D/R)/(1.0+D*a^2) ・・・式5

Rは光が届く最大範囲、aは減衰曲線をどれぐらい曲げるか
という感じです
aが2乗になってるのは単純にaを掛けるとa<1.0の時の変化が急だったために使いやすくするため2乗にしました
(要するに別にaは2乗にしなくても良いです 実装でそのようにしたのであえてここでもこのように書いただけで)
大体aは0.0から2.0の範囲で充分だと思います
a=0.0で線形な減衰になります
距離の2乗に反比例させずとも、距離に反比例する時の強さだけ変えていけば似たような変化をしますので省きました
分子側が線形の式になっているため最大距離がRに制限できます
ただし、Rが小さいときはあんまり曲線的な変化を示せません
明るさが足りない時はなるべくRを大きくとると良いようです
ちょっとこの辺に癖があるかも

というわけで実際に使った結果がこちら
サンプルページ

自分としてはまあまあかなーと思える出来
弱冠不自然な気もしますがRを大きくとってaを大きく設定すれば大体逆2乗の効果とも一致します
処理も軽いしバランスは良いかと

独自の減衰計算 その2

先に書いた計算式では距離減衰を無くすには光の届く範囲を無限遠にしないといけませんでした
これではちょっと扱いにくいのでこんな式も作ってみました

A=(1.0-D/R)^F ・・・式6

線形の距離減衰(式3)は0~1の範囲にあります
これをべき乗すれば曲線的な変化になりますのでFを大きくするほど急激に減衰が掛かり
逆に小さいほど減衰が弱く、F=0の時減衰が起こらなくなります

ただし、照らされる範囲は半径Rに制限されるのでR以上離れた点は急に暗くなります
全体を減衰なしで照らしたい場合はRを全体を包む距離にするか、減衰なしのポイントライトを用意します

また、式5ではRが小さいときにあまり曲線的な変化を付けられませんでしたが、式6ではその問題点も改善されています
逆にRが大きい時に逆2乗に近づけるのは苦手な感じですね
Rが大きいとFを相当に大きくとらないといけなくなるかも

というわけでサンプルページ


あとがき

さて、ポイントライトの巧みなウソはご理解いただけたでしょうか?
リアルタイムレンダリングではポイントライトの明るさを変更できないため、距離減衰を減らして誤魔化します
飽くまで距離減衰が変えられているだけなので現実のポイントライトとは振る舞いが違います
ですから写真や実写映像作品のような現実のライティングとはちょっと勝手が異なります

次回は今回作ったコーネルボックス、ポイントライトを使って擬似的なグローバルイルミネーションをしてみます
現実と仮想現実の違いってヤツを見せてやるぜ
・・・上手く出来るかな

ところでライトが壁際に来るとBlinn-Phong鏡面反射では少し奇妙ですね
光の反射方向と視線ベクトルの内積をとる方法もイマイチでした
軽くてもっと綺麗なやつ考えてみるのも一興かもしれません


2013/11/24掲載 2013/12/08更新

FC2カウンター
inserted by FC2 system