Soft-Edged Shadows
Step 4: Rendering the shadowed sceneTo project the blur map onto the scene, we render the scene as usual, but project the blur map using screen-space coordinates. We use the clip space position with some hard-coded math to generate the screen-space coordinates. The vertex and pixel shaders shown below render the scene with per-pixel lighting along with shadows. struct VSOUTPUT_SCENE { float4 vPosition : POSITION; float2 vTexCoord : TEXCOORD0; float4 vProjCoord : TEXCOORD1; float4 vScreenCoord : TEXCOORD2; float3 vNormal : TEXCOORD3; float3 vLightVec : TEXCOORD4; float3 vEyeVec : TEXCOORD5; }; // Scene vertex shader VSOUTPUT_SCENE VS_Scene( float4 inPosition : POSITION, float3 inNormal : NORMAL, float2 inTexCoord : TEXCOORD0 ) { VSOUTPUT_SCENE OUT = (VSOUTPUT_SCENE)0; // Output the transformed position OUT.vPosition = mul( inPosition, g_matWorldViewProj ); // Output the texture coordinates OUT.vTexCoord = inTexCoord; // Output the projective texture coordinates (we use this // to project the spot texture down onto the scene) OUT.vProjCoord = mul( inPosition, g_matTexture ); // Output the screen-space texture coordinates OUT.vScreenCoord.x = ( OUT.vPosition.x * 0.5 + OUT.vPosition.w * 0.5 ); OUT.vScreenCoord.y = ( OUT.vPosition.w * 0.5 - OUT.vPosition.y * 0.5 ); OUT.vScreenCoord.z = OUT.vPosition.w; OUT.vScreenCoord.w = OUT.vPosition.w; // Get the world space vertex position float4 vWorldPos = mul( inPosition, g_matWorld ); // Output the world space normal OUT.vNormal = mul( inNormal, g_matWorldIT ); // Move the light vector into tangent space OUT.vLightVec = g_vLightPos.xyz - vWorldPos.xyz; // Move the eye vector into tangent space OUT.vEyeVec = g_vEyePos.xyz - vWorldPos.xyz; return OUT; } We add an additional spot term by projecting down a spot texture from the light. This not only simulates a spot lighting effect, it also cuts out parts of the scene outside the shadow map. The spot map is projected down using standard projective texturing. float4 PS_Scene( VSOUTPUT_SCENE IN ) : COLOR0 { // Normalize the normal, light and eye vectors IN.vNormal = normalize( IN.vNormal ); IN.vLightVec = normalize( IN.vLightVec ); IN.vEyeVec = normalize( IN.vEyeVec ); // Sample the color and normal maps float4 vColor = tex2D( ColorSampler, IN.vTexCoord ); // Compute the ambient, diffuse and specular lighting terms float ambient = 0.0f; float diffuse = max( dot( IN.vNormal, IN.vLightVec ), 0 ); float specular = pow(max(dot( 2 * dot( IN.vNormal, IN.vLightVec ) * IN.vNormal - IN.vLightVec, IN.vEyeVec ), 0 ), 8 ); if( diffuse == 0 ) specular = 0; // Grab the shadow term float fShadowTerm = tex2Dproj( BlurVSampler, IN.vScreenCoord ); // Grab the spot term float fSpotTerm = tex2Dproj( SpotSampler, IN.vProjCoord ); // Compute the final color return (ambient * vColor) + (diffuse * vColor * g_vLightColor * fShadowTerm * fSpotTerm) + (specular * vColor * g_vLightColor.a * fShadowTerm * fSpotTerm); } That's it! We have soft edged shadows that look quite nice! The advantage of this technique is that it completely removes edge-aliasing artifacts that the shadow mapping technique suffers from. Another advantage is that one can generate soft shadows for multiple lights with a small memory overhead. When dealing with multiple lights, all you need is one shadow map per light, whereas the screen and blur buffers can be common to all the lights! Finally, this technique can be applied to both shadow maps and shadow volumes, so irrespective of the shadowing technique, you can generate soft-edged shadows with this method. One disadvantage is that this method is a wee bit fill-rate intensive due to the Gaussian filter. This can be minimized by using smaller blur buffers and slightly sacrificing the visual quality. Here's a comparison between the approach mentioned here, 3x3 percentage closer filtering and normal shadow mapping.
Thank you for reading my article. I hope you liked it. If you have any doubts, questions or comments, please feel free to mail me at anidex@yahoo.com. Here's the source code. References
|
|