Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
112 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Occlusion Culling Using DirectX 9


The Code

Type Declarations

SObject (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;
    }
};
Code Listing 4.1: SObject definition

Object Declaration

For 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

Code Listing 4.2: Declarations of objects pertaining to the occlusion culling procedure

Setting up the Occlusion Objects

The 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;
}
Code Listing 4.3: The SetupOcclusion() function

Culling the Objects

The 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;
}
Code Listing 4.4: The OcclusionCull() function

The Render Loop

The 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;
}
Code Listing 4.5: The simplified render loop




Conclusion


Contents
  Introduction
  The Code
  Conclusion

  Source code
  Printable version
  Discuss this article