在direct 3d中实现shadow map的步骤

检查硬件兼容性

[cpp]  view plain copy
  1. // hlsl中的lerp等函数需要支持pixel shader 2.0以上  
  2. if(pCaps->PixelShaderVersion < D3DPS_VERSION(2, 0))  
  3. {  
  4.     return false;  
  5. }  
  6. // 作为shadow map的render target需要支持D3DFMT_R32F格式  
  7. IDirect3D9 * pD3D = DXUTGetD3D9Object();  
  8. if(FAILED(pD3D->CheckDeviceFormat(  
  9.     pCaps->AdapterOrdinal, pCaps->DeviceType, AdapterFormat, D3DUSAGE_RENDERTARGET, D3DRTYPE_CUBETEXTURE, D3DFMT_R32F)))  
  10. {  
  11.     return false;  
  12. }  
 

定义一个d3d texture作为shadow的render target,一个d3d surface作为shadow map的depth scentil

[cpp]  view plain copy
  1. CComPtr<IDirect3DTexture9> m_shadowMapRT;  
  2. CComPtr<IDirect3DSurface9> m_shadowMapDS;  
 

创建shadow map的render target

[cpp]  view plain copy
  1. pd3dDevice->CreateTexture(  
  2.     SHADOWMAP_SIZE,         // shadow map的尺寸,更具场景大小来决定,尺寸越大shadow越精确  
  3.     SHADOWMAP_SIZE,  
  4.     1,                      // 由于shadow map不需要mip level,故而为1  
  5.     D3DUSAGE_RENDERTARGET,  // 将这个贴图作为d3d的render target  
  6.     D3DFMT_R32F,            // 使用32位浮点数作为red值,且只有red值,这种d3dfmt会出现不兼容性,所以要事先检查IsD3D9DeviceAcceptable  
  7.     D3DPOOL_DEFAULT,        // 使用默认的内存池,由于是render target,所以系统应该会分配为system mem  
  8.     &m_shadowMapRT,         // 用以保存返回的render target  
  9.     NULL)  
 

创建shadow map的depth scentil

[cpp]  view plain copy
  1. // 因为新建的render target不支持multi-sampling,所以在实际渲染时需要再创建一个尺寸和render target相同,也不支持multi-sampling的depth scentil与之对应  
  2. DXUTDeviceSettings d3dSettings = DXUTGetDeviceSettings();  
  3. FAILED_THROW_D3DEXCEPTION(pd3dDevice->CreateDepthStencilSurface(  
  4.     SHADOWMAP_SIZE,  
  5.     SHADOWMAP_SIZE,  
  6.     d3dSettings.d3d9.pp.AutoDepthStencilFormat,  
  7.     D3DMULTISAMPLE_NONE,  
  8.     0,  
  9.     TRUE,  
  10.     &m_shadowMapDS,  
  11.     NULL));  
 

不要忘记在适当的时候销毁资源

[cpp]  view plain copy
  1. m_shadowMapRT.Release();  
  2. m_shadowMapDS.Release();  
 

接下来在渲染帧的时候,将shadow渲染到render target,但要注意场景的变换是依据光源的

[cpp]  view plain copy
  1. // 获得相机投影矩阵  
  2. D3DXMATRIXA16 mWorld = *m_camera.GetWorldMatrix();  
  3. // 计算光照的透视变换  
  4. D3DXMATRIXA16 mViewLight;  
  5. D3DXMatrixLookAtLH(  
  6.     &mViewLight,  
  7.     &D3DXVECTOR3(0.0f, 0.0f, 50.0f),  
  8.     &D3DXVECTOR3(0.0f, 0.0f, 0.0f),  
  9.     &D3DXVECTOR3(0.0f, 1.0f, 0.0f));  
  10. D3DXMATRIXA16 mProjLight;  
  11. D3DXMatrixOrthoLH(&mProjLight, 50, 50, 25, 75);  
  12. D3DXMATRIXA16 mWorldViewProjLight = mWorld * mViewLight * mProjLight;  
  13. // 备份原先的render target及depth scentil  
  14. HRESULT hr;  
  15. LPDIRECT3DSURFACE9 pOldRT = NULL;  
  16. V(pd3dDevice->GetRenderTarget(0, &pOldRT));  
  17. LPDIRECT3DSURFACE9 pShadowSurf;  
  18. V(m_shadowMapRT->GetSurfaceLevel(0, &pShadowSurf));  
  19. V(pd3dDevice->SetRenderTarget(0, pShadowSurf));  
  20. SAFE_RELEASE(pShadowSurf);  
  21. LPDIRECT3DSURFACE9 pOldDS = NULL;  
  22. V(pd3dDevice->GetDepthStencilSurface(&pOldDS));  
  23. V(pd3dDevice->SetDepthStencilSurface(m_shadowMapDS));  
  24. // 清理render target  
  25. V(pd3dDevice->Clear(  
  26.     0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00ffffff, 1.0f, 0));  
  27. // 渲染shadow场景  
  28. if(SUCCEEDED(hr = pd3dDevice->BeginScene()))  
  29. {  
  30.     // 更新更新constant table中的matrix,应以在vertex shader中变换  
  31.     V(m_constantTable->SetMatrix("g_mWorldViewProjLight", &mWorldViewProjLight));  
  32.     // 设置渲染shadow map专用的vertex shader及pixcel shader,也可以使用d3dx effect  
  33.     ...  
  34.     // 渲染场景模型  
  35.     ...  
  36.     V(pd3dDevice->EndScene());  
  37. }  
  38. // 恢复原先的render target及depth scentil  
  39. V(pd3dDevice->SetRenderTarget(0, pOldRT));  
  40. V(pd3dDevice->SetDepthStencilSurface(pOldDS));  
  41. SAFE_RELEASE(pOldRT);  
  42. SAFE_RELEASE(pOldDS);  
 

渲染实际的场景,但要在pixel shader中判断是否需采用diffuse还是ambient

[cpp]  view plain copy
  1. // 清理缓存背景及depth stencil  
  2. V(pd3dDevice->Clear(  
  3.     0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 72, 72, 72), 1, 0));  
  4. if(SUCCEEDED(hr = pd3dDevice->BeginScene()))  
  5. {  
  6.     // 更新更新constant table中的matrix,及对应的shadow map  
  7.     V(m_constantTable->SetMatrix(...));  
  8.     V(m_constantTable->SetTexture("g_ShadowTexture", m_shadowMapRT));  
  9.     V(m_constantTable->SetMatrix("g_mWorldViewProjLight", &mWorldViewProjLight));  
  10.     ...  
  11.     // 设置渲染实际场景所用的vertex shader及pixcel shader,也可以使用d3dx effect  
  12.     ...  
  13.     // 渲染场景模型  
  14.     ...  
  15.     V(pd3dDevice->EndScene());  
  16. }  
 

渲染shadow map的vertex shader,其实质就是将顶点变换后的zw保存到texcoord0,那样的话在pixel shader中就能收到已经插值好的值

[cpp]  view plain copy
  1. // 用以保存顶点变换的matrix  
  2. float4x4 g_mWorldViewProjectionLight;  
  3. // vertex shader  
  4. void RenderShadowVS(float4 Pos : POSITION,  
  5.                     float3 Normal : NORMAL,  
  6.                     out float4 oPos : POSITION,  
  7.                     out float2 Depth : TEXCOORD0)  
  8. {  
  9.     oPos = mul(Pos, g_mWorldViewProjectionLight);  
  10.     Depth.xy = oPos.zw;  
  11. }  
 

渲染shadow map的pixel shader,实质就是将z/w的值保存到render target的对应R32F上去,且这里只有一个red值

[cpp]  view plain copy
  1. // pixcel shader  
  2. void RenderShadowPS(float2 Depth : TEXCOORD0,  
  3.                     out float4 Color : COLOR)  
  4. {  
  5.     Color = Depth.x / Depth.y;  
  6. }  
 

渲染场景的vertex shader

  • 计算必要的顶点变换
  • 实现将diffuse color计算好,避免pixel shader中计算
  • 还要将顶点的光源(shadow map中使用的)变换计算并输出,可以利用float4 texcoord1的通道来保存

 

渲染场景的pixcel shader

  • 将light变换之后的position重新映射到shadow map,并比较大小来判断当前像素是否处在阴影中
  • 由于浮点数存在误差,需要使用+ SHADOW_EPSILON来加大像素与shadow map中的距离
  • 取shadow map像素时可以使用2x2插值来削尖锐化现象

[cpp]  view plain copy
  1. // 将light position的值[-1, 1]转换成shadow map的uv  
  2. float2 ShadowTexC = 0.5 * In.PosLight.xy / In.PosLight.w + float2( 0.5, 0.5 );  
  3. ShadowTexC.y = 1.0f - ShadowTexC.y;  
  4. // transform to texel space  
  5. float2 texelpos = SMAP_SIZE * ShadowTexC;  
  6. // Determine the lerp amounts             
  7. float2 lerps = frac( texelpos );  
  8. // read in bilerp stamp, doing the shadow checks  
  9. float sourcevals[4];  
  10. sourcevals[0] = (tex2D( samplerShadow, ShadowTexC ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f;    
  11. sourcevals[1] = (tex2D( samplerShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f;    
  12. sourcevals[2] = (tex2D( samplerShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f;    
  13. sourcevals[3] = (tex2D( samplerShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f;    
  14. // lerp between the shadow values to calculate our light amount  
  15. float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),  
  16.                           lerp( sourcevals[2], sourcevals[3], lerps.x ),  
  17.                           lerps.y );  
  18. // 使用LightAmount来进行从ambient到diffuse之间的插值  
  19. Output.RGBColor = tex2D(MeshTextureSampler, In.TextureUV) * lerp(g_MaterialAmbientColor, In.Diffuse, LightAmount);  
 

注意:这里的SHADOW_EPSILON的大小,应当与shadow map中一个pixel在实际场景中所占的单位来决定,小于这个单位的话,会在模型表面出现“颗粒”,这是因为场景模型中的这个点位于shadow map中的那个平均值depth之后

 

myd3dlib中实现shadow map的截图,效果并不明显,这里只是演示了self shadow cast


原文链接http://blog.csdn.net/tangyin025/article/details/6366519

你可能感兴趣的:(在direct 3d中实现shadow map的步骤)