//HardwareShadowMap.cpp,原程序见Nvidia SDK9.1
//该程序与Nvidia SDK中的SoftShadow程序一样,也是用ShadowMap产生阴影。 即 //从灯光位置将场景渲染到Texture(ShadowMao)中产生深度图,再次渲染场景,各点与 //ShadowMap中的深度比较,深度浅,即在阴影中。变黑即可。 //ShadowMap方法优点是比ShadowMap简单,能处理一些ShadowVolume处理不料了的情况. //如处理静态阴影,可先渲染好ShadowMap,或将静态与动态分开渲染,绘制时,每帧只渲染动态的, //这样,速度可以达到比较理想 //本程序比较简单,显示一个摇臂小机器在桌面上运动,产生动态阴影. //本例给出四种阴影效果,无柔化的,简单柔化的,高级柔化的,当然,越高级速度越慢 //主程序为HardwareShadowMapApp.cpp,主要是D3D初始化,控制灯,关键的Render中,调用本程序渲染: // g_pHardwareShadowMap->Render(pd3dDevice, fTime, fElapsedTime, // g_Camera.GetWorldMatrix(), // g_Camera.GetViewMatrix(), // g_Camera.GetProjMatrix()); #include "HardwareShadowMap.h" #include #include #pragma warning(disable : 4786) #include #pragma warning(disable : 4786) #include static const int TEXDEPTH_HEIGHT = 512; //ShadowMap 大小 static const int TEXDEPTH_WIDTH = 512; HardwareShadowMap::HardwareShadowMap() //初始化参数 { m_time = ::timeGetTime()*0.001f; m_startTime = m_time; m_frame = 0; m_fps = 30; m_main_menu = 0; m_context_menu = 0; m_pEffect = NULL; m_pAttributes = NULL; m_bWireframe = false; m_fRadius = 0; m_vecCenter = D3DXVECTOR3(0.f, 0.f, 0.f); m_pBackBuffer = NULL; m_pZBuffer = NULL; m_pSMColorTexture = NULL; m_pSMDecalTextureGround = NULL; m_pSMDecalTextureBot = NULL; m_pSMZTexture = NULL; m_pSMColorSurface = NULL; m_pSMZSurface = NULL; m_lightDir = D3DXVECTOR4(0.f, 0.f, 0.f, 0.f); m_bitDepth = 24; m_Paused = false; m_pDeclaration = NULL; m_fDepthBias = 0.0002f; m_fBiasSlope = 2.0f; m_pScene = NULL; m_bLameTech = 0; D3DXMatrixIdentity(&m_World); D3DXMatrixIdentity(&m_View); D3DXMatrixIdentity(&m_Projection); } //初始化 HRESULT HardwareShadowMap::ResetDevice( IDirect3DDevice9* m_pd3dDevice, const D3DSURFACE_DESC* m_pBackBufferSurfaceDesc ) { HRESULT hr; assert(m_pd3dDevice); D3DFORMAT zFormat = D3DFMT_D24S8; m_bitDepth = 24; if(FAILED(CheckResourceFormatSupport(m_pd3dDevice, zFormat, D3DRTYPE_TEXTURE, D3DUSAGE_DEPTHSTENCIL))) { MessageBox(NULL, _T("Device/driver does not support hardware shadow maps!"), _T("ERROR"), MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return E_FAIL; } //setup buffers if(FAILED(m_pd3dDevice->GetRenderTarget(0, &m_pBackBuffer))) return E_FAIL; if(FAILED(m_pd3dDevice->GetDepthStencilSurface(&m_pZBuffer))) return E_FAIL; if(!m_pBackBuffer || !m_pZBuffer) return E_FAIL; //载入灯光光罩图像:一个外边黑中间白圆的图,做光罩 if(FAILED(D3DXCreateTextureFromFileEx(m_pd3dDevice, GetFilePath::GetFilePath(_T("spotlight.tga")).c_str(), TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_pSMDecalTextureGround))) return E_FAIL; //载入阳光光罩图像:一个外边黑中间白圆的图,比灯光的亮 if(FAILED(D3DXCreateTextureFromFileEx(m_pd3dDevice, GetFilePath::GetFilePath(_T("Sunlight.tga")).c_str(), TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &m_pSMDecalTextureBot))) return E_FAIL; D3DFORMAT colorFormat = D3DFMT_A8R8G8B8; if( (zFormat == D3DFMT_D16) || (zFormat == D3DFMT_D15S1) ) colorFormat = D3DFMT_R5G6B5; //根据Z-buffer格式,调整color格式 //建立ShadowMap图 if(FAILED(m_pd3dDevice->CreateTexture(TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1, D3DUSAGE_RENDERTARGET, colorFormat, D3DPOOL_DEFAULT, &m_pSMColorTexture, NULL))) return E_FAIL; //建立ShadowMap的Z-buffer图 if(FAILED(m_pd3dDevice->CreateTexture(TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1, D3DUSAGE_DEPTHSTENCIL, zFormat, D3DPOOL_DEFAULT, &m_pSMZTexture, NULL))) return E_FAIL; if(!m_pSMColorTexture || !m_pSMZTexture || !m_pSMDecalTextureGround || !m_pSMDecalTextureBot) return E_FAIL; // Retrieve top-level surfaces of our shadow buffer (need these for use with SetRenderTarget) //取得图的Surface,供渲染使用 if(FAILED(m_pSMColorTexture->GetSurfaceLevel(0, &m_pSMColorSurface))) return E_FAIL; if(FAILED(m_pSMZTexture->GetSurfaceLevel(0, &m_pSMZSurface))) return E_FAIL; if(!m_pSMColorSurface || !m_pSMZSurface) return E_FAIL; // Assign registers to the relevant vertex attributes //定义顶点格式 D3DVERTEXELEMENT9 declaration[] = { { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, D3DDECL_END() }; m_pd3dDevice->CreateVertexDeclaration(declaration, &m_pDeclaration); const char* profileOpts[] = { "-profileopts", "dcls", NULL, }; DWORD tempFVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX0; //建立方桌 hr = CreateQuad(m_pd3dDevice,&m_smQuad); //读取机器人Mesh,是Nvidia自己的nvb格式的 tstring fileName(_T("ClawBOT.nvb")); // m_pScene = new NVBScene; hr = m_pScene->Load(fileName, m_pd3dDevice, GetFilePath::GetFilePath); if(FAILED(hr)) return hr; if (!m_pScene->m_VertexHasNormal) { MessageBox(NULL, _T("Model does not have normals"), _T("ERROR"), MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return E_FAIL; } //bigship needs a bit of scale / trans m_smBigship.scaleVec = D3DXVECTOR3(0.5f, 0.5f, 0.5f); m_smBigship.transVec = D3DXVECTOR3(0.0f, -5.0f, 0.0f); //载入效果fx文件 // Load our Effect file // note: path is relative to MEDIA\ dir hr = D3DXCreateEffectFromFile(m_pd3dDevice, GetFilePath::GetFilePath(_T("programs\\HLSL_HardwareShadowMap\\HardwareShadowMap.fx")).c_str(), NULL, NULL, 0, NULL, &m_pEffect, NULL); if (FAILED(hr)) { MessageBox(NULL, _T("Failed to load effect file"), _T("ERROR"), MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return hr; } //定义灯光方向 m_lightDir.x = 0.8f; m_lightDir.y = 1.0f; m_lightDir.z = 0.0f; m_lightDir.w = 0.0f; D3DXVec4Normalize(&m_lightDir, &m_lightDir); //将灯光向量传到效果中 m_pEffect->SetValue("LightVec", (float*)&m_lightDir, sizeof(float)*3); m_lightPos.x = m_pScene->m_Center.x + m_pScene->m_Radius / 0.5f; m_lightPos.y = m_pScene->m_Center.y + m_pScene->m_Radius / 0.5f; m_lightPos.z = 0; return S_OK; } //向效果传递矩阵的函数 HRESULT HardwareShadowMap::SetVertexShaderMatrices(const D3DXMATRIX& worldMat, const D3DXMATRIX& viewMat, const D3DXMATRIX& projMat, const D3DXMATRIX& texMat) { D3DXMATRIX worldViewMat = worldMat * viewMat; D3DXMATRIX worldViewProjMat = worldMat * viewMat * projMat; D3DXMATRIX worldITMat; D3DXMatrixInverse(&worldITMat, NULL, &worldMat); D3DXMatrixTranspose(&worldITMat, &worldITMat); m_pEffect->SetMatrix("WorldViewProj", &worldViewProjMat); m_pEffect->SetMatrix("WorldView", &worldViewMat); m_pEffect->SetMatrix("WorldIT", &worldITMat); m_pEffect->SetMatrix("TexTransform", &texMat); return S_OK; } //建立方桌 HRESULT HardwareShadowMap::CreateQuad(IDirect3DDevice9* m_pd3dDevice, SMMeshInfo* mesh) { HRESULT hr = S_OK; hr = m_pd3dDevice->CreateVertexBuffer(4 * sizeof(SMVertex), D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &(mesh->pVB), NULL); if(FAILED(hr)) return hr; SMVertex* pVData; hr = mesh->pVB->Lock(0, 0, (void**)&pVData, 0); if(FAILED(hr)) return hr; float value = 200.0f; pVData[0].x = -value; pVData[0].y = -10.0f; pVData[0].z = value; pVData[0].nx = 0.0f; pVData[0].ny = 1.0f; pVData[0].nz = 0.0f; pVData[1].x = value; pVData[1].y = -10.0f; pVData[1].z = value; pVData[1].nx = 0.0f; pVData[1].ny = 1.0f; pVData[1].nz = 0.0f; pVData[2].x = -value; pVData[2].y = -10.0f; pVData[2].z = -value; pVData[2].nx = 0.0f; pVData[2].ny = 1.0f; pVData[2].nz = 0.0f; pVData[3].x = value; pVData[3].y = -10.0f; pVData[3].z = -value; pVData[3].nx = 0.0f; pVData[3].ny = 1.0f; pVData[3].nz = 0.0f; hr = mesh->pVB->Unlock(); if(FAILED(hr)) return hr; hr = m_pd3dDevice->CreateIndexBuffer(4 * sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &(mesh->pIB), NULL); if(FAILED(hr)) return hr; WORD* pIData; hr = mesh->pIB->Lock(0, 0, (void**)&pIData, 0); if(FAILED(hr)) return hr; //it's a strip pIData[0] = 0; pIData[1] = 2; pIData[2] = 1; pIData[3] = 3; hr = mesh->pIB->Unlock(); mesh->dwNumFaces = 2; mesh->dwNumVerts = 4; mesh->primType = D3DPT_TRIANGLESTRIP; //quad doesn't get scaled / translated mesh->scaleVec = D3DXVECTOR3(1.0f, 1.0f, 1.0f); mesh->transVec = D3DXVECTOR3(0.0f, 0.0f, 0.0f); return S_OK; } //检查显卡能力 HRESULT HardwareShadowMap::CheckResourceFormatSupport(IDirect3DDevice9* m_pd3dDevice, D3DFORMAT fmt, D3DRESOURCETYPE resType, DWORD dwUsage) { HRESULT hr = S_OK; IDirect3D9* tempD3D = NULL; m_pd3dDevice->GetDirect3D(&tempD3D); D3DCAPS9 devCaps; m_pd3dDevice->GetDeviceCaps(&devCaps); D3DDISPLAYMODE displayMode; tempD3D->GetAdapterDisplayMode(devCaps.AdapterOrdinal, &displayMode); hr = tempD3D->CheckDeviceFormat(devCaps.AdapterOrdinal, devCaps.DeviceType, displayMode.Format, dwUsage, resType, fmt); tempD3D->Release(), tempD3D = NULL; return hr; } //关键:渲染阴影图 HRESULT HardwareShadowMap::RenderShadowMap(IDirect3DDevice9* m_pd3dDevice) { //setup matrices for shadowmap D3DXVECTOR3 eye, lookAt, up; //灯光位置为视点 lookAt.x = m_lightPos.x - (m_lightDir.x); lookAt.y = m_lightPos.y - (m_lightDir.y); lookAt.z = m_lightPos.z - (m_lightDir.z); up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; D3DXMATRIX lightView, lightProj; D3DXMatrixLookAtLH(&lightView, &m_lightPos, &lookAt, &up); //灯光透视矩阵,60度,1:1,最近50,最远500,可根据自己的需要修改 D3DXMatrixPerspectiveFovLH(&lightProj, D3DXToRadian(60.0f), 1.0f, 50.0f, 500.0f); m_LightViewProj = lightView * lightProj; //设置ShadowMap图为渲染目标 //set render target to shadow map surfaces if(FAILED(m_pd3dDevice->SetRenderTarget(0, m_pSMColorSurface))) return E_FAIL; //深度图 //set depth stencil if(FAILED(m_pd3dDevice->SetDepthStencilSurface(m_pSMZSurface))) return E_FAIL; //save old viewport //保存老的视口,设置新的 D3DVIEWPORT9 oldViewport; m_pd3dDevice->GetViewport(&oldViewport); //set new, funky viewport D3DVIEWPORT9 newViewport; newViewport.X = 0; newViewport.Y = 0; newViewport.Width = TEXDEPTH_WIDTH; newViewport.Height = TEXDEPTH_HEIGHT; newViewport.MinZ = 0.0f; newViewport.MaxZ = 1.0f; m_pd3dDevice->SetViewport(&newViewport); //其实,使用Fx应该不需要再设视口了 //use technique that will draw plain black pixels //选择使用的Technique为发生阴影图 if (FAILED(m_pEffect->SetTechnique("GenHardwareShadowMap"))) { MessageBox(NULL, _T("Failed to set 'GenHardwareShadowMap' technique in effect file"), _T("ERROR"), MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return E_FAIL; } //depth bias //为什么用这两个渲染参数不太了解,按理说不需要对Z-Buffer偏移了.调节这两个参数也没发现场景有什么变化.估计是将深度上移动少量距离,避免比较时出错. m_pd3dDevice->SetRenderState(D3DRS_DEPTHBIAS, *(DWORD*)&m_fDepthBias); m_pd3dDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, *(DWORD*)&m_fBiasSlope); //先清除图象 m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00FFFFFF, 1.0f, 0L); D3DXMATRIX tempIdentity; D3DXMatrixIdentity(&tempIdentity); //绘制全部场景 for (unsigned int i = 0; i < m_pScene->m_NumMeshes; ++i) { const NVBScene::Mesh& mesh = m_pScene->m_Meshes[i]; D3DXMATRIX worldMat = mesh.m_Transform * m_World; SetVertexShaderMatrices(worldMat, lightView, lightProj, tempIdentity); m_pd3dDevice->SetVertexDeclaration(m_pDeclaration); // render mesh using GenHardwareShadowMap technique UINT uPasses; if (D3D_OK == m_pEffect->Begin(&uPasses, 0)) { // The 0 specifies that ID3DXEffect::Begin and ID3DXEffect::End will save and restore all state modified by the effect. for (UINT uPass = 0; uPass < uPasses; uPass++) { // Set the state for a particular pass in a technique. m_pEffect->BeginPass(uPass); // Draw it if (FAILED(mesh.Draw())) return E_FAIL; m_pEffect->EndPass(); } m_pEffect->End(); } } D3DXMATRIX tempScaleMat; D3DXMatrixScaling(&tempScaleMat, m_smQuad.scaleVec.x, m_smQuad.scaleVec.y, m_smQuad.scaleVec.z); SetVertexShaderMatrices(tempScaleMat, lightView, lightProj, tempIdentity); //set vb HRESULT hr = m_pd3dDevice->SetStreamSource(0, m_smQuad.pVB, 0, sizeof(SMVertex)); if (FAILED(hr)) return hr; //set index buffer hr = m_pd3dDevice->SetIndices(m_smQuad.pIB); if(FAILED(hr)) return hr; //绘制桌面,其实如不需要桌子的影子,在此不必绘制桌面了 //render quad UINT uPasses; if (D3D_OK == m_pEffect->Begin(&uPasses, 0)) { // The 0 specifies that ID3DXEffect::Begin and ID3DXEffect::End will save and restore all state modified by the effect. for (UINT uPass = 0; uPass < uPasses; uPass++) { m_pEffect->BeginPass(uPass); // Set the state for a particular pass in a technique. m_pd3dDevice->DrawIndexedPrimitive(m_smQuad.primType, 0, 0, m_smQuad.dwNumVerts, 0, m_smQuad.dwNumFaces); m_pEffect->EndPass(); } m_pEffect->End(); } //绘制完成,还原视口 m_pd3dDevice->SetViewport(&oldViewport); //depth bias float fTemp = 0.0f; m_pd3dDevice->SetRenderState(D3DRS_DEPTHBIAS, *(DWORD*)&fTemp); m_pd3dDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, *(DWORD*)&fTemp); return S_OK; } //结束释放资源 void HardwareShadowMap::LostDevice() { SAFE_RELEASE(m_smBigship.pVB); SAFE_RELEASE(m_smBigship.pIB); SAFE_RELEASE(m_smQuad.pVB); SAFE_RELEASE(m_smQuad.pIB); SAFE_RELEASE(m_pSMColorTexture); SAFE_RELEASE(m_pSMDecalTextureGround); SAFE_RELEASE(m_pSMDecalTextureBot); SAFE_RELEASE(m_pSMZTexture); SAFE_RELEASE(m_pSMColorSurface); SAFE_RELEASE(m_pSMZSurface); SAFE_RELEASE(m_pBackBuffer); SAFE_RELEASE(m_pZBuffer); SAFE_RELEASE(m_pDeclaration); SAFE_DELETE_ARRAY(m_pAttributes); SAFE_DELETE(m_pScene); SAFE_RELEASE(m_pEffect); } //渲染主过程 HRESULT HardwareShadowMap::Render( IDirect3DDevice9* m_pd3dDevice, double fTime, float fElapsedTime, const D3DXMATRIX* cworldMat, const D3DXMATRIX* cviewMat, const D3DXMATRIX* cprojMat) { HRESULT hr; D3DXHANDLE hTechnique = NULL; //场景动画参数处理 // update time m_time = ::timeGetTime()*0.001f; if (m_frame == 0) m_startTime = m_time; else if (m_time > m_startTime) m_fps = (float)m_frame / (m_time - m_startTime); // Update view matrix m_View = *cviewMat;//m_UICamera->GetRotationMatrix() * m_UICamera->GetTranslationMatrix(); //m_UIScene->SetControlOrientationMatrix(m_View); // Update scene position m_World = *cworldMat;//m_UIScene->GetRotationMatrix() * m_UIScene->GetTranslationMatrix(); static float time = 0.0f; static bool forward = true; if (!m_Paused) { if (forward) time += 30 / m_fps; else time -= 30 / m_fps; } if (time > m_pScene->m_NumMeshKeys - 1) { forward = false; time = (float)m_pScene->m_NumMeshKeys - 2.0f; } if (time < 1) forward = true; if (true)//m_pd3dDevice->BeginScene() == D3D_OK) { m_pScene->Update(time, &m_World); //动画场景 m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, (m_bWireframe ? D3DFILL_WIREFRAME : D3DFILL_SOLID)); //render into shadow map if(FAILED(RenderShadowMap(m_pd3dDevice))) //调用上面的阴影图渲染过程,产生阴影图 return E_FAIL; //根据菜单选择不同的阴影处理Technique过程 const D3DXHANDLE techs[4] = { "UseHardwareShadowMap", "UseHardwareShadowMapLame", "UseHardwareShadowMapGoodNoRot", "UseHardwareShadowMapGoodRot" }; if (FAILED(m_pEffect->SetTechnique(techs[m_bLameTech]))) { MessageBox(NULL, _T("Failed to set 'UseHardwareShadowMap' technique in effect file"), _T("ERROR"), MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return E_FAIL; } //设置渲染目标为背景图 //set render target back to normal back buffer / depth buffer if(FAILED(m_pd3dDevice->SetRenderTarget(0, m_pBackBuffer))) return E_FAIL; //深度 if(FAILED(m_pd3dDevice->SetDepthStencilSurface(m_pZBuffer))) return E_FAIL; //清除缓存(看了一个ATI的Demo,把D3DCLEAR_TARGET去掉了,想想很对,一般情况,根本没必要每次清除TARGET) m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB(0, 60, 60, 60), 1.0f, 0L); //set depth map as texture //将前面生成的阴影深度图传到效果中 if(FAILED(m_pEffect->SetTexture("ShadowMap", m_pSMZTexture))) return E_FAIL; //设置灯光光罩 //set spotlight texture for Bot if(FAILED(m_pEffect->SetTexture("SpotLight", m_pSMDecalTextureBot))) return E_FAIL; //下面这一步Very重要,计算灯光的投影矩阵,这样,效果里直接使用texproj函数,一步就可找到对应的Shadow上的深度点 //set special texture matrix for shadow mapping float fOffsetX = 0.5f + (0.5f / (float)TEXDEPTH_WIDTH); float fOffsetY = 0.5f + (0.5f / (float)TEXDEPTH_HEIGHT); unsigned int range = 1; //note different scale in DX9! //float fBias = -0.001f * (float)range; float fBias = 0.0f; D3DXMATRIX texScaleBiasMat( 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, (float)range, 0.0f, fOffsetX, fOffsetY, fBias, 1.0f ); //逐个渲染各物件 for (unsigned int i = 0; i < m_pScene->m_NumMeshes; ++i) { const NVBScene::Mesh& mesh = m_pScene->m_Meshes[i]; //投影矩阵为worldMat * m_LightViewProj * texScaleBiasMat D3DXMATRIX worldMat = mesh.m_Transform * m_World; SetVertexShaderMatrices(worldMat, m_View, m_Projection, worldMat * m_LightViewProj * texScaleBiasMat); // render mesh using HardwareShadowMapTechnique UINT uPasses; if (D3D_OK == m_pEffect->Begin(&uPasses, 0)) { // The 0 specifies that ID3DXEffect::Begin and ID3DXEffect::End will save and restore all state modified by the effect. for (UINT uPass = 0; uPass < uPasses; uPass++) { // Set the state for a particular pass in a technique. m_pEffect->BeginPass(uPass); // Draw it if (FAILED(hr = mesh.Draw())) return hr; m_pEffect->EndPass(); } m_pEffect->End(); } } //渲染地面,使用光罩 //set spotlight texture for Ground if(FAILED(m_pEffect->SetTexture("SpotLight", m_pSMDecalTextureGround))) return E_FAIL; D3DXMATRIX tempScaleMat; D3DXMatrixScaling(&tempScaleMat, m_smQuad.scaleVec.x, m_smQuad.scaleVec.y, m_smQuad.scaleVec.z); SetVertexShaderMatrices(tempScaleMat, m_View, m_Projection, tempScaleMat * m_LightViewProj * texScaleBiasMat); //set vb hr = m_pd3dDevice->SetStreamSource(0, m_smQuad.pVB, 0, sizeof(SMVertex)); if (FAILED(hr)) return hr; //set index buffer hr = m_pd3dDevice->SetIndices(m_smQuad.pIB); if(FAILED(hr)) return hr; //render quad using HardwareShadowMapTechnique UINT uPasses; if (D3D_OK == m_pEffect->Begin(&uPasses, 0)) { // The 0 specifies that ID3DXEffect::Begin and ID3DXEffect::End will save and restore all state modified by the effect. for (UINT uPass = 0; uPass < uPasses; uPass++) { // Set the state for a particular pass in a technique. m_pEffect->BeginPass(uPass); // Draw it if(FAILED(m_pd3dDevice->DrawIndexedPrimitive(m_smQuad.primType, 0, 0, m_smQuad.dwNumVerts, 0, m_smQuad.dwNumFaces))) return E_FAIL; m_pEffect->EndPass(); } m_pEffect->End(); } // m_pd3dDevice->EndScene(); } m_frame++; return S_OK; }
//HardwareShadowMap.fx 阴影效果实现 //本效果没有考虑颜色及材料贴图,物体每点都是按法向量与灯光角度决定的颜色,即Phong着色. //如果考虑颜色及贴图的话,对多种物体多个材料都要加进来,可能就复杂无比了,估计这就是Shader难普及的原因吧 //当然,对于简单的材料贴图,如果加入进来的话,修改起来也不是很难 //本效果分为两部分 //首先是发生ShadowMap,从灯光视点渲染场景,生成的Z-Buffer即为ShadowMap //第二部分是实际渲染,查找ShadowMap的对应点,渲染阴影场景 //包含几种选择: // UseHardwareShadowMapLame 模糊阴影 // UseHardwareShadowMapGoodNoRot,没有前面CPP提到的UseHardwareShadowMapGoodRot // UseHardwareShadowMap //以及UseHardwareShadowMapLame4x4: 我加入的一个折衷的方案 //----------------------------------------------------------------------------- texture ShadowMap; texture SpotLight; //----------------------------------------------------------------------------- float3 ObjectColor={1.0f,0.0f,0.0f}; //我加的场景色彩参数,可由主程序设置 float4x4 WorldViewProj : WorldViewProj; float4x4 WorldIT : WorldIT; float4x4 WorldView : WorldView; float4x4 TexTransform; float3 LightVec; //----------------------------------------------------------------------------- struct VS_INPUT { float4 Position : POSITION; float3 Normal : NORMAL; }; struct VS_OUTPUT { float4 Position : POSITION; float4 TexCoord0 : TEXCOORD0; float4 TexCoord1 : TEXCOORD1; float3 Color0 : COLOR0; }; struct VS_OUTPUT_3 { float2 Position : VPOS; float4 TexCoord0 : TEXCOORD0; float4 TexCoord1 : TEXCOORD1; float3 Color0 : COLOR0; }; //----------------------------------------------------------------------------- sampler ShadowMapSampler = sampler_state { Texture = ; MinFilter = LINEAR; //线性滤波 MagFilter = LINEAR; MipFilter = None; AddressU = Clamp; AddressV = Clamp; }; sampler ShadowMapSamplerPoint = sampler_state { Texture = ; MinFilter = POINT; //不滤波 MagFilter = POINT; MipFilter = None; AddressU = Clamp; AddressV = Clamp; }; sampler SpotLightSampler = sampler_state { Texture = ; MinFilter = Linear; //三线性滤波 MagFilter = Linear; MipFilter = Linear; AddressU = Clamp; AddressV = Clamp; }; //----------------------------------------------------------------------------- //生成ShadowMap //该VS不需要做什么事情,只要把顶点坐标转换到屏幕坐标就可以了, //如不使用效果直接使用固定管线渲染也可以,当然了,不必使用材料贴图等,以提高速度 //本程序采取偷工减料的办法,直接使用了后面渲染的MainVS,利用里面的顶点转换,其他多余工作做了也没坏处(除了慢一点外) //----------------------------------------------------------------------------- //写ShadowMap,随便写一个黑点,写什么无所谓,反正需要Z-Buffer //未来显卡发展了,应该加个return NULL的功能,什么都不写,估计会更快. float4 BlackPS(void) : COLOR { // just return opaque black (shadow) return float4(0, 0, 0, 1); } //简单阴影渲染,不加模糊处理 //VS主要工作包括把顶点坐标转换到屏幕坐标 //根据ShadowMap的矩阵,生成光罩图的查询坐标 //以便PS把灯光光罩图象加到场景中 VS_OUTPUT MainVS(VS_INPUT IN) { VS_OUTPUT OUT; // transform normal into world space, then dot with world space light vector // to determine how much light is falling on the surface: float3 worldNormal = normalize(mul(IN.Normal, (float3x3)WorldIT)); float ldotn = max(dot(LightVec, worldNormal), 0.0); //计算与顶点法线与光线的点积,Phong渲染 OUT.Color0 = ldotn; //记录到顶点颜色 // transform model-space vertex position to light-space: OUT.TexCoord0 = mul(IN.Position, TexTransform); //由输入的ShadowMap矩阵,生成ShadowMap的查询坐标 // OUT.TexCoord1 = mul(IN.Position, TexTransform); //生成光罩图查询坐标 OUT.TexCoord1 = OUT.TexCoord0; //改写成这样的形式是否比上面一句快? // transform model-space vertex position to screen space: OUT.Position = mul(IN.Position, WorldViewProj); return OUT; } //----------------------------------------------------------------------------- //PS查询光照图,确定输出点的颜色,阴影场景渲染完成,就是这么简单 //不过,这个方法锯齿比较多 float4 MainPS(VS_OUTPUT IN) : COLOR { float3 shadow = tex2D(ShadowMapSampler, IN.TexCoord0).rgb; //直接2D查询拿来做阴影?并不比较深度? //非也,如果IN.TexCoord0为float2 格式,就取出该点位置的图像内容 //这里的IN.TexCoord0可不时U,V,而是float3 //如果为float3,该函数执行比较查询,即查询xy位置的内容,将内容与Z的深度比较,估计应该返回比较的结果0或1, //按结果判断,图像深度小于Z深度返回0,为阴影,反之为1 //这样,一个函数解决了一堆语句才能解决的问题 //该处为float4格式,手册上没有说明,是否自动当成float3了 //这样处理,关键就是前面的灯光投影矩阵了,一次乘法就将查询坐标转换为xyz格式 float3 spotlight = tex2D(SpotLightSampler, IN.TexCoord1).rgb; //原并未使用光罩 float3 color = shadow * IN.Color0*spotlight; //相乘叠加阴影 // 原来程序将光罩去掉了,这样做光罩似乎有点问题 return float4(color, 1.0); } //下面是高模糊处理的渲染 //----------------------------------------------------------------------------- VS_OUTPUT RenderSceneVS(VS_INPUT IN) { VS_OUTPUT OUT; // transform normal into world space, then dot with world space light vector // to determine how much light is falling on the surface: float3 worldNormal = normalize(mul(IN.Normal, (float3x3)WorldIT)); float ldotn = max(dot(LightVec, worldNormal), 0.0); //计算点的法线与光线的夹角,这就是点的颜色 OUT.Color0 = ldotn; //记录点的颜色 // transform model-space vertex position to light-space: OUT.TexCoord0 = mul(IN.Position, TexTransform); //ShadowMap坐标 OUT.TexCoord1 = mul(IN.Position, WorldView); //这里是光罩图坐标,改为WorldView了,后面仍未用到 // transform model-space vertex position to screen space: OUT.Position = mul(IN.Position, WorldViewProj); return OUT; } //高模糊的PS,查询64个点的区域,进行模糊 float4 FilterShadowLamePS(VS_OUTPUT IN) : COLOR { const float SHADOW_SIZE = 512.f; float shadow = 0; for (int y=-4; y<4; y++) for (int x=-4; x<4; x++) //查询8x8的64个点的ShadowMap区域,进行模糊处理 { float4 coord = IN.TexCoord0; coord.xy += (float2(x,y)/SHADOW_SIZE) * IN.TexCoord0.w; shadow += tex2Dproj( ShadowMapSampler, coord ); //这里换成了tex2Dproj了 } shadow= smoothstep(2, 58, shadow); //这个函数是平滑要点,内容是判断shadow是否在指定的2,58间 //如果是,就返回一个2,58间的平稳变化的插值数据, //具体是什么值,还真想不出来 float3 color = shadow * IN.Color0; //叠加阴影 return float4(color,0); } //根据上面原理,我将区域改小,速度自然加快了,这样可以在质量与速度上折衷一下 float4 FilterShadowLamePS4x4(VS_OUTPUT IN) : COLOR { const float SHADOW_SIZE = 512.f; float shadow = 0; for (int y=-2; y<2; y++) for (int x=-2; x<2; x++) //改为查询4x4的16个点的ShadowMap区域,进行模糊处理 { float4 coord = IN.TexCoord0; coord.xy += (float2(x,y)/SHADOW_SIZE) * IN.TexCoord0.w; shadow += tex2Dproj( ShadowMapSampler, coord ); //这里换成了tex2Dproj了 } // shadow= smoothstep(2, 58, shadow); shadow= smoothstep(2, 15, shadow); float shadow_value=0.2f; //我在这里加了个影子亮度参数,这样,影子就不再是死黑死黑的了 float3 color= ObjectColor*IN.Color0; //加上预定义的物体颜色 color = color*(1-shadow_value)+shadow * shadow_value; //叠加阴影 return float4(color,0); } //高级的模糊阴影PS,查询20个点的区域,使用了log2等高级方法 //速度与我修改的4x4相当,所以也不费心研究它的算法了 float4 FilterShadowPS(VS_OUTPUT_3 IN) : COLOR { const float SHADOW_SIZE = 512.f; const float2 sample_array[20] = { //准备20查询位置区域坐标 //float2( 0.f, 0.f ) / SHADOW_SIZE, float2( 1.f, 0.f ) / SHADOW_SIZE, float2( 0.f, 1.f ) / SHADOW_SIZE, float2( 0.f, -1.f ) / SHADOW_SIZE, float2( -1.f, 0.f ) / SHADOW_SIZE, float2( -1.f, -1.f ) / SHADOW_SIZE, float2( 1.f, -1.f ) / SHADOW_SIZE, float2( -1.f, 1.f ) / SHADOW_SIZE, float2( 1.f, 1.f ) / SHADOW_SIZE, float2( 0.f, -2.f ) / SHADOW_SIZE, float2( -2.f, 0.f ) / SHADOW_SIZE, float2( 0.f, 2.f ) / SHADOW_SIZE, float2( 2.f, 0.f ) / SHADOW_SIZE, float2( -1.f, -2.f ) / SHADOW_SIZE, float2( 1.f, -2.f ) / SHADOW_SIZE, float2( -2.f, -1.f ) / SHADOW_SIZE, float2( -2.f, 1.f ) / SHADOW_SIZE, float2( -1.f, 2.f ) / SHADOW_SIZE, float2( 1.f, 2.f ) / SHADOW_SIZE, float2( 2.f, -1.f ) / SHADOW_SIZE, float2( 2.f, 1.f ) / SHADOW_SIZE, }; float2 dSdX = SHADOW_SIZE * ddx( IN.TexCoord0.xy / IN.TexCoord0.w ); //ddx,ddy是计算屏幕坐标与x,y轴的偏导数 float2 dSdY = SHADOW_SIZE * ddy( IN.TexCoord0.xy / IN.TexCoord0.w ); //;(,我发晕了,简单推断它是做一种平滑吧 float approx_major_len = max( dot(dSdX, dSdX), dot(dSdY, dSdY) ); // :( float rho = 0.5f*log2(approx_major_len); // :( float shadow = tex2Dproj(ShadowMapSampler, IN.TexCoord0); //查询出ShadowMap的值 float4 offset = (float4) 0; for ( int i=0; i<4; i++ ) //查询累加周围点的值,应该是1-4吧,上面已经将0值取出来了, { offset.x = dot(sample_array[i].xy, float2(0.793353,-0.608761)); offset.y = dot(sample_array[i].xy, float2(0.608761, 0.793353)); shadow += tex2Dproj(ShadowMapSampler, IN.TexCoord0 + offset*IN.TexCoord0.w); } float3 result; if ( rho < 0.f ) //根据前面得到的rho, { float4 shadowCoord = (float4) 0; shadowCoord.xyz = IN.TexCoord0.xyz / IN.TexCoord0.w; float shadow2 = shadow; for ( int i=4; i<20; i++ ) //查询更远距离的 { offset.x = dot(sample_array[i].xy, float2(0.793353,-0.608761)); offset.y = dot(sample_array[i].xy, float2(0.608761, 0.793353)); shadow2 += tex2Dlod(ShadowMapSampler, shadowCoord + offset); } shadow2 = shadow + shadow2; shadow2 = smoothstep(1.f, 19.f, shadow2); //平滑 shadow = smoothstep(1,5,shadow); float lerp_fac = saturate(-(rho)); result = lerp(float3(0,1,0), float3(1,0,0), lerp_fac); shadow = lerp(shadow, shadow2, lerp_fac); } else { shadow = smoothstep(1,5,shadow); //如rho不小于0,直接平滑5个点的 result = float3(0,1,0); } float3 color = result * (0.75*IN.Color0*shadow + 0.25); return float4(color, 1.0); } //VSPS完,下面是定义technique,使用上面的VSPS //----------------------------------------------------------------------------- technique UseHardwareShadowMap { pass P0 { VertexShader = compile vs_1_1 MainVS(); PixelShader = compile ps_1_1 MainPS(); ZEnable = True; AlphaBlendEnable = False; Lighting = False; CullMode = CW; TexCoordIndex[0] = 0; TexCoordIndex[1] = 1; TextureTransformFlags[0] = Projected; TextureTransformFlags[1] = Projected; } } technique UseHardwareShadowMapGoodNoRot { pass P0 { VertexShader = compile vs_3_0 RenderSceneVS(); PixelShader = compile ps_3_0 FilterShadowPS(); ZEnable = True; AlphaBlendEnable = False; Lighting = False; CullMode = CW; TexCoordIndex[0] = 0; TexCoordIndex[1] = 1; //TextureTransformFlags[0] = Projected; //TextureTransformFlags[1] = Projected; } } technique UseHardwareShadowMapLame { pass P0 { VertexShader = compile vs_3_0 RenderSceneVS(); PixelShader = compile ps_3_0 FilterShadowLamePS4x4(); ZEnable = True; AlphaBlendEnable = False; Lighting = False; CullMode = CW; TexCoordIndex[0] = 0; TexCoordIndex[1] = 1; //TextureTransformFlags[0] = Projected; //TextureTransformFlags[1] = Projected; } } technique GenHardwareShadowMap { pass P0 { VertexShader = compile vs_1_1 MainVS(); PixelShader = compile ps_1_1 BlackPS(); ZEnable = True; AlphaBlendEnable = False; Lighting = False; CullMode = CCW; // note: not quite optimal ColorWriteEnable = 0; // no need to render to color, we only need z } } //----------------------------------------------------------------------------- //阴影完了,看起来不复杂,但应用到整个场景中还比较麻烦:因为场景的内容可能很多很大,而ShadowMap拉伸太大精度很差.原先按投影锥检索可视物体的方法不能使用了,场景外的物体也会将阴影投到场景中.所以,实际如何做是个问题.