前回の記事を書いた後で、「そういえばスペキュラ部分の説明を忘れていたな」と気が付いた
ので、今回は補足としてスペキュラの実装もメモしておきます。
といっても、今回はかなりザックリとした説明にしますが苦笑。
スペキュラ(鏡面反射)とは、そのなの通りライトの光が面で反射した光のことです。
なのでディフューズと違い、その光量は反射ベクトルの向きによって変化します。
まぁ、分かりにくい人は鏡かガラスを想像してみてください。鏡は全ての光を反射
しますが、やすりでこすってザラザラにすると曇ってにぶい反射になると思います。
物理等では主に前者を鏡面反射光と呼ぶと思いますが、3DCGではもっぱら後者の
方を指し、前者は環境マッピングと呼んでいますがここではこの後者の鏡面反射を説明
することにします(何だかややこしいな)。
スペキュラの実装方法についてもphongやblinn-phong等色々な方法がありますが
ここでは(面倒なので)それらについて説明せず、どう合成するのかだけにしたいと思います。
広く使われる一般的なスペキュラの合成方法は以下のような加算による方法です。
Color = saturate( Color + SpecularColor * SpecularAmount );
この方法で実装したのが画像の2枚目です。昔の3DCGはこんな感じでスペキュラが
ギラギラしたものが多かったような気がしますが、おそらくこの方法で実装しているため
だろうと思います。この方法は現実のスペキュラと異なる点があり、それは"加算で実装
するためライトの色より強くなる"問題が発生するということです。現実世界ではどんな角度
から眺めてもライトの色よりスペキュラ部分が明るくなることは絶対に無い(はず)です。
ここからは自分の考察と勝手な結論なのですが、これはエネルギー保存則によって
スペキュラとして発する光が大きくなるほどディフューズとして発する光が小さくなる
ためライトから受ける光以上の明るさにならないようになっているのだろうと思います
(例えば3Dで鏡を実装する場合、ディフューズは0に設定しますが、これはこういった
理由からなんだろうな)。
つまり、現実世界のスペキュラを再現するためには以下の式のようにスペキュラの分だけ
ディフューズを暗くすればよいということになります。
SpecularColor -= Color;
Color = saturate( Color + SpecularColor * SpecularAmount );
式ではディフューズではなくスペキュラ色を減衰させていますが、Colorとの合成が加算
処理なので結局は同じ意味となります。この処理で実装したのが画像の3枚目ですが、
2枚目では肌の部分のスペキュラが不自然な感じに見えますが、3枚目の方は自然な
感じで見えるようになりました。
ちなみにリムライティング色についても、SpecularAmountに上乗せする形で実装させる
ようにしました。これはリムライティングはスペキュラから派生した表現という考察の結果
からなのですがこれもいい具合に馴染んだのでした。後はスペキュラ色についてですが、
以下のように反射ベクトルR先のGI色とライト色を元にしています。
float3 LightingColor = saturate(
lerp( (half3)0, InLightColor, saturate(dot(-R,LightDir)) ) + GetPRTColor( R )
) - InOutColor;
全部でこんな感じだろうか。これで明部分・暗部分ともにある程度正確なライティングに
なりましたが。結局はこれでも疑似的な手法に過ぎないんだよなーと思ったりするのでした。
まぁリムライティング自体は非現実なライティングなものの、見た目がいいかなという
個人的な理由で実装させているので、ある程度の嘘はやっぱり必要なのかもしれない。