表面下散乱シェーダ

最近WebGLを勉強中ですが、結構面白いものができたので紹介しようと思います
まだWebGLにそこまで詳しいわけではないのですが

今回紹介するのはSub Surface Scattering(表面下散乱)シェーダです
とりあえずサンプルページ
WebGLなのでそれに対応したブラウザで見てください
(2013年11月5日現在だと主にGoogleChromeやFirefox辺りがいいでしょう)

こんな感じの画面になります


サンプルにはWebGL開発支援サイト wgld.orgさんの行列ライブラリminMatrix.jsを使用させてもらってます


表面下散乱について

さて、表面下散乱といわれてもピンと来ない方も居るかもしれません
言葉で説明すると、半透明な物体に光が入り込んだ時に内部に入った光が乱反射しながら進んで行き、やがてまた表面へと飛び出していく反射光、と言ったところでしょうか
分かりにくいですね
要は、手のひらを太陽にかざしてみれば俺のこぶしが真っ赤に燃える
ってヤツですね

これを表現すると何がいい事あるの?というと、半透明な質感を表現できることになるのです
ここでいう半透明とは、光は通るのだけど内部の不純物や気泡だとかで拡散し、奥が透けて見えることの無い質感、ということになります
ヒトの皮膚は半透明で太陽のような強烈な光じゃなくても実は赤い光が特に浸透しやすく、3DCGの一般的な照明表現では陰影がはっきりしすぎて硬い印象になってしまいます
そこで表面下散乱を考慮した陰影をつけると光が透けるようなやわらかい印象になるわけです
皮膚以外にも翡翠や大理石のような質感を表現したり、或いはすごく濁った水とかにも使えるかもしれません
半透明の質感には何でも使えるので応用範囲はとても広いはずです

しかし、従来の方法だと大抵の場合複雑な計算の結果表現されるもので、皮膚専用のシェーダ(いわゆるスキンシェーダ)なんかを作っていたりして他の質感には使えません
有名なグラボメーカーNVIDIAのスキンシェーダのサンプルでは、拡散光を一度テクスチャに焼きこみ、R,G,B別々にブラーを掛け、しかもそのブラーにテクスチャの歪み補正を掛けていて
さらにさらにブラーによってテクスチャの境界に滲みの影響が出ないように工夫もしなければいけない
などといったとっても大変な実装になっていました
計算コストは言わずもがなかなり高いです
まあその分見栄えはいいですね(その点で言うと自分のサンプルじゃかなわないだろうけど・・・)
TSDと呼ばれる手法だそうです
多分こちらで公開されているWebGLサンプルはTSDなんじゃないかな?
http://alteredqualia.com/three/examples/webgl_materials_skin.html

他にもゲームなどでリアルタイムに適用したい低負荷な実装方法として最も有名なのはハーフランバート法でしょうね
ハーフランバート法とは一般的な陰影表現であるランバート反射の計算結果を半分にしてしまうことで裏側にまで光を当ててしまおう
というかなりいい加減なことをしています
いい加減ですが、結果として光がやわらかな印象となりあたかも透けているような印象を与えます
3DCGは嘘とごまかしの世界なのでこれもまた一つの方法としてアリでしょうね

より詳しく知りたい人は以下の記事が参考になると思います
http://news.mynavi.jp/column/graphics/057/index.html


現状での問題点

さて、上にも書いたようにこれまでにも表面下散乱を3DCGに取り入れようという試みはなされていて手法も色々あるわけですが、とりあえず現状の問題点を考えて見ましょう

まずTSDですが、やっぱり重いですよね
かなり計算コストが高いのでゲームで使うとなるとよほどのグラボ使っても他の表現犠牲にしないといけないかも
将来的にはハードが高速化したとしてもやはり軽い方法があるならそちらを使って、リソースを別に割きたいですよね
それからブラー半径を変動すれば別の質感にも使えるかもしれませんが、おそらく今のところスキン専用の設計でしょうね

じゃあハーフランバートならお手軽でいいんじゃない?と思うかもしれませんが、こちらは先ほども書いたとおりかなり計算はいい加減です
物体が反射する光エネルギーは入射したエネルギーを超えてしまい若干発光気味になりますし、要らん所まで光がつくし、とちょっとウソ臭くなってしまいます
さらに言えば手のひらを太陽にかざしても真っ赤にはなりません
なぜならR,G,Bともに同じ計算で同じ浸透の仕方をするからです

じゃあ軽くて見栄えもいいものは無いの?ってなるわけですね
広い世の中あることはあるのかもしれませんが自分の知る限りではコレといった定番手法は知りません
ないなら作ればいいじゃない
って事で作りましたのが今回ご紹介いたしますSSSシェーダにございます


表面下散乱シェーダ概要

前置きが長くなりましたがそれではこのシェーダについて説明しましょう
どうやって表面下散乱を表現するのか
表面下散乱は現象としては複雑ですが、その光の属性は拡散反射光です
つまり拡散反射光計算を工夫することで擬似的ながら表現できるはずなのです
この発想はハーフランバートと似ていますね

そして、TSDのスキンシェーダの記事を見ているときに気になる用語が出てきました
RDP(Reflectance Diffusion Profile)反射率拡散プロファイルです
これはどういったものかというと、ある一点にレーザーのようにピンポイントで光を当てた時、その点から離れた地点ではどのように光を反射するか
ということをあらわした反射率の距離関数のようなものですかね
要は光の浸透のしやすさを表す指標なわけです
これによって皮膚がどのように光が浸透し、反射されるのかを測定したそうです

つまり半透明な材質では光の浸透が強いほどに遠くまで散乱して反射されるわけですね
じゃあ、拡散光の計算をR,G,Bの成分ごとにRDPを使って引き伸ばせばそれらしくなるのでは?
ってわけです
テクスチャベースでは無く反射光計算で済ますのでシンプルで高速です
式もそこまで複雑ではないです
しかも、スキン専用ではないので他の材質にも応用可能と柔軟性も高い


ランバート反射について

ではまず基本的な拡散光計算手法であるランバート反射について説明しよう
ランバート反射とは物体表面で光が全て乱反射した場合(つまり鏡面反射が0)物体表面のある点における反射光の強さDは
物体表面の法線ベクトルをNとし、光源から放たれる光の向きをLとするとNとLの内積であらわされる
(ここでN、Lは単位ベクトル)

 D=N・L ・・・(式1)

となるわけですね
まあこれだけだと負の値が出てしまうので負の値は切り捨てて0とします
このDを拡散反射の係数として使うわけです
要は光の向きと面の向きが平行だと一番強く反射して、直交すると反射しないということです

リアルタイム3Dではこれに鏡面反射を付け加えて反射光としてしまえ!って強引なことをするので光のエネルギーが増えちゃうんだけど
まあそれは置いておいて先に進みましょう


陰影の引き伸ばし

ではこのランバートの計算を引き伸ばすとはどういうことか
そんなに難しいことではないです
光を裏面にまで回りこませたいので負の値の部分を底上げして、正の値になるようにします
上げ底をxとして

 D=N・L+x ・・・(式2)

といいたいところですがこれだと最大値が1.0をこえて光が増えますので

 D=(N・L+x)/(1.0+x) ・・・(式3)

としておきましょう
式3のxを変化させることで拡散反射光が引き伸ばされて見えるわけです
(xを負の値にすると逆に拡散反射の範囲が狭まるわけですが 使い道はないかと… 面白い見た目にはなるかも?)

これであとはRGB毎のRDPをxに入れれば完成!
ではありません…残念ながら
RDPは距離の関数ですし、これ角度の関数だし…
それに他にも問題点があるのです


ランバート計算の引き伸ばしの問題点

ここでちょっとハーフランバートの話をしましょう
ハーフランバートはこれまでの説明で言うならば次のような式で表せます

 D=( (N・L+1.0)/2.0 )^2 ・・・(式4)

この形は…
式3にx=1.0を代入して2乗にした式ですね
2乗しているのはおそらく光の変化がなだらか過ぎるためでしょう
式の形がほぼ同じですので、もちろん性質も似ています
ちなみにx=1.0ってことはハーフランバートは光が真後ろまで浸透した状態といえるわけです

ハーフランバート…
アヤツにはまずい問題点があるぞ…
ハーフランバートの物体に当たった光は増幅してしまう
つまり先ほど自分が導き出した式も・・・
増えます
うn

ちょっとここで光が増えるってどういうこと?ってのをグラフにしますと
光と面のなす角度と反射光の強さは次のようになります

横が成す角の大きさで縦が反射の強さとします
ちょっといい加減なグラフですが…
青い線と赤い線の間の面積分だけ光が増幅してるんですよね
結構大きい
ハーフランバートって反射光の特性もこんな感じで変わっちゃうんですね
ランバートが釣鐘型なのにハーフランバートは波型で
現実の表面下散乱はどんな反射の特性になるのかはあんまり分からないけど

ハーフランバートと自分の作った式は形が似ていましたね
つまり自分の式にも同じ問題点が含まれているのです
引き伸ばす係数xを変えていった場合次のようになります

引き伸ばすほどに増幅が強くなります

これがハーフランバートが物理的に正しくないと言われる所以ですね
では物理的に正しい状態ってどういうことか?
物理現象としては 物体に入り込んだ光=反射して出て行く光 な状態となるわけですが
あんまり正確なこと言うと、内部での浸透率の違いや熱に変換されてのロスなどがあるので=じゃないんですけど
材質が均質な場合入り込んだ光とほぼイコールであると
これを計算式に正直に入れるとなると大変で、自分でもどうしていいか分かりません
多分エネルギー計算に詳しい人ならなにか思いつくかもしれないんだけど
残念ながら自分アホなもんで…どうしていいか皆目見当がつきません


実装

まあ3DCGはウソついて何ぼなのです
要は表の面は光が浸透して反射する分が減り、裏側は浸透してきた光の分だけ発光するわけです
ので、ちょっとエネルギーは保存されないけど式を変形して次のようにしました

 D=(N・L+x)/(1.0+2.0*x) ・・・(式5)

分母のxに2.0を掛けてしまいます
こうすることで全体の光エネルギーを減らしちゃうわけです
本当はxの増加に対してどれだけ光が増えていくかの関数を打ち消すような式がいいんだろうけど
それやると式が複雑になるし、そもそもランバートを引き伸ばした形だと物理的な反射特性としては正しくないので
労力と計算負荷の割りに物理的な説得力は引き出せないのでこれでいいかと

式5のグラフは次のようになります

よく見るとランバートと比較しても増えた光と減った光がイコールではない感じですが
まあそれなりにバランスは取れてるように思います
これで充分じゃないでしょうか?


ちなみに式5に入れるxですが1.0を超える値でも面白い見た目になります
xが1.0を超える…とはつまりどういうことなの?というとですね
x=1.0のとき光の及ぶ範囲はちょうどNとLのなす角度が180度、つまり光が当たっている面のちょうど真裏まで届いている状態です
これを超えるということは、入射した光が物体を突き抜けてしまっている状態ですね 多分
突き抜けてるってことは裏にあるものが見えてないといけないのかもしれない
x>1.0の時にはアルファブレンドするべきなのかも?
ちなみに式5のxに大きな数字を入れると、無限大になった場合0.5に収束しますので
あんまり大きい数字を入れると全体が0.5ののっぺりしたものになりますし、結構光のエネルギーも増えているはずです
極端に大きな数字を与えなければなかなか良い見た目を得られるはずです


表面下散乱の係数設定について

さて、ここまでは計算式の意味を解説してきましたが
では式5に与える係数xとはどんなものであるのか
まあ途中でも触れましたがRDPのようなものをここに与えることで材質を表現できるわけですね
ですがRDPは距離に関するもので角度に関するものではありません
この違いを何とか誤魔化さないと場合によっては良好な結果にならないでしょう

ではどのように係数xを与えればいいかなのですが
RDPのRGB各成分の最大到達距離の比率を、例えば皮膚の場合比率R:G:Bは1.0:0.4:0.23としました
(といってもRDPのデータなど自分は持ち合わせていないのでなんとなくな値なんですけどね…)
この比率を基本として、物体表面の曲率やら物体の大きさなどを考慮して、どれぐらいの大きさで浸透させるか
というスケール値を持たせます
つまり、マテリアルとしてR,G,Bの比率と各頂点に対してスケールを設定し調整する必要があります
結構この辺は面倒くさい作業となってしまいますね・・・
まあ多分そこまでしなくてもマテリアル設定にスケールを持たせて一定に扱ってもそれなりな見た目になると思いますが
できれば頂点属性として持たせたほうがリアルになるでしょう
データ増えちゃうけど

サンプルではxの数値はスケールまで含めてマテリアル属性とし、全体に同じ値を与えてます
あんまり複雑な形状じゃないので荒は見えないかと
モデルデータの読み込み機能までつけばもうちょっと色々試せるんだけど
まだそこまで手が回らない…
誰かこれを別のソフトでもいいからやってみて!


問題点・課題

この方法だと拡散光の計算時に光の散乱を計算するので、シャドウ表現の影響を計算できません
陰影(シェード)についてのみ散乱が考慮されるわけです
シャドウについて影響を計算するにはシャドウの実装側でどうにかするしかないでしょう
ちょっと面倒ですね・・・
それから、飽くまで角度依存の表現手法なので角ばった形状のものはあまり向いてないです
一応法線ベクトルを工夫しておけば(スムージング角を設定せず平均化してしまうなど)光の透過は表現できるかも知れないけど
それだとスペキュラがおかしくなるので困りますね

2013/11/05掲載 最終更新2013/11/06

FC2カウンター
inserted by FC2 system