3) 定义并生成Shadow Map纹理
1 texture2D Lamp0ShadowMapColor : RENDERCOLORTARGET 2 < 3 float2 ViewPortRatio = {1.0,1.0}; 4 int MipLevels = 1; 5 string Format = "A8R8G8B8" ; 6 >; 7 8 sampler2D Lamp0ShadowMapSampler = sampler_state { 9 Texture = <Lamp0ShadowMapColor>; 10 FILTER = MIN_MAG_MIP_LINEAR; 11 AddressU = Clamp; 12 AddressV = Clamp; 13 };
第3行的作用是使生成的Shadaow Map纹理大小与渲染窗口自动保持一致,这样可以很方便地观察到Shadow Map纹理大小改变时,对最终生成的阴影效果的影响。
1 float4x4 matWorld : World; 2 float4x4 matView : View; 3 float4x4 matProject : Projection; 4 5 struct SourceData 6 { 7 float3 pos3 : POSITION; 8 float4 n : NORMAL; 9 }; 10 11 struct VertexOutput 12 { 13 float4 pos4 : POSITION; 14 15 float4 rpos4 : TEXCOORD3; 16 float4 n : NORMAL; 17 18 float4 lpos4 : TEXCOORD2; 19 float4 ldirt4 : TEXCOORD6; 20 float4 uvd : TEXCOORD5; 21 }; 22 23 static float4x4 matLightView = LightViewMat(Lamp0Point, Lamp0LookAt); 24 static float4x4 matLightProj = LightProjcetMat(); 25 26 VertexOutput makeShadowVS(SourceData vData) 27 { 28 VertexOutput vOut = (VertexOutput)0; 29 30 float4x4 matTmp = mul(matWorld, matLightView); 31 matTmp = mul(matTmp, matLightProj); 32 33 34 float4 coordCVV = mul(float4(vData.pos3.xyz, 1.0f), matTmp); 35 36 float4 m = 1/coordCVV.w; 37 38 vOut.pos4.xyz = m*coordCVV.xyz; 39 vOut.pos4.w = 1.0f; 40 41 vOut.lpos4 = vOut.pos4; 42 vOut.lpos4.z *= fat; 43 44 return vOut; 45 } 46 47 float4 makeShadowPS(VertexOutput In) : COLOR 48 { 49 return float4(In.lpos4.z, 0, 0, 1); 50 }
在生成纹理时,将Z-Buffer Test 设为Enable状态,这样就可以保证纹理中保存的深度值始终是离光源最近的那个点的。另外,可以修改上段代码第5行的纹理像素格式,就能方便地得到更精确的深度值。
4) 使用Shadow Map纹理生成阴影
以图一为例,直观来看,生成阴影前应该先将相应观察平面S上的像素对应的空间点(如b'对应的b)的位置计算出来,再用之前生成的Light Space的matLightView和matLightProj把点b投射到平面H上。这样就需要进行从b'到b的变换,很显然观察窗口S的透视矩阵的逆矩阵是存在的。但实际上还有更简易的做法:
1 VertexOutput useShadowVS(SourceData vData) 2 { 3 VertexOutput v = (VertexOutput)0; 4 v.pos4 = mul(float4(vData.pos3, 1.0f), matWorldViewProj); 5 6 7 v.n = mul(float4(vData.n.xyz, 0.0f), matWorld); 8 v.n = normalize(v.n); 9 v.rpos4 = mul(float4(vData.pos3, 1.0f), matWorld); 10 11 float3 vLightDirect = Lamp0Point - v.rpos4.xyz; 12 vLightDirect = normalize(vLightDirect); 13 v.ldirt4 = float4(vLightDirect, 0.0f); 14 15 float4x4 matTmp = mul(matWorld, matLightView); 16 matTmp = mul(matTmp, matLightProj); 17 18 float4 lightCVV = mul(float4(vData.pos3, 1.0f), matTmp); 19 lightCVV.z -= 0.1f; 20 21 float m = 1/lightCVV.w; 22 lightCVV.xyz = m*lightCVV.xyz; 23 lightCVV.w = 1.0f; 24 25 v.lpos4 = lightCVV; 26 27 28 float2 uv = (float2)0; 29 uv.x = (1.0f+v.lpos4.x)/2.0f; 30 uv.y = (1.0f-v.lpos4.y)/2.0f; 31 v.uvd.xy = uv; 32 v.uvd.z = v.lpos4.z; 33 34 return v; 35 } 36 37 38 float4 useShadowPS(VertexOutput v) : COLOR 39 { 40 41 float2 uv = v.uvd.xy; 42 float dep = v.uvd.z; 43 44 float3 samplerCol = (float3)0; 45 float c = -1; 46 float tmpLm = 0.0f; 47 48 float3 sdp = tex2D(Lamp0ShadowMapSampler, uv).rgb; 49 if( dep < sdp.x ) 50 { 51 tmpLm = 1.0f; 52 } 53 float fall = 1.0/dot(v.ldirt4.xyz, v.ldirt4.xyz); 54 55 float3 ld = v.ldirt4.xyz; 56 float3 n = v.n; 57 float diffuse = dot(ld, n); 58 float3 col = float3(1,1,1); 59 float linf = 0.8f; 60 //col = diffuse * col; 61 62 tmpLm = (tmpLm)*diffuse*fall*linf; 63 col = tmpLm * col; 64 65 return float4(col, 1); 66 }
第15到第32行,直接计算出每一个顶点在Light Space投影平面上的点的x、y、 z坐标值;在进入到观察者投影变换时,可见像素的x、y、z坐标就可以据此通过插值得到。这样做好处是,避免计算透视变换的逆运算,能使代码更简洁,不足之处是增加了大量多余的运算。
第19行,对lightCVV的z值做了一个偏移运算,作用是校正浮点运算可能出现的误差。以图一中的点b为例,由于基于浮点数的空间变换运算会出现计算误差,因此位于W表面上的b点经投影变换后,本应等于Z-Buffer中相应像素的深度值,有可能变得大于此值,从而导致其后的逻辑判断出错(第49行),所以需要对运算结果做一个误差校正。更一般的做法是将lightCVV乘以一个事先设置好的误差校正矩阵。
第53行,计算光照强度衰减因子(与距离的平方成反比)。初始光照强度在第59行设定。