Nvidia SDK9.1 HardwareShadowMap 阴影程序注释

 

//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拉伸太大精度很差.原先按投影锥检索可视物体的方法不能使用了,场景外的物体也会将阴影投到场景中.所以,实际如何做是个问题.


 

你可能感兴趣的:(Nvidia SDK9.1 HardwareShadowMap 阴影程序注释)