www.pudn.com > D3DShadow.rar > Shadow.cpp
#include#include #include #include "d3dfile.h" #include "d3dutil.h" //----------------------------------------------------------------------------- // External definitions and prototypes //----------------------------------------------------------------------------- inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); } struct VERTEX { D3DXVECTOR3 p; D3DXVECTOR3 n; FLOAT tu, tv; static const DWORD FVF; }; const DWORD VERTEX::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1; struct SHADOWVERTEX { D3DXVECTOR4 p; D3DCOLOR color; static const DWORD FVF; }; const DWORD SHADOWVERTEX::FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE; //----------------------------------------------------------------------------- // Name: struct ShadowVolume // Desc: A shadow volume object //----------------------------------------------------------------------------- class ShadowVolume { D3DXVECTOR3 m_pVertices[32000]; // Vertex data for rendering shadow volume DWORD m_dwNumVertices; public: VOID Reset() { m_dwNumVertices = 0L; } HRESULT BuildFromMesh( LPD3DXMESH pObject, D3DXVECTOR3 vLight ); HRESULT Render( LPDIRECT3DDEVICE9 pd3dDevice ); }; //----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- LPDIRECT3D9 g_pD3D = NULL; // Used to create the D3DDevice LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device LPD3DXMESH g_pMesh = NULL; // Our mesh object in sysmem D3DMATERIAL9* g_pMeshMaterials = NULL; // Materials for our mesh LPDIRECT3DTEXTURE9* g_pMeshTextures = NULL; // Textures for our mesh DWORD g_dwNumMaterials = 0L; // Number of mesh materials CD3DMesh* g_pObjectMesh; CD3DMesh* g_pGroundMesh; D3DXMATRIXA16 g_pObjectMatrix; D3DXMATRIXA16 g_pGroundMatrix; ShadowVolume* g_pShadowVolume; LPDIRECT3DVERTEXBUFFER9 m_pBigSquareVB; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- HRESULT ShadowVolume::Render( LPDIRECT3DDEVICE9 pd3dDevice ) { pd3dDevice->SetFVF( D3DFVF_XYZ ); return pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST, m_dwNumVertices/3, m_pVertices, sizeof(D3DXVECTOR3) ); } //----------------------------------------------------------------------------- // Name: AddEdge() // Desc: Adds an edge to a list of silohuette edges of a shadow volume. //----------------------------------------------------------------------------- VOID AddEdge( WORD* pEdges, DWORD& dwNumEdges, WORD v0, WORD v1 ) { // Remove interior edges (which appear in the list twice) for( DWORD i=0; i < dwNumEdges; i++ ) { if( ( pEdges[2*i+0] == v0 && pEdges[2*i+1] == v1 ) || ( pEdges[2*i+0] == v1 && pEdges[2*i+1] == v0 ) ) { if( dwNumEdges > 1 ) { pEdges[2*i+0] = pEdges[2*(dwNumEdges-1)+0]; pEdges[2*i+1] = pEdges[2*(dwNumEdges-1)+1]; } dwNumEdges--; return; } } pEdges[2*dwNumEdges+0] = v0; pEdges[2*dwNumEdges+1] = v1; dwNumEdges++; } //----------------------------------------------------------------------------- // Name: BuildFromMesh() // Desc: Takes a mesh as input, and uses it to build a shadowvolume. The // technique used considers each triangle of the mesh, and adds it's // edges to a temporary list. The edge list is maintained, such that // only silohuette edges are kept. Finally, the silohuette edges are // extruded to make the shadow volume vertex list. //----------------------------------------------------------------------------- HRESULT ShadowVolume::BuildFromMesh( LPD3DXMESH pMesh, D3DXVECTOR3 vLight ) { // Note: the MESHVERTEX format depends on the FVF of the mesh struct MESHVERTEX { D3DXVECTOR3 p, n; FLOAT tu, tv; }; MESHVERTEX* pVertices; WORD* pIndices; // Lock the geometry buffers pMesh->LockVertexBuffer( 0L, (LPVOID*)&pVertices ); pMesh->LockIndexBuffer( 0L, (LPVOID*)&pIndices ); DWORD dwNumFaces = pMesh->GetNumFaces(); // Allocate a temporary edge list WORD* pEdges = new WORD[dwNumFaces*6]; if( pEdges == NULL ) { pMesh->UnlockVertexBuffer(); pMesh->UnlockIndexBuffer(); return E_OUTOFMEMORY; } DWORD dwNumEdges = 0; // For each face for( DWORD i=0; i = 0.0f ) { AddEdge( pEdges, dwNumEdges, wFace0, wFace1 ); AddEdge( pEdges, dwNumEdges, wFace1, wFace2 ); AddEdge( pEdges, dwNumEdges, wFace2, wFace0 ); } } for( i=0; i UnlockVertexBuffer(); pMesh->UnlockIndexBuffer(); return S_OK; } //----------------------------------------------------------------------------- // Name: RenderShadow() // Desc: //----------------------------------------------------------------------------- HRESULT RenderShadow() { // Disable z-buffer writes (note: z-testing still occurs), and enable the // stencil-buffer g_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, TRUE ); // Dont bother with interpolating color g_pd3dDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT ); // Set up stencil compare fuction, reference value, and masks. // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true. // Note: since we set up the stencil-test to always pass, the STENCILFAIL // renderstate is really not needed. g_pd3dDevice->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); g_pd3dDevice->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); g_pd3dDevice->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); // If ztest passes, inc/decrement stencil buffer value g_pd3dDevice->SetRenderState( D3DRS_STENCILREF, 0x1 ); g_pd3dDevice->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); g_pd3dDevice->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff ); g_pd3dDevice->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); // Make sure that no pixels get drawn to the frame buffer g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); { // Draw front-side of shadow volume in stencil/z only g_pd3dDevice->SetTransform( D3DTS_WORLD, &g_pObjectMatrix ); g_pShadowVolume->Render( g_pd3dDevice ); // Now reverse cull order so back sides of shadow volume are written. g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW ); // Decrement stencil buffer value g_pd3dDevice->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECR ); // Draw back-side of shadow volume in stencil/z only g_pd3dDevice->SetTransform( D3DTS_WORLD, &g_pObjectMatrix ); g_pShadowVolume->Render( g_pd3dDevice ); } // Restore render states g_pd3dDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD ); g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); g_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); return S_OK; } //----------------------------------------------------------------------------- // Name: DrawShadow() // Desc: Draws a big gray polygon over scene according to the mask in the // stencil buffer. (Any pixel with stencil==1 is in the shadow.) //----------------------------------------------------------------------------- HRESULT DrawShadow() { // Set renderstates (disable z-buffering, enable stencil, disable fog, and // turn on alphablending) g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_FOGENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE);//D3DTOP_SELECTARG2);// D3DTOP_MODULATE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); // Only write where stencil val >= 1 (count indicates # of shadows that // overlap that pixel) g_pd3dDevice->SetRenderState( D3DRS_STENCILREF, 0x1 ); g_pd3dDevice->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_LESSEQUAL ); g_pd3dDevice->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); // Draw a big, gray square g_pd3dDevice->SetFVF( SHADOWVERTEX::FVF ); g_pd3dDevice->SetStreamSource( 0, m_pBigSquareVB, 0, sizeof(SHADOWVERTEX) ); g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 ); // Restore render states g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_FOGENABLE, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); return S_OK; } //----------------------------------------------------------------------------- // Name: InitD3D() // Desc: Initializes Direct3D //----------------------------------------------------------------------------- HRESULT InitD3D( HWND hWnd ) { // Create the D3D object. if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) return E_FAIL; // Set up the structure used to create the D3DDevice. Since we are now // using more complex geometry, we will create a device with a zbuffer. D3DPRESENT_PARAMETERS d3dpp; ZeroMemory( &d3dpp, sizeof(d3dpp) ); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // Create the D3DDevice if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice ) ) ) { return E_FAIL; } // Turn on the zbuffer g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE ); // Turn on ambient lighting g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); return S_OK; } //----------------------------------------------------------------------------- // Name: InitGeometry() // Desc: Load the mesh and build the material and texture arrays //----------------------------------------------------------------------------- HRESULT InitGeometry() { g_pObjectMesh= new CD3DMesh(); if( FAILED( g_pObjectMesh->Create( g_pd3dDevice, _T("man.x") ) ) ) return E_FAIL; g_pObjectMesh->SetFVF( g_pd3dDevice,VERTEX::FVF ); g_pObjectMesh->RestoreDeviceObjects( g_pd3dDevice); g_pGroundMesh= new CD3DMesh(); if( FAILED( g_pGroundMesh->Create( g_pd3dDevice, _T("hill.x") ) ) ) return E_FAIL; g_pGroundMesh->SetFVF( g_pd3dDevice, VERTEX::FVF ); g_pGroundMesh->RestoreDeviceObjects( g_pd3dDevice); g_pShadowVolume = new ShadowVolume(); // We need to extract the material properties and texture names from the // pD3DXMtrlBuffer // Create a big square for rendering the stencilbuffer contents if( FAILED( g_pd3dDevice->CreateVertexBuffer( 4*sizeof(SHADOWVERTEX), D3DUSAGE_WRITEONLY, SHADOWVERTEX::FVF, D3DPOOL_MANAGED, &m_pBigSquareVB, NULL ) ) ) return E_FAIL; SHADOWVERTEX* v; FLOAT sx = (FLOAT)580; FLOAT sy = (FLOAT)600; m_pBigSquareVB->Lock( 0, 0, (void**)&v, 0 ); v[0].p = D3DXVECTOR4( 0, sy, 0.0f, 1.0f ); v[1].p = D3DXVECTOR4( 0, 0, 0.0f, 1.0f ); v[2].p = D3DXVECTOR4( sx, sy, 0.0f, 1.0f ); v[3].p = D3DXVECTOR4( sx, 0, 0.0f, 1.0f ); v[0].color = 0x7f000000; v[1].color = 0x7f000000; v[2].color = 0x7f000000; v[3].color = 0x7f000000; m_pBigSquareVB->Unlock(); return S_OK; } VOID SetupLights() { // Set up a material. The material here just has the diffuse and ambient // colors set to yellow. Note that only one material can be used at a time. D3DMATERIAL9 mtrl; ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) ); mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f; mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f; mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f; mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f; g_pd3dDevice->SetMaterial( &mtrl ); //set up lights D3DXVECTOR3 vecDir; D3DLIGHT9 light; ZeroMemory( &light, sizeof(D3DLIGHT9) ); light.Type = D3DLIGHT_DIRECTIONAL; light.Diffuse.r = 1.0f; light.Diffuse.g = 1.0f; light.Diffuse.b = 1.0f; vecDir = D3DXVECTOR3(10,10,-10); D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir ); light.Range = 5000.0f; g_pd3dDevice->SetLight( 0, &light ); g_pd3dDevice->LightEnable( 0, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); // Finally, turn on some ambient light. g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00404040 ); } //----------------------------------------------------------------------------- // Name: Cleanup() // Desc: Releases all previously initialized objects //----------------------------------------------------------------------------- VOID Cleanup() { if (g_pObjectMesh) delete g_pObjectMesh; if (g_pGroundMesh) delete g_pGroundMesh; if(m_pBigSquareVB) m_pBigSquareVB->Release(); if(g_pShadowVolume ) delete g_pShadowVolume ; if( g_pd3dDevice != NULL ) g_pd3dDevice->Release(); if( g_pD3D != NULL ) g_pD3D->Release(); } //----------------------------------------------------------------------------- // Name: SetupMatrices() // Desc: Sets up the world, view, and projection transform matrices. //----------------------------------------------------------------------------- VOID SetupMatrices() { // For our world matrix, we will just leave it as the identity D3DXMATRIXA16 matWorld; D3DXMatrixRotationZ( &g_pObjectMatrix, 0);//timeGetTime()/1000.0f ); // D3DXMatrixTranslation(&g_pObjectMatrix,0,0,10); static float step=100.f; g_pObjectMatrix.m[3][2]=step; D3DXMatrixRotationZ( &g_pGroundMatrix, 0 ); //g_pObjectMatrix //g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); // Set up our view matrix. A view matrix can be defined given an eye point, // a point to lookat, and a direction for which way is up. Here, we set the // eye five units back along the z-axis and up three units, look at the // origin, and define "up" to be in the y-direction. // D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f ); D3DXVECTOR3 vEyePt( 100.0f, -100.0f,100.0f ); D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 42.0f ); D3DXVECTOR3 vUpVec( 0.0f, .0f, 1.0f ); D3DXMATRIXA16 matView; D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec ); g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); // For the projection matrix, we set up a perspective transform (which // transforms geometry from 3D view space to 2D viewport space, with // a perspective divide making objects smaller in the distance). To build // a perpsective transform, we need the field of view (1/4 pi is common), // the aspect ratio, and the near and far clipping planes (which define at // what distances geometry should be no longer be rendered). D3DXMATRIXA16 matProj; //D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 10000.0f ); g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); } void Update() { // Move the light FLOAT Lx = 200*cosf(timeGetTime()/1000.0f); FLOAT Ly = 200*sinf(timeGetTime()/1000.0f); FLOAT Lz = 400; D3DLIGHT9 light; D3DUtil_InitLight( light, D3DLIGHT_POINT, Lx, Ly, Lz ); light.Attenuation0 = 0.9f; light.Attenuation1 = 0.0f; g_pd3dDevice->SetLight( 0, &light ); // Transform the light vector to be in object space D3DXVECTOR3 vLight; D3DXMATRIXA16 m; D3DXMatrixInverse( &m, NULL, &g_pObjectMatrix);//m_matObjectMatrix ); vLight.x = Lx*m._11 + Ly*m._21 + Lz*m._31 + m._41; vLight.y = Lx*m._12 + Ly*m._22 + Lz*m._32 + m._42; vLight.z = Lx*m._13 + Ly*m._23 + Lz*m._33 + m._43; // Build the shadow volume g_pShadowVolume->Reset(); g_pShadowVolume->BuildFromMesh( g_pObjectMesh->GetSysMemMesh(), vLight ); g_pd3dDevice->LightEnable( 0, TRUE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); // Finally, turn on some ambient light. g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00a0a0a0 ); } //----------------------------------------------------------------------------- // Name: Render() // Desc: Draws the scene //----------------------------------------------------------------------------- VOID Render() { // Clear the backbuffer and the zbuffer g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); // Begin the scene if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) { // Setup the world, view, and projection matrices SetupMatrices(); // SetupLights(); Update(); // Meshes are divided into subsets, one for each material. Render them in // a loop g_pd3dDevice->SetTransform( D3DTS_WORLD, &g_pGroundMatrix ); g_pGroundMesh->Render(g_pd3dDevice); g_pd3dDevice->SetTransform( D3DTS_WORLD, &g_pObjectMatrix ); g_pObjectMesh->Render(g_pd3dDevice); RenderShadow(); DrawShadow(); // End the scene g_pd3dDevice->EndScene(); } // Present the backbuffer contents to the display g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); } //----------------------------------------------------------------------------- // Name: MsgProc() // Desc: The window's message handler //----------------------------------------------------------------------------- LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_DESTROY: Cleanup(); PostQuitMessage( 0 ); return 0; } return DefWindowProc( hWnd, msg, wParam, lParam ); } //----------------------------------------------------------------------------- // Name: WinMain() // Desc: The application's entry point //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { // Register the window class WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "D3D Tutorial", NULL }; RegisterClassEx( &wc ); // Create the application's window HWND hWnd = CreateWindow( "D3D Tutorial", "D3D Tutorial :shadow volume", WS_OVERLAPPEDWINDOW, 0, 0, 580, 600, GetDesktopWindow(), NULL, wc.hInstance, NULL ); // Initialize Direct3D if( SUCCEEDED( InitD3D( hWnd ) ) ) { // Create the scene geometry if( SUCCEEDED( InitGeometry() ) ) { // Show the window ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd ); // Enter the message loop MSG msg; ZeroMemory( &msg, sizeof(msg) ); while( msg.message!=WM_QUIT ) { if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } else Render(); } } } UnregisterClass( "D3D Tutorial", wc.hInstance ); return 0; }