TOP

ABOUT

PROGRAM

DIALY







Variance Shadow Maps

 1.はじめに
 分散シャドウマップやってみましたが…,あんましサンプルとしては良くないので参考程度に見てください。



 2.Variance Shadow Maps
 シャドウマップで問題になるのは,影のジャギーです。通常のシャドウマップを実装したことがあるかたならよくわかると思います。透視シャドウマップ系の技術では,シャドウマップの生成を工夫することによって,ジャギーを回避しようとしますが,今回やつ分散シャドウマップでは,影のジャギーをぼかしてソフトシャドウを表現するものです。
 シャドウマップの生成自体は通常のシャドウマップと変わりません。通常のシャドウマップは,影ができるかできないかということを0か1かで判断しますが,分散シャドウマップは影ができるかどうかを確率論でよく用いられるという「チェビシェフの不等式」(下式)を用いて判定するそうです。


要するに光が当たっているかどうかの最大確率を算出して,それに基づいて影の具合を決定するそうです。
光が当たっている確率が高い所であれば,普通にライティングした色になりますが,光が当たっている確率が低い所であれば,影で黒くなるはずです。
 わかりやすい説明が,西川の"3Dグラフィックス・マニアックス(29)影の生成(10)〜改良型デプスシャドウ技法(5)"に載っているので,そちらをご参照下さい。また,ちゃんとした論文みたい方は,こちらのページ[http://www.punkuser.net/vsm/]に元になる論文およびデモのソースプログラム・Fx Composerのサンプルが置いてあるので参考にしてみてください。
で,まぁ実際に気になるのは,「通常のシャドウマップと比べてどうなのさ?」というところだと思います。自分なりに実装してみたプログラムで実際に比較してみました。


↑通常のシャドウマップ


↑分散シャドウマップ

見てわかるように,影の境界部分は素晴らしく綺麗になってますね。かなり使える技術ではないでしょうか?
 実際のコードの部分ですが,通常のシャドウマップは下のような感じです。
00124:  //------------------------------------------------------------------------
00125:  // Name : USM_Filter()
00126:  // Desc : 通常のシャドウマップ
00127:  //------------------------------------------------------------------------
00128:  float USM_Filter( float2 texCoord, float fragDepth )
00129:  {
00130:  	float depth = tex2D( ShadowSmp, texCoord ).r;
00131:  	float lit = (float)1.0f;
00132:  
00133:  	if ( depth < fragDepth - Offset )
00134:  		lit = 0.5f;
00135:  
00136:  	return lit;
00137:  }

 それに対して,分散シャドウマップの方は,下のようになります。
00139:  //------------------------------------------------------------------------
00140:  // Name : VSM_Filter()
00141:  // Desc : 分散シャドウマップ
00142:  //------------------------------------------------------------------------
00143:  float VSM_Filter( float2 texcoord, float fragDepth )
00144:  {
00145:  	float4 depth = tex2D( ShadowSmp, texcoord );
00146:  
00147:  	float depth_sq = depth.x * depth.x;
00148:  	float variance = depth.y - depth_sq;
00149:  	float md = fragDepth - depth.x;
00150:  	float p = variance / (variance + (md * md));
00151:  
00152:  	return saturate(max( p, depth.x <= fragDepth ));	
00153:  }

次に描画部分のVertex ShaderとPixel Shaderですが下のコードを用意して描画してみたんですが…
00103:  //------------------------------------------------------------------------
00104:  // Name : VertexShaderFunction()
00105:  // Desc : 影付き描画用頂点シェーダ
00106:  //------------------------------------------------------------------------
00107:  VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
00108:  {
00109:      VertexShaderOutput output = (VertexShaderOutput)0;
00110:  
00111:  	output.TexCoord = input.TexCoord;
00112:  	float4 worldPosition = mul( input.Position, World );
00113:  	float4 lightViewPos = mul( worldPosition, ShadowView );
00114:  	float3 lightVec = LightPosition.xyz - worldPosition.xyz;
00115:  
00116:  	output.Position = mul( worldPosition, mul(View, Projection) );
00117:  	output.LightViewPos = mul( lightViewPos, ShadowProjection );
00118:  	output.Normal = normalize( mul( input.Normal, World ) );
00119:  	output.Light = normalize( lightVec );
00120:  	output.Depth = (length( LightPosition.xyz - worldPosition.xyz )/MaxDepth);
00121:      return output;
00122:  }

00155:  //------------------------------------------------------------------------
00156:  // Name : PixelShaderFunction()
00157:  // Desc : 影付き描画用ピクセルシェーダ
00158:  //------------------------------------------------------------------------
00159:  float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
00160:  {
00161:      // TODO: add your pixel shader code here.
00162:  	float3 output = ModelDiffuseColor * (max(dot(input.Normal, input.Light), 0.0f) * 0.5 + 0.5);
00163:  	input.LightViewPos.xy /= input.LightViewPos.w;
00164:  	float2 tex = input.LightViewPos.xy * float2( 0.5f, -0.5f ) + 0.5f;
00165:  
00166:  	float lit = 1.0f;
00167:  	if ( VSMFilterEnable )
00168:  	{
00169:  		lit = VSM_Filter( tex, input.Depth );
00170:  
00171:  		// 影の一番暗い色を0.5にするための処理
00172:  		lit *= 0.5;
00173:  		lit += 0.5;
00174:  	}
00175:  	else
00176:  		lit = USM_Filter( tex, input.Depth );
00177:  
00178:  	return float4(output, 1.0f) * lit;	
00179:  }

このシェーダを使って,描画してみたんですが下の画像のようになってしまいました。
法線ベクトルとかライトベクトルとか変な与え方してるんじゃないかと思い,通常描画とかしてみておかしな所がないか調べては見たんですが…。原因がよくわかりませんでした。(誰か原因わかる人いらっしゃいましたら,ご教授ください)



とりあえず,応急措置として以下のように床の部分の色を反転するように対策をしてみました。
<00181:  //------------------------------------------------------------------------
00182:  // Name : PixelShaderFunctionForGround()
00183:  // Desc : 影付き描画用ピクセルシェーダ
00184:  //------------------------------------------------------------------------
00185:  float4 PixelShaderFunctionForGround(VertexShaderOutput input) : COLOR0
00186:  {
00187:      // TODO: add your pixel shader code here.
00188:  	float3 output = ModelDiffuseColor * (max(dot(input.Normal, input.Light), 0.0f) * 0.5 + 0.5);
00189:  	input.LightViewPos.xy /= input.LightViewPos.w;
00190:  	float2 tex = input.LightViewPos.xy * float2( 0.5f, -0.5f ) + 0.5f;
00191:  
00192:  	float lit = 1.0f;
00193:  	if ( VSMFilterEnable )
00194:  	{
00195:  		lit = VSM_Filter( tex, input.Depth );
00196:  
00197:  		// 色の反転
00198:  		lit = 1.0 - lit;	
00199:  
00200:  		// 影の一番暗い色を0.5にするための処理
00201:  		lit *= 0.5;
00202:  		lit += 0.5;	
00203:  	}
00204:  	else
00205:  		lit = USM_Filter( tex, input.Depth );
00206:  
00207:  	
00208:  	return float4(output, 1.0f) * lit;	
00209:  }

あんまし,よろしくないですが…上記のコードを使用することで下のような画面が生成されます。



 Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。
プログラムの作成にはMicrosoft Visual Studio 2008 SP1 Professional, XNA Game Studio 3.1を用いています。