对象剔除是图形编程中一个重要的方面。渲染那些看不见的物体是十分耗时的。然而,就目前来说当务之急是优化剔除算法本身。因为通常剔除算法会消耗大量的处理时间。 传统的算法对于一般情况的处理已经提升得很快了也改进得足够多了,但还是留下了些许遗憾。一些算法剔除了太多的物体,而另一些剔除的数量却不够多。 遮盖剔除的原理告诉我们一个事实,那就是一个物体即便在视锥内,仍有可能被遮住而看不见。 ![](http://images.gamedev.net/features/programming/occlusionculling/scene.jpg)
![](http://images.gamedev.net/features/programming/occlusionculling/render.jpg)
这里,如图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,此物体需要进行实际的渲染。否则物体将被排除。 第一步: 对于遮盖测试物体的实际网格包含了太多的顶点,而包围网格的顶点数要少得多,它将被用作替代物。那为什么用包围网格而不是包围盒或包围球呢? ![](http://images.gamedev.net/features/programming/occlusionculling/Bounding%20Types.jpg) 图3.1各种类型的包围体
图3.1展示了多种类型的包围体,包括盒体、球体和网格。注意在这个特定的例子中包围网格和包围球的顶点数目是一样的。可是,即使顶点数目比较接近,在体积的接近程度上,包围网格的体积上和原始网格的的体积是最接近的。这在遮盖处理中很重要,要不大量的顶点会错误地渲染或剔除掉。 然而包围网格不能像包围盒或包围球那样通过某个算法来产生。它需要实时地建模和加载,就象普通的网格一样。每一个对象的包围网格先渲染一次以确保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.boundingMesh->Render( d3dDevice, objects.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.boundingMesh->Render( d3dDevice, objects.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.render = false; // No pixels visible, do not render else objects.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.render ) { objects.meshReference->Render( d3dDevice, objects.matTranslate ); } } d3dDevice->EndScene(); } // Present the scene d3dDevice->Present( NULL, NULL, NULL, NULL ); return S_OK; } Code Listing 4.5: The simplified render loop |