順番的にはおかしいですが,やっておかないと気持ち悪いのでLambertシェーディングをやります。
ライティング(3) Lambert★ 1.はじめに…
順番的にはおかしいですが,やっておかないと気持ち悪いのでLambertシェーディングをやります。
★ 2.Lambert Shading
ランバート照明は視線方向に依存しない光源の入射方向と面の法線だけで算出される陰影処理技法です。
この技法ではランバートの余弦則(Lambert's cosine law)が定義されています。この法則が意味しているのは「物体の表面で反射する光の輝度は,光の入射ベクトルLと表面に対する法線ベクトルNとが成す角度の余弦に比例する」ということです。 ランバートの余弦則を数式で表すと次のようになります。 ただし,Idは光の強さ,θは入射角,kdは物体の反射係数,Nは法線ベクトル,Lは物体から光源へと向かうライトベクトルを示します。 また,cosθの値が負の場合(法線がライトと反対の方向を向いてしまっている場合)には値は0にします。 ランバートの余弦則にのっとった入射した光の強さが見る色に比例するような光を拡散反射光(Diffuse reflection light)と言うそうです。 これだけだと光の反対側がまったく見えないので環境光(Ambient light)を導入してカバーします。 光源から出た光は物体面で反射を繰り返し,その光がまた物体面を照らす。そのため,直接光が当たらない部分でも物を見ることができる。このような光を近似したものが環境光です。 環境光によって照らされたときの物体面の反射光の強さは次式によって求められます。 ここで,Iaは光の強さ,kaは物体の反射係数を示します。 ランバート照明モデルはランバートの余弦則による拡散反射光と環境光によるライティングです。 光の強さIa, Id,反射係数のka, kdは光の出所が異なるので異なる値を使いますが,別に同じ値をつかっちゃだめってわけでもありません。IaはIdよりもちょっと弱く設定して,ka, kdは同じ値を使ってしまう場合もあります。 では,ピクセルシェーダーでの処理です。 上で述べてきたことをただ単にコードに落とすだけなので問題ないでしょう。 00054 : float4 LambertPS(VertexOutput input) : COLOR 00055 : { 00056 : float3 P = input.objPosition.xyz; 00057 : float3 N = normalize(input.normal); 00058 : 00059 : // Ambient Color 00060 : float3 ambient = gAmbient * Ka; 00061 : 00062 : // Diffuse Color 00063 : float3 L = normalize(lightPosition - P); // ローカル座標系のでのライトベクトル 00064 : float diffuseLight = max(dot(L, N), 0); 00065 : float3 diffuse = Kd * lightColor * diffuseLight; 00066 : 00067 : return float4(ambient + diffuse, 1.0f); 00068 : } ★ 3.Half Lambert Lighting (2008/03/15追記)
西川さんの記事に書いてあったハーフ・ランバートライティングが気になったのでやってみました。
上の画像の左側が普通のランバートで,右側がハーフ・ランバートになります。 たしかによくみると,光が当たってないからといって,現実世界では左側のように,そこまで黒くなることはあまりないので,右側の画像のほうが「リアルだろ?」と言われればそんな感じもしてきます。 西川さんの記事によるとハーフ・ランバートライティングはValveが'98年の「Half-Life」から使用しているValve独自の疑似ラジオシティラインティング技法であるそうです。あくまでも疑似的な手法なので物理的には全く正しくないやり方とのこと。 普通のランバートは上で述べたように余弦をそのまま適用するので,明暗がかなり強烈にでてしまいます。そこでValveでは「急激じゃなければいいんじゃね?」と考えたらしく,急激に変化するコサインカーブに細工をして緩やかにして,さらに暗部階調を持ち上げる工夫をしたそうです。具体的には,コサインカーブの値に1/2をかけ,さらに1/2を足して持ち上げ,さらにこれを2乗しているそうです。 そんなに難しくなさそうなので,コードに直せそうなので直してみます。 00051: // 00052: // Pixel Shader 00053: // 00054: float4 HalfLambertPS(VertexOutput input) : COLOR 00055: { 00056: float3 P = input.objPosition.xyz; 00057: float3 N = normalize(input.normal); 00058: 00059: // Ambient Color 00060: float3 ambient = gAmbient * Ka; 00061: 00062: // Diffuse Color 00063: float3 L = normalize(lightPosition - P); // ローカル座標系のでのライトベクトル 00064: float diffuseLight = max(dot(L, N), 0) * 0.5 + 0.5; 00065: float3 diffuse = Kd * lightColor * (diffuseLight * diffuseLight); 00066: 00067: return float4(ambient + diffuse, 1.0f); 00068: }コサインのところは内積に置き換えているので,max(dot(L, N), 0)に0.5をかけて半分にして,0.5かけて計算した結果に0.5を足して階調を持ち上げます。つぎにこの計算結果を2乗すればいいので,diffuseLightを2回掛けるように変更すれば,記事に書いてあるのと同じことになります。 正しいか正しくないかは別問題として,明暗が強烈にでるのを避ける手法としては実装もそんなに難しくないですし,それなりにいいんじゃないかと思います。 ★ Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。 プログラムの作成にはMicrosoft Visual Studio 2005 SP1 Professional, Cg Toolkit 2.0 Decemberを用いています。 |