eXtreme3D

导航

统计

Half Life2 Displacement Terrain System

   HL2的地形系统是一个另类的地形系统,它没有使用当前流行的heightmap技术,而是完全使用displacement map技术来进行处理,使用displacement map技术的初衷是为了在框架中和BSP技术保持一致,这是因为在BSP中最小的渲染图元是surface,surface的限制是所有的顶点必须位于同一个平面上,实际上从理论上来说在HL2这样的引擎中也可以使用heightmap来构造地形,那就是将heightmap数据也分块保存,但是当前流行的地形技术使用的都是一个整张heightmap,而且大量的LOD算法都是基于这个基础上的。使用displacement map的另外一个好处是可以构造更加复杂的地形,这是因为它存在法线的缘故。同时使用displacement map还有一个好处是在将来,因为当前MS已经对displacement map技术进行了支持,现在的问题是GPU在硬件上还不支持,如果一旦硬件支持的话,就有可能将整个地形由GPU来处理,那么对地形渲染的速度和精度会有非常大的提高。
1、what’s Displacement Map
      displacemtn map技术是最近几年才出现的一个技术,它出现的初衷是为了解决内存和GPU之间的带宽问题,它开始主要用于高poly角色模型的渲染,由于模型的高数据量会使内存和GPU之间的传输成为瓶颈,因此将其转换为低poly模型和一张displacement传送到GPU中,在GPU中转换为高poly模型进行渲染,可以大大减轻传送的数据量。
但是当前这项技术已经获得非常大的发展,它使用的范围非常大,从室外地形、水面、海洋到室内场景的渲染,它为提高场景的真实度提供了一个有力的工具。一个displacement map通常包含两部分:一张normal map用来保存法线,另一张height map用来保存顶点的偏移。当需要从低poly模型转换为高poly模型时很简单,首先从模型的一个face上获得一个插值点P1,然后从displacement map中获得相应位置的normal和distance,那么新的顶点P2为:
P2 = P1 + normal*distance
关于displacement map的参考文章请看shaderX2中的Displacement Mapping。
2、HL2 Displacement Map Rule
   在HL2中displacement map信息被保存在BSP文件中,它有以下几个数据块:
LUMP_DISP_VERTS  保存displacement真实顶点信息
LUMP_DISP_TRIS   保存displacement三角形标识符信息(walkable or buildable)
LUMP_DISPINF     保存displacement 的连接信息
它们保持的信息结构如下:
LUMP_DISP_VERTS:
class CDispVert
{
public:
Vector m_vVector;// Vector field defining displacement volume.
float m_flDist; // Displacement distances.
float m_flAlpha;// "per vertex" alpha values.
};
LUMP_DISP_TRIS:
class CDispTri
{
public:
unsigned short m_uiTags; // Displacement triangle tags.
};
LUMP_DISPINF:
class ddispinfo_t
{
public:
int NumVerts() const{ return NUM_DISP_POWER_VERTS(power); }
intNumTris() const{ return NUM_DISP_POWER_TRIS(power); }

 

public:
Vector startPosition;// start position used for //orientation -- (added BSPVERSION 6)
int m_iDispVertStart;// Index into LUMP_DISP_VERTS.
int m_iDispTriStart;// Index into LUMP_DISP_TRIS.

int power; // power - indicates size of map //(2^power + 1)
int minTess; // minimum tesselation allowed
float smoothingAngle;   // lighting smoothing angle
int contents;        // surface contents
unsigned short m_iMapFace; // Which map face this
                                        //displacement comes from.
int m_iLightmapAlphaStart;// Index into ddisplightmapalpha.

int m_iLightmapSamplePositionStart;// Index into.
                            // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS   
CDispNeighbor m_EdgeNeighbors[4];// Indexed by NEIGHBOREDGE_ defines.
CDispCornerNeighbors m_CornerNeighbors[4]; // Indexed by CORNER_ defines.

enum { ALLOWEDVERTS_SIZE = PAD_NUMBER( MAX_DISPVERTS, 32 ) / 32 };
unsigned long m_AllowedVerts[ALLOWEDVERTS_SIZE]; //
// This is built based on the layout and 
//sizes of our neighbors
// and tells us which
 //vertices are allowed to be active.
};

在HL2中规定一个displacement最大由17*17个顶点组成,每一个displacement都将左下角看作是自身坐标系的原点,并按照顺时针方向进行记数。如下:


在ddispinfo_t类中我们可以看到为了记录displacement的邻接关系,它使用了两个数组,m_EdgeNeighbors记录四条边的邻接关系,而m_CornerNeighbors记录了四个角的邻接关系。必须注意的是在HL2中邻接的displacement并不一定都是一样大的,也就是说邻接的displacement不一定是一个,也可以是两个,如果邻接的为两个的话那么邻接的displacement的大小必须小一倍。如下:


在displacement和surface中都有一个变量来指定它们相互之间的对应关系,你可以这样认为,如果一个surface是一个displacement surface的话,那么在surface所保存的顶点信息就是displacement所在的基准面信息,实际上就是记录基准面的四角坐标,当产生实际顶点时过程如下:通过这四角的坐标和displacement的power信息可以插值获得每一个真实顶点的基准点坐标,然后通过CdispVert所保存的信息获得真实的顶点坐标。
3、如何处理地形系统
      在HL2中为了高效的渲染地形定义了大量的类,其中最重要的为CdispInfo,它几乎包含了地形的大部分处理,还有一个类CpowerInfo,这是一个予计算的模版类,它预先计算了每一种大小displacement的信息,这样做是为了加速地形的渲染,而类CcoreDispInfo只用来载入数据时使用,它的功能就是将BSP文件中的信息转换为程序可以处理数据。其实在HL2中比较难懂的是它的LOD算法,下面是它具体的算法:
      首先是它的更新策略,在正常情况下(LOD的控制台变量为默认值)对displacement的更新有两种情况,它规定了一个更新半径(由控制台变量r_DispFullRadius指定)凡是位于此半径内的displacement的LOD等级都为最大。另外在每次更新时每一个displacement内部都记录了更新时camera的位置,如果当前camera的位置减去上一次更新时camera位置的大小大于一个规定值(由控制台变量r_DispRadius指定),对其进行更新,必须注意的是此时更新的是位于更新半径外的displacement。
      下面是它的LOD算法,还是要提醒此时是对更新半径外的displacement进行处理。通常现在LOD算法都是基于距离进行计算的,但是HL2中没有采用这种方法,而是使用一种称为屏幕误差的方法进行。具体方法如下:
      首先假设将要更新的displacement位于屏幕的中心,然后求出此时的camera变换矩阵,遍历displacement中的所有顶点(不包含当前displacement的中心点和四个角上的点),对于每一个顶点,用它的坐标和它所在error edge的算术中点坐标相减,然后用这个值和当前displacement的AABB中心点相加,结果通过camera矩阵进行变换然后将X,Y部分分别除以Z值得到的就是这个顶点在屏幕上的视觉误差了。这是一个非常不容易理解的部分,尤其是除以Z值的部分,其实这样做是为了将其投影到Z为1.0的平面上,省略了进行投影变换的过程,提高了效率。当这个误差大于预定义值就将这个顶点渲染出来。
      通过上面的介绍可以看出在HL2中并没有象其它LOD算法一样对每一个displacement硬性的规定一个LOD级别,而是通过每一个顶点的屏幕误差来确定它是否可以被渲染,这样做在渲染非常大的地形时效率不会太好,但是在处理HL2这样的小场景时效率确实非常不错。同时也要注意这种地形方法的优点,它非常适合处理落差非常大的室外场景,如地牢围攻这样比较夸张的地形,不过此时就需要对它的LOD算法进行一下改进,另外在HL2中地形一般都比较小,如果地图设计良好的话基本上很少会渲染hidden surface,因此上不需要做地形tile的HSR,而将这种地形系统扩展到无限地形系统时,必然需要使用一定OC算法来做HSR的工作,这可能是将其扩展到无限地形系统的一个非常具有挑战性的工作了。

posted on 2007-03-25 17:08 Dreams 阅读(...) 评论(...) 编辑 收藏