检查硬件兼容性
- // 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
- 计算必要的顶点变换
- 实现将diffuse color计算好,避免pixel shader中计算
- 还要将顶点的光源(shadow map中使用的)变换计算并输出,可以利用float4 texcoord1的通道来保存
渲染场景的pixcel shader
- 将light变换之后的position重新映射到shadow map,并比较大小来判断当前像素是否处在阴影中
- 由于浮点数存在误差,需要使用+ SHADOW_EPSILON来加大像素与shadow map中的距离
- 取shadow map像素时可以使用2x2插值来削尖锐化现象
- // 将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