使用DirectX9進行遮蓋剔除

看到一篇講述遮擋剔除的文章,寫的不錯,轉帖地址:http://bbs.gameres.com/showthread.asp?threadid=11409

對象剔除是圖形編程中一個重要的方面。渲染那些看不見的物體是十分耗時的。然而,就目前來說當務之急是優化剔除算法本身。因爲通常剔除算法會消耗大量的處理時間。

傳統的算法對於一般情況的處理已經提升得很快了也改進得足夠多了,但還是留下了些許遺憾。一些算法剔除了太多的物體,而另一些剔除的數量卻不夠多。

遮蓋剔除的原理告訴我們一個事實,那就是一個物體即便在視錐內,仍有可能被遮住而看不見。

         

                                           圖1.1                                                      圖1.2

這裏,如圖1.1所示,有五個多邊形被顯示在屏幕上。然而,在最後的渲染結果中(如圖1.2所示),實際只有三個物體可以看得見。簡單的視錐剔除算法會導致所有的物體都被渲染,原因是它們都在視錐內。就象在圖1.1中一樣,即使另外兩個物體完全被遮住,它們仍會被渲染,這將耗費許多時間。

遮蓋剔除通常會判斷哪個物體會真正看得見,只有這些物體纔會被渲染,從而節省了大量的時間。遮蓋物(occluder)就是那些以遮住其它物體的東西(比如:圖1.1中的那個巨大的紅色盒子)。部分被遮蓋的物體就是那些只能部分看見的物體(藍色的五邊形和紫色的楔形),這是需要被渲染的。完全被遮的物體就是完全看不見的物體(綠色的球體和桔紅色的盒子),它們在渲染過程中將被排除掉。

關於遮蓋剔除的更多信息,請參看由Tomas Möller 和 Eric Haines寫的Occlusion

Culling Algorithms地(Occlusion Culling Algorithms

介紹IDirect3DQuery9

IDirect3DQuery9 接口是Directx9的一個新特性。它允許開發人員能訪問大量的統計數據,這其中包括優化信息、由資源管理器所處理的對象以及三角形處理信息。

IDirect3DQuery9 還能執行遮蓋請求,它將計算在屏幕上可見的象素數目。只有在請求開始和請求結束之間渲染的象素纔會被計算在內。如果計算的結果爲0,那麼說明物體的所有頂點都被遮住了,這個物體就當前攝像機所處的位置來說是看不見的。另外,如果計算的結果是大於0的,那麼物體是可見的。

Query Type Datatype Use
D3DQUERYTYPE_VCACHE D3DDEVINFO_VCACHE Information about optimization,

pertaining to data layout for vertex caching
D3DQUERYTYPE_RESOURCEMANAGER D3DDEVINFO_RESOURCEMANAGER Number of objects

sent, created, evicted, and managed in video memory
D3DQUERYTYPE_VERTEXSTATS D3DDEVINFO_D3DVERTEXSTATS Number of triangles that

have been processed and clipped
D3DQUERYTYPE_EVENT bool For any and all asynchronous events issued from API

calls
D3DQUERYTYPE_OCCLUSION DWORD The number of pixels that pass Z-testing, or

are visible on-screen.
Table 2.1: Uses of IDirect3DQuery9

ATI的Occlusion Query(http://www.ati.com/developer/samples/dx9/OcclusionQuery.html)演示給出了使用IDirect3DQuery9的基礎。

使用 DirectX9進行遮蓋剔除

IDirect3DQuery9的出現提供了一個簡單而有效的遮蓋剔除的方法。基本過程如下:

1. 渲染各物體的包圍網格(bounding mesh)。
2. 對每一個物體執行如下操作:
  A。開始詢問。
  B。再一次渲染各物體的包圍盒。
  C。終止詢問。
  D。獲取遮蓋詢問的數據。如果物體可見那麼數據會大於0,此物體需要進行實際的渲染。否則物體將被排除。

第一步:
  對於遮蓋測試物體的實際網格包含了太多的頂點,而包圍網格的頂點數要少得多,它將被用作替代物。那爲什麼用包圍網格而不是包圍盒或包圍球呢?

                               圖 1.3

圖1.3展示了多種類型的包圍體,包括盒體、球體和網格。注意在這個特定的例子中包圍網格和包圍球的頂點數目是一樣的。可是,即使頂點數目比較接近,在體積的接近程度上,包圍網格的體積上和原始網格的的體積是最接近的。這在遮蓋處理中很重要,要不大量的頂點會錯誤地渲染或剔除掉。

然而包圍網格不能像包圍盒或包圍球那樣通過某個算法來產生。它需要實時地建模和加載,就象普通的網格一樣。每一個對象的包圍網格先渲染一次以確保Z-buffer中有整個場景的深度信息。如果遮蓋詢問在整個場景的深度信息加入Z-buffer之前就開始了,那麼即使被要求詢問的物體在最終的場景裏完全被遮擋住,可能也會被錯誤地認爲是可見的。

第二步:
  現在所有物體的包圍網格深度數據已經在Z-buffer中了,我們還要再做一次同樣的事,這一次遮蓋詢問將被用來判斷每個物體的可視狀態。如果詢問後發現沒有可見的象素,這個物體最後將不會被渲染。如果發現有一個或多個可見的象素點,那這個物體就會被加入到渲染處理過程裏。

  需要重點注意的是遮蓋剔除的渲染不應在全尺寸的主緩衝裏進行。用一個小些的緩衝(320 X 240可以很好地工作)可以提升效率。

代碼

類型聲明

Sobject(代碼表4.1)是主對象實體。CMesh是一個封裝了加載、渲染和釋放ID3DXMesh接口的類。

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

對象聲明

遮蓋剔除的處理過程中,我們還需要聲明LPDIRECT3DQUERY9, LPD3DXRENDERTOSURFACE, LPDIRECT3DSURFACE9, 和 LPDIRECT3DTEXTURE9

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

設置遮蓋對象

首先詢問要被創建,此外還有紋理和要渲染的緩衝。在創建紋理過程裏我們要用到D3DUSAGE_RENDERTARGET ,因爲我們要在其上進行渲染。而緩衝通過LPDIRECT3DTEXTURE9 中的GetSurfaceLevel()來獲得。我們還要用LPD3DXRENDERTOSURFACE 接口把Z-buffer的格式值設置成D3DFMT_D16。

//-----------------------------------------------------------------------------
// 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

剔除物體

OcclusionCull()函數實現了先前提到的算法。首先,D3DXRENDERTOSURFACE 被激活

並被清空。接下來渲染對象的包圍網格。再次渲染包圍網格並獲取遮蓋詢問數據。最後緩衝中的場景被中止並使其無效(即不實際渲染到屏幕)。

//-----------------------------------------------------------------------------
// 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

渲染循環

渲染循環包含創建所有資源(包括攝像機和轉換矩陣)和物體的剔除。最後在最終場景中可視的物體將會被渲染。

//-----------------------------------------------------------------------------
// 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

結論:
  結論我就不譯了,想了解的話參看一下原文http://gamedev.net/reference/programming/features/occlusionculling/page3.asp。裏面還有原代碼可以下載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章