シャドウマッピングをためしてみました。
シャドウマッピング★ 1.はじめに…
シャドウマッピングをためしてみました。
★ 2.Shadow Mapping
シャドウマッピングは,その名の通りシャドウマップと呼ばれる下の画像のようなテクスチャを使用してシーンに影を付加して描画を行う手法です。
シャドウマップの長所としては物体の形によらず影がかけることです。短所としては,テクスチャを使用するためテクスチャの解像度が低いとジャギーがでてしまい影が汚くなってしまうという点です。 シャドウマッピングを行うためには前回やった射影テクスチャリングを使います。 まずは,シャドウマップの作成です。 上の図のようにまずはライトを視点とみなして,ライト方向から見た時の深度バッファ(Zバッファ)の値をシャドウマップに格納します。深度値は射影空間のz値を使います。よってまずは,ライトを視点とみなしてビュー行列を作成し,ライトの射影行列を作成してシーンを描画します。シーンを描画したらこれをキャプチャしてテクスチャに保存し,シャドウマップが完成です。 シャドウマップを作成する頂点シェーダのコードは次のようになります。 00046: VertexOutput CreateShadowVS( VertexInput input ) 00047: { 00048: VertexOutput output; 00049: 00050: // 座標変換 00051: float4 Position = mul( input.Position, mWLP ); 00052: 00053: // 位置 00054: output.Position = Position; 00055: 00056: // シャドウ作成用 00057: output.ShadowTexCoord = Position; 00058: 00059: return output; 00060: } ここで注意してほしいのは,頂点シェーダ側では深度算出を行っていないということです。 Imagireさんの本に書いてあるのですが,頂点シェーダ側でzをwで除算を行ってしまうと誤差が発生するそうです。 ピクセルシェーダ側に渡される色は線形補間された色になります。たとえば,z座標値とw座標が(z0, w0),(z1, w1)で,補間係数をtとすると, が正しい値ですが,頂点シェーダの方でwで割った値を補間すると, と異なる値になってしまいます。 Imagireさんの本を読んでいなかったら,全然気づいていなかったでしょうね。 そんなわけで,wで割る処理はピクセルシェーダの方で行います。 00096: float4 CreateShadowPS( VertexOutput input ) : COLOR 00097: { 00098: float4 output; 00099: 00100: // 深度値算出 00101: output = input.ShadowTexCoord.z / input.ShadowTexCoord.w; 00102: 00103: return output; 00104: } シャドウマップの作成ができたら,次は影を描画する処理です。 影の描画するやり方ですが,上の図を基に説明します。 まず,紫色の点を視点と仮定します。ある点を描画するときにライトまでの距離とシャドウマップに格納されている深度値を比較します。比較した結果,シャドウマップに格納された深度値よりもライトまでの距離が大きい場合は,影を描画します。小さいあるいは等しい場合は影ではない部分です。 たとえば,点P1の場合は,ライトまでの距離の方がシャドウマップに格納された深度値よりも大きいです。このような場合は影を描画します。 点P2の場合は,ライトまでの距離とシャドウマップに格納された深度が等しくなります。この場合は影は描画されません。 この処理をどうのようにするかということですが,深度値を射影テクスチャリングのようにオブジェクトにマッピングして比較を行えるようにします。距離の比較のやりかたはシャドウマップの作成と同じようにzの値をwで割って比較します。 頂点シェーダのコードは次のようになります。 00066: VertexOutput VertexShaderFunction( VertexInput input ) 00067: { 00068: VertexOutput output; 00069: 00070: // 座標変換 00071: output.Position = mul( input.Position, mWVP ); 00072: 00073: // ライトベクトル 00074: float3 L = normalize( vLightPosition - input.Position.xyz ); 00075: 00076: // 法線ベクトル 00077: float3 N = normalize( input.Normal ); 00078: 00079: // 色 00080: output.Diffuse = input.Color * max( dot( L, N ), 0 ); 00081: output.Ambient = input.Color * 0.3f; 00082: 00083: // シャドウマップ 00084: output.ShadowTexCoord = mul( input.Position, mWLPB ); 00085: 00086: // 深度 00087: output.Depth = mul( input.Position, mWLP ); 00088: 00089: return output; 00090: } ピクセルシェーダの方のコードは次のようになります。 00110: float4 PixelShaderFunction( VertexOutput input ) : COLOR 00111: { 00112: float4 output; 00113: 00114: // 深度値を取り出す 00115: float shadow = tex2Dproj( ShadowMapSmp, input.ShadowTexCoord ).x; 00116: 00117: // 深度値を比較して色を決定 00118: output = input.Ambient + ( ( shadow * input.Depth.w < input.Depth.z - 0.05f ) ? 0 : input.Diffuse ); 00119: 00120: return output; 00121: } 118行目で出てくる謎の数字0.05fですが,これを0.05fではなく0.0fにすると下のようになります。 0.05fという数値は上の画像のように縞模様が発生するのを防ぐための数値です。shadow * input.Depth.wの値をinput.Depth.zよりも少し大きくさせるというゲタをはかせる効果を持っている数値なのでバイアス値と呼ばれています。 深度値と同じ時に座標が完全に一致しないために判定ミスが起こるというのが,この縞模様が発生する原因だそうです。 バイアス値は目で見ておかしくないか手作業で決めました。 これでようやく,シャドウマッピングもできるようになりました。それと今回からプログラムをVisual Studio 2008で作るようにしましたので,注意してください。 ★ Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。 プログラムの作成にはMicrosoft Visual Studio 2008 Professional, NVIDIA Cg Toolkit 2.0 May 2008を用いています。 |