3D游戏技术 - 大型3D地图优化渲染技术


目录(?)[+]

技术简介:

如果需要渲染一个大型3D地图,由于数据量,需要渲染的东西非常多,所以尤其一些慢一点的机器就会变得非常卡。

如下面这些会造成帧率(FPS)下降的图:

这样的图:

3D游戏技术 - 大型3D地图优化渲染技术_第1张图片

还有这样的魔兽世界的图:

3D游戏技术 - 大型3D地图优化渲染技术_第2张图片

还有这样的图:

3D游戏技术 - 大型3D地图优化渲染技术_第3张图片

上图我是指她的背景图啦,O(∩_∩)O~

 

到底暴雪是如何让这些场景流畅地渲染的呢?

那就必须想办法提高渲染速度,也就是帧率(FPS)要提高,才能使得游戏流畅。

像魔兽这样大型的游戏具体是用什么技术的,那不敢确定,但是会用到的相关技术会有:

1 地图分块剔除

2 kd树

3 BSP树

4 LOD技术

等等,最后把场景优化到极致,才能创造出伟大的游戏。每一种技术都需要挺长篇幅介绍的,本文就分析第一种技术:地图分块剔除技术(Sub-Grid Culling)

地图分块剔除技术分析

1 3D投影剔除(Frustum Culling)

把一个大地图分隔成由多个小地图实现,那么本来需要调用一次就画成的地图,现在需要多次调用,但是我们可以利用Frustum Culling(3D投影剔除-我觉得比较满意的翻译名词吧)技术优化渲染。

Frustum Culling的思路是:

 每一个小块地图,我们计算相对应的AABB包围体;

如果这个AABB和Frustum不相交,那么就不用画这个小块地图。

Frustum是下面的形状:

3D游戏技术 - 大型3D地图优化渲染技术_第4张图片

如下图:大三角形代表Frustum,小方格代表小块地图,灰色小方格代表与Frustum相交,需要渲染,但是白色地带就不与Frustum相交,所以都不需要渲染,那么我们可以看出这就省了很多渲染内容,也就速度大大提高了。

3D游戏技术 - 大型3D地图优化渲染技术_第5张图片

也可以不使用分格和AABB方法进行Frustum Culling:

把所有的组成地图的三角形输入显卡,让显卡自动裁剪不在Frustum中的三角形。但是其实这个裁剪发生在Clipping Stage,就差不多是渲染管道的最后的渲染工作了,那么这些不用渲染你的三角形就会通过大部分渲染管道,如:Vertex Shader,那么就浪费了很多计算呢时间。

使用分格和AABB的方法就可以简单的利用一个AABB相交测试就可以丢弃一个不用渲染的小块地图数据了,效率可以更大地提高。

掌握这个分块的数量也很重要。如下图:

3D游戏技术 - 大型3D地图优化渲染技术_第6张图片

这样就是更加精细的分块了。需要渲染的面积更小,但是相对而言节省的也不多,而精细的分块也需要花费更多的计算时间,如:画基本元几何图形的函数调用次数也更多了,还有AABB相交测试也增多;

所谓过犹不及,要掌握好度很重要。到底分多少个小地图块是合适的,就需要实际分析了。

而且这里的Frustum Culling主要是节省了vertex shading的计算时间,如果一个图形是主要花费在pixel shanding上的话,比如大量的particles需要渲染,那么这种Frustum Culling方法就不会有太大的提速效果。

2 小地图数据结构体:

首先需要定义一个结构体来表示小地图块的数据,如下:

[cpp] view plain copy print ?
  1. struct SubGrid  
  2.     {  
  3.         ID3DXMesh* mesh;  
  4.         AABB box;  
  5.   
  6.         // For sorting.  
  7.         bool operator<(const SubGrid& rhs)const;  
  8.   
  9.         const static int NUM_ROWS  = 33;  
  10.         const static int NUM_COLS  = 33;  
  11.         const static int NUM_TRIS  = (NUM_ROWS-1)*(NUM_COLS-1)*2;  
  12.         const static int NUM_VERTS = NUM_ROWS*NUM_COLS;  
  13.     };  

主要功能就是这个比较操作符,是根据该小地图块和Camera的距离来比较大小的。把所有的小地图块都按照离Camera的距离由进到远排序,那么就方便由进到远(前到后)的渲染方式渲染。这种方式的渲染可以提高速度,尤其是在渲染管道中pixel shader渲染是瓶颈的时候,当然如果vertex shader是瓶颈,那么就提高不明显。

基本原理就是:

1 更新像素(pixel)的时候,渲染器都进行了depth testing(深度测试),只有在距离镜头进的物体才会被渲染,否则就不渲染。

2 如果排序好的物体,前面渲染的物体都是距离近的,那么后面渲染的物体如果被前面的物体挡住了,那么就根本不需要渲染了。

3 如果不是排序好的话,前面渲染了距离远的物体,那么后面渲染的物体距离镜头更加近,就需要重新更新当前像素,造成重复浪费渲染。

[cpp] view plain copy print ?
  1. bool Terrain::SubGrid::operator<(const SubGrid& rhs) const  
  2. {  
  3.       D3DXVECTOR3 d1 = box.center() - gCamera->pos();  
  4.       D3DXVECTOR3 d2 = rhs.box.center() - gCamera->pos();  
  5.       return D3DXVec3LengthSq(&d1) < D3DXVec3LengthSq(&d2);  
  6. }  


 

3 计算出Frustum:

Frustum是从投影矩阵抽出来的。因为Frustum就是由投影矩阵定义的,也可以说两者都在实际上是一致的:

如投影示意图:

3D游戏技术 - 大型3D地图优化渲染技术_第7张图片

Frustum:

3D游戏技术 - 大型3D地图优化渲染技术_第8张图片

其实是概念上不一样,但是实际上一样的东西。不过应用也不一样。

下面是从投影矩阵抽出Frustum的程序:

[cpp] view plain copy print ?
  1. void Camera::buildWorldFrustumPlanes()  
  2. {  
  3.     // Note: Extract the frustum planes in world space.  
  4.   
  5.     D3DXMATRIX VP = mView * mProj;  
  6.   
  7.     D3DXVECTOR4 col0(VP(0,0), VP(1,0), VP(2,0), VP(3,0));  
  8.     D3DXVECTOR4 col1(VP(0,1), VP(1,1), VP(2,1), VP(3,1));  
  9.     D3DXVECTOR4 col2(VP(0,2), VP(1,2), VP(2,2), VP(3,2));  
  10.     D3DXVECTOR4 col3(VP(0,3), VP(1,3), VP(2,3), VP(3,3));  
  11.   
  12.     // Planes face inward.  
  13.     mFrustumPlanes[0] = (D3DXPLANE)(col2);        // near  
  14.     mFrustumPlanes[1] = (D3DXPLANE)(col3 - col2); // far  
  15.     mFrustumPlanes[2] = (D3DXPLANE)(col3 + col0); // left  
  16.     mFrustumPlanes[3] = (D3DXPLANE)(col3 - col0); // right  
  17.     mFrustumPlanes[4] = (D3DXPLANE)(col3 - col1); // top  
  18.     mFrustumPlanes[5] = (D3DXPLANE)(col3 + col1); // bottom  
  19.   
  20.     for(int i = 0; i < 6; i++)  
  21.         D3DXPlaneNormalize(&mFrustumPlanes[i], &mFrustumPlanes[i]);  
  22. }  

其中的数学原理却是比较复杂的,需要相当的图形学基础。

4 Frustum和AABB包围体的碰撞检测

下面是检测Frustum六个平面与AABB包围体碰撞的代码:

[cpp] view plain copy print ?
  1. bool Camera::isVisible(const AABB& box)const  
  2. {  
  3.     // Test assumes frustum planes face inward.  
  4.   
  5.     D3DXVECTOR3 P;  
  6.     D3DXVECTOR3 Q;  
  7.   
  8.     //      N  *Q                    *P  
  9.     //      | /                     /  
  10.     //      |/                     /  
  11.     // -----/----- Plane     -----/----- Plane      
  12.     //     /                     / |  
  13.     //    /                     /  |  
  14.     //   *P                    *Q  N  
  15.     //  
  16.     // PQ forms diagonal most closely aligned with plane normal.  
  17.   
  18.     // For each frustum plane, find the box diagonal (there are four main  
  19.     // diagonals that intersect the box center point) that points in the  
  20.     // same direction as the normal along each axis (i.e., the diagonal   
  21.     // that is most aligned with the plane normal).  Then test if the box  
  22.     // is in front of the plane or not.  
  23.     for(int i = 0; i < 6; ++i)  
  24.     {  
  25.         // For each coordinate axis x, y, z...  
  26.         for(int j = 0; j < 3; ++j)  
  27.         {  
  28.             // Make PQ point in the same direction as the plane normal on this axis.  
  29.             if( mFrustumPlanes[i][j] >= 0.0f )  
  30.             {  
  31.                 P[j] = box.minPt[j];  
  32.                 Q[j] = box.maxPt[j];  
  33.             }  
  34.             else   
  35.             {  
  36.                 P[j] = box.maxPt[j];  
  37.                 Q[j] = box.minPt[j];  
  38.             }  
  39.         }  
  40.   
  41.         // If box is in negative half space, it is behind the plane, and thus, completely  
  42.         // outside the frustum.  Note that because PQ points roughly in the direction of the   
  43.         // plane normal, we can deduce that if Q is outside then P is also outside--thus we  
  44.         // only need to test Q.  
  45.         if( D3DXPlaneDotCoord(&mFrustumPlanes[i], &Q) < 0.0f  ) // outside  
  46.             return false;  
  47.     }  
  48.     return true;  
  49. }  

因为Frustum有六个平面,所以要循环检测六次,只要任何一次AABB包围体是在平面负面的,那么就返回false,表示没有在Frustum内。

这样检测可以不用考虑这些平面并不是无限延伸的,而是当作一般平面来检测,简化了计算。

因为每个平面四面都有被其他平面包围着,如果AABB与该平面在Frustum外相交,那么AABB就会是在其他四个平面任一个平面的负面,所以会在和其他平面检测的时候,检测出来该AABB不在Frustum内。

可以参考我另外一个关于平面和AABB包围体碰撞检测的博客:http://blog.csdn.net/kenden23/article/details/16916327

5 最后就是渲染:

[cpp] view plain copy print ?
  1. void Terrain::draw()  
  2. {  
  3.     // Frustum cull sub-grids.  
  4.     std::list<SubGrid> visibleSubGrids;  
  5.     for(UINT i = 0; i < mSubGrids.size(); ++i)  
  6.     {  
  7.         if( gCamera->isVisible(mSubGrids[i].box) )  
  8.             visibleSubGrids.push_back(mSubGrids[i]);  
  9.     }  
  10.   
  11.     // Sort front-to-back from camera.  
  12.     visibleSubGrids.sort();  
  13.   
  14.     mFX->SetMatrix(mhViewProj, &gCamera->viewProj());  
  15.     mFX->SetTechnique(mhTech);  
  16.     UINT numPasses = 0;  
  17.     mFX->Begin(&numPasses, 0);  
  18.     mFX->BeginPass(0);  
  19.   
  20.     for(std::list<SubGrid>::iterator iter = visibleSubGrids.begin(); iter != visibleSubGrids.end(); ++iter)  
  21.         HR(iter->mesh->DrawSubset(0));  
  22.   
  23.     mFX->EndPass();  
  24.     mFX->End();  
  25. }  

 

渲染步骤:

1  先检测是否可见,即是否与Frustum相交,相交的放入一个list容器中。

[cpp] view plain copy print ?
  1. std::list<SubGrid> visibleSubGrids;  
  2.     for(UINT i = 0; i < mSubGrids.size(); ++i)  
  3.     {  
  4.         if( gCamera->isVisible(mSubGrids[i].box) )  
  5.             visibleSubGrids.push_back(mSubGrids[i]);  
  6.     }  


2 按照离镜头的远近排序:

[cpp] view plain copy print ?
  1. // Sort front-to-back from camera.  
  2.     visibleSubGrids.sort();  

3 最后就是逐个小地图块渲染

[cpp] view plain copy print ?
  1. for(std::list<SubGrid>::iterator iter = visibleSubGrids.begin(); iter != visibleSubGrids.end(); ++iter)  
  2.         HR(iter->mesh->DrawSubset(0));  


其他代码是Shader渲染设置。

6 实现地图分块剔除技术关键步骤总结:

1 地图分块,定义好保存数据的结构体,包含AABB包围体

2 根据投影矩阵,计算Frustum

3 检测Frustum和小地图块的AABB包围体是否碰撞,保存好碰撞的小地图块

4 根据离镜头远近排序可见的(即碰撞的)小地图块,按循序渲染

 

下面是应用了本技术的效果图:

3D游戏技术 - 大型3D地图优化渲染技术_第9张图片

3D游戏技术 - 大型3D地图优化渲染技术_第10张图片

你可能感兴趣的:(3D游戏技术 - 大型3D地图优化渲染技术)