检查硬件兼容性
// hlsl中的lerp等函数需要支持pixel shader 2.0以上 if(pCaps->PixelShaderVersion < D3DPS_VERSION(2, 0)) { return false; } // 作为shadow map的render target需要支持D3DFMT_R32F格式 IDirect3D9 * pD3D = DXUTGetD3D9Object(); if(FAILED(pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType, AdapterFormat, D3DUSAGE_RENDERTARGET, D3DRTYPE_CUBETEXTURE, D3DFMT_R32F))) { return false; }
定义一个d3d texture作为shadow的render target,一个d3d surface作为shadow map的depth scentil
CComPtr<IDirect3DTexture9> m_shadowMapRT; CComPtr<IDirect3DSurface9> m_shadowMapDS;
创建shadow map的render target
pd3dDevice->CreateTexture( SHADOWMAP_SIZE, // shadow map的尺寸,更具场景大小来决定,尺寸越大shadow越精确 SHADOWMAP_SIZE, 1, // 由于shadow map不需要mip level,故而为1 D3DUSAGE_RENDERTARGET, // 将这个贴图作为d3d的render target D3DFMT_R32F, // 使用32位浮点数作为red值,且只有red值,这种d3dfmt会出现不兼容性,所以要事先检查IsD3D9DeviceAcceptable D3DPOOL_DEFAULT, // 使用默认的内存池,由于是render target,所以系统应该会分配为system mem &m_shadowMapRT, // 用以保存返回的render target NULL)
创建shadow map的depth scentil
// 因为新建的render target不支持multi-sampling,所以在实际渲染时需要再创建一个尺寸和render target相同,也不支持multi-sampling的depth scentil与之对应 DXUTDeviceSettings d3dSettings = DXUTGetDeviceSettings(); FAILED_THROW_D3DEXCEPTION(pd3dDevice->CreateDepthStencilSurface( SHADOWMAP_SIZE, SHADOWMAP_SIZE, d3dSettings.d3d9.pp.AutoDepthStencilFormat, D3DMULTISAMPLE_NONE, 0, TRUE, &m_shadowMapDS, NULL));
不要忘记在适当的时候销毁资源
m_shadowMapRT.Release(); m_shadowMapDS.Release();
接下来在渲染帧的时候,将shadow渲染到render target,但要注意场景的变换是依据光源的
// 获得相机投影矩阵 D3DXMATRIXA16 mWorld = *m_camera.GetWorldMatrix(); // 计算光照的透视变换 D3DXMATRIXA16 mViewLight; D3DXMatrixLookAtLH( &mViewLight, &D3DXVECTOR3(0.0f, 0.0f, 50.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); D3DXMATRIXA16 mProjLight; D3DXMatrixOrthoLH(&mProjLight, 50, 50, 25, 75); D3DXMATRIXA16 mWorldViewProjLight = mWorld * mViewLight * mProjLight; // 备份原先的render target及depth scentil HRESULT hr; LPDIRECT3DSURFACE9 pOldRT = NULL; V(pd3dDevice->GetRenderTarget(0, &pOldRT)); LPDIRECT3DSURFACE9 pShadowSurf; V(m_shadowMapRT->GetSurfaceLevel(0, &pShadowSurf)); V(pd3dDevice->SetRenderTarget(0, pShadowSurf)); SAFE_RELEASE(pShadowSurf); LPDIRECT3DSURFACE9 pOldDS = NULL; V(pd3dDevice->GetDepthStencilSurface(&pOldDS)); V(pd3dDevice->SetDepthStencilSurface(m_shadowMapDS)); // 清理render target V(pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00ffffff, 1.0f, 0)); // 渲染shadow场景 if(SUCCEEDED(hr = pd3dDevice->BeginScene())) { // 更新更新constant table中的matrix,应以在vertex shader中变换 V(m_constantTable->SetMatrix("g_mWorldViewProjLight", &mWorldViewProjLight)); // 设置渲染shadow map专用的vertex shader及pixcel shader,也可以使用d3dx effect ... // 渲染场景模型 ... V(pd3dDevice->EndScene()); } // 恢复原先的render target及depth scentil V(pd3dDevice->SetRenderTarget(0, pOldRT)); V(pd3dDevice->SetDepthStencilSurface(pOldDS)); SAFE_RELEASE(pOldRT); SAFE_RELEASE(pOldDS);
渲染实际的场景,但要在pixel shader中判断是否需采用diffuse还是ambient
// 清理缓存背景及depth stencil V(pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 72, 72, 72), 1, 0)); if(SUCCEEDED(hr = pd3dDevice->BeginScene())) { // 更新更新constant table中的matrix,及对应的shadow map V(m_constantTable->SetMatrix(...)); V(m_constantTable->SetTexture("g_ShadowTexture", m_shadowMapRT)); V(m_constantTable->SetMatrix("g_mWorldViewProjLight", &mWorldViewProjLight)); ... // 设置渲染实际场景所用的vertex shader及pixcel shader,也可以使用d3dx effect ... // 渲染场景模型 ... V(pd3dDevice->EndScene()); }
渲染shadow map的vertex shader,其实质就是将顶点变换后的zw保存到texcoord0,那样的话在pixel shader中就能收到已经插值好的值
// 用以保存顶点变换的matrix float4x4 g_mWorldViewProjectionLight; // vertex shader void RenderShadowVS(float4 Pos : POSITION, float3 Normal : NORMAL, out float4 oPos : POSITION, out float2 Depth : TEXCOORD0) { oPos = mul(Pos, g_mWorldViewProjectionLight); Depth.xy = oPos.zw; }
渲染shadow map的pixel shader,实质就是将z/w的值保存到render target的对应R32F上去,且这里只有一个red值
// pixcel shader void RenderShadowPS(float2 Depth : TEXCOORD0, out float4 Color : COLOR) { Color = Depth.x / Depth.y; }
渲染场景的vertex shader
渲染场景的pixcel shader
// 将light position的值[-1, 1]转换成shadow map的uv float2 ShadowTexC = 0.5 * In.PosLight.xy / In.PosLight.w + float2( 0.5, 0.5 ); ShadowTexC.y = 1.0f - ShadowTexC.y; // transform to texel space float2 texelpos = SMAP_SIZE * ShadowTexC; // Determine the lerp amounts float2 lerps = frac( texelpos ); // read in bilerp stamp, doing the shadow checks float sourcevals[4]; sourcevals[0] = (tex2D( samplerShadow, ShadowTexC ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f; sourcevals[1] = (tex2D( samplerShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f; sourcevals[2] = (tex2D( samplerShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < In.PosLight.z / In.PosLight.w)? 0.0f: 1.0f; 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; // lerp between the shadow values to calculate our light amount float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ), lerp( sourcevals[2], sourcevals[3], lerps.x ), lerps.y ); // 使用LightAmount来进行从ambient到diffuse之间的插值 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