Occlusion Culling Using DirectX 9
The CodeType DeclarationsSObject (Code Listing 4.1) is the main object entity. CMesh is a class that encapsulates the loading, rendering, and release of an ID3DXMesh interface. struct SObject { CMesh* meshReference; // Reference to a mesh object CMesh* boundingMesh; // Reference to low-poly bounding mesh D3DXVECTOR3 pos; // Position of this object D3DXMATRIX matTranslate; // Translation matrix for this object bool render; // If true, render the object float distanceToCamera; // The distance to the camera (player position) // Constructor SObject( CMesh* meshRef, CMesh* boundMesh, D3DXVECTOR3 position ) { meshReference = meshRef; boundingMesh = boundMesh; pos = position; render = false; distanceToCamera = 0.0f; } }; Object DeclarationFor the occlusion process, interfaces of LPDIRECT3DQUERY9, LPD3DXRENDERTOSURFACE, LPDIRECT3DSURFACE9, and LPDIRECT3DTEXTURE9 need to be declared. LPDIRECT3D9 d3dObject; // Direct3D Object LPDIRECT3DDEVICE9 d3dDevice; // Direct3D Device LPDIRECT3DQUERY9 d3dQuery; // The occlusion query LPD3DXRENDERTOSURFACE occlusionRender; // Occlusion's render to surface LPDIRECT3DSURFACE9 occlusionSurface; // Occlusion's surface that it uses LPDIRECT3DTEXTURE9 occlusionTexture; // Texture to get surface from std::vector<SObject> objects; // Vector of objects Setting up the Occlusion ObjectsThe query itself must be created, along with the texture and the render-to-surface. D3DUSAGE_RENDERTARGET is used during the creation of the texture, since it will be rendered to. The surface itself is obtained through the GetSurfaceLevel() function of LPDIRECT3DTEXTURE9. A Z-buffer format of D3DFMT_D16 is used for the LPD3DXRENDERTOSURFACE interface, as it will be needed for use. //----------------------------------------------------------------------------- // Name: SetupOcclusion() // Desc: Create the objects needed for the occlusion culling //----------------------------------------------------------------------------- HRESULT SetupOcclusion() { // Create the query d3dDevice->CreateQuery( D3DQUERYTYPE_OCCLUSION, &d3dQuery ); // Get the display mode to obtain the format D3DDISPLAYMODE mode; d3dDevice->GetDisplayMode( 0, &mode ); // Create the texture first, so we can get access to it's surface if( FAILED( D3DXCreateTexture( d3dDevice, 320, 240, 1, D3DUSAGE_RENDERTARGET, mode.Format, D3DPOOL_DEFAULT, &occlusionTexture ) ) ) { return E_FAIL; } // Obtain the surface (what we really need) D3DSURFACE_DESC desc; occlusionTexture->GetSurfaceLevel(0, &occlusionSurface); occlusionSurface->GetDesc(&desc); // Create the render to surface if( FAILED( D3DXCreateRenderToSurface( d3dDevice, desc.Width, desc.Height, desc.Format, TRUE, D3DFMT_D16, &occlusionRender ) ) ) { return E_FAIL; } return S_OK; } Culling the ObjectsThe OcclusionCull() function implements the theory presented earlier. First, the LPD3DXRENDERTOSURFACE is activated and cleared. Second, every object's bounding mesh is rendered. The meshes are re-rendered and then their occlusion queries are retrieved. Finally, the surface's scene is ended and deactivated. //----------------------------------------------------------------------------- // Name: OcclusionCull() // Desc: Cull the objects //----------------------------------------------------------------------------- HRESULT OcclusionCull() { // Begin occlusionRender if( SUCCEEDED( occlusionRender->BeginScene( occlusionSurface, NULL ) ) ) { // Clear the occlusionRender's surface d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 200, 200, 200 ), 1.0f, 0); // First, render every object's bounding box for(int i = 0; i < objects.size(); i++ ) { objects[i].boundingMesh->Render( d3dDevice, objects[i].matTranslate ); } // Now, render each box again, except this time, count how many pixels are visible // by using an occlusion query. We are guaranteed to get the right amount, // since all the bounding boxes have already been rendered for( int i = 0; i < objects.size(); i++ ) { // Start the query d3dQuery->Issue( D3DISSUE_BEGIN ); // Render objects[i].boundingMesh->Render( d3dDevice, objects[i].matTranslate ); // End the query, get the data d3dQuery->Issue( D3DISSUE_END ); // Loop until the data becomes available DWORD pixelsVisible = 0; while (d3dQuery->GetData((void *) &pixelsVisible, sizeof(DWORD), D3DGETDATA_FLUSH) == S_FALSE); if( pixelsVisible == 0 ) objects[i].render = false; // No pixels visible, do not render else objects[i].render = true; // Pixels visible, render } // End the occlusion render scene occlusionRender->EndScene( 0 ); // User is pressing the 'M' key, save this buffer to .BMP file if( keys['M'] ) D3DXSaveSurfaceToFile( "buffer.bmp", D3DXIFF_BMP, occlusionSurface, NULL, NULL ); } return S_OK; } The Render LoopThe render loop consists of building all the matrices, including the camera and object transforms, then culling. Finally, the objects that will be visible in the final scene are rendered. //----------------------------------------------------------------------------- // Name: Render // Desc: Render a frame //----------------------------------------------------------------------------- HRESULT Render() { // Setup the matrices for this frame SetupMatrices(); // Cull the objects OcclusionCull(); if( SUCCEEDED( d3dDevice->BeginScene() ) ) { // Clear the main device d3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255,0,0), 1.0f, 0 ); // Render the appropriate objects // Leave out objects that are occluded for( int i = 0; i < objects.size(); i++ ) { if( objects[i].render ) { objects[i].meshReference->Render( d3dDevice, objects[i].matTranslate ); } } d3dDevice->EndScene(); } // Present the scene d3dDevice->Present( NULL, NULL, NULL, NULL ); return S_OK; } |
|