前腐后继 发表于 2006-12-8 19:47:00

Half Life2 Displacement Terrain System

<strong><br/><br/></strong>Half&nbsp;Life2&nbsp;Displacement&nbsp;Terrain&nbsp;System<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HL2的地形系统是一个另类的地形系统,它没有使用当前流行的heightmap技术,而是完全使用displacement&nbsp;map技术来进行处理,使用displacement&nbsp;map技术的初衷是为了在框架中和BSP技术保持一致,这是因为在BSP中最小的渲染图元是surface,surface的限制是所有的顶点必须位于同一个平面上,实际上从理论上来说在HL2这样的引擎中也可以使用heightmap来构造地形,那就是将heightmap数据也分块保存,但是当前流行的地形技术使用的都是一个整张heightmap,而且大量的LOD算法都是基于这个基础上的。使用displacement&nbsp;map的另外一个好处是可以构造更加复杂的地形,这是因为它存在法线的缘故。同时使用displacement&nbsp;map还有一个好处是在将来,因为当前MS已经对displacement&nbsp;map技术进行了支持,现在的问题是GPU在硬件上还不支持,如果一旦硬件支持的话,就有可能将整个地形由GPU来处理,那么对地形渲染的速度和精度会有非常大的提高。<br/>1、what’s&nbsp;Displacement&nbsp;Map<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;displacemtn&nbsp;map技术是最近几年才出现的一个技术,它出现的初衷是为了解决内存和GPU之间的带宽问题,它开始主要用于高poly角色模型的渲染,由于模型的高数据量会使内存和GPU之间的传输成为瓶颈,因此将其转换为低poly模型和一张displacement传送到GPU中,在GPU中转换为高poly模型进行渲染,可以大大减轻传送的数据量。<br/>但是当前这项技术已经获得非常大的发展,它使用的范围非常大,从室外地形、水面、海洋到室内场景的渲染,它为提高场景的真实度提供了一个有力的工具。一个displacement&nbsp;map通常包含两部分:一张normal&nbsp;map用来保存法线,另一张height&nbsp;map用来保存顶点的偏移。当需要从低poly模型转换为高poly模型时很简单,首先从模型的一个face上获得一个插值点P1,然后从displacement&nbsp;map中获得相应位置的normal和distance,那么新的顶点P2为:<br/>P2&nbsp;=&nbsp;P1&nbsp;+&nbsp;normal*distance<br/>关于displacement&nbsp;map的参考文章请看shaderX2中的Displacement&nbsp;Mapping。<br/>2、HL2&nbsp;Displacement&nbsp;Map&nbsp;Rule<br/>&nbsp;&nbsp;&nbsp;在HL2中displacement&nbsp;map信息被保存在BSP文件中,它有以下几个数据块:<br/>LUMP_DISP_VERTS&nbsp;&nbsp;保存displacement真实顶点信息<br/>LUMP_DISP_TRIS&nbsp;&nbsp;&nbsp;保存displacement三角形标识符信息(walkable&nbsp;or&nbsp;buildable)<br/>LUMP_DISPINF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;保存displacement&nbsp;的连接信息<br/>它们保持的信息结构如下:<br/>LUMP_DISP_VERTS:<br/>class&nbsp;CDispVert<br/>{<br/>public:<br/>Vector&nbsp;m_vVector;//&nbsp;Vector&nbsp;field&nbsp;defining&nbsp;displacement&nbsp;volume.<br/>float&nbsp;m_flDist;&nbsp;//&nbsp;Displacement&nbsp;distances.<br/>float&nbsp;m_flAlpha;//&nbsp;"per&nbsp;vertex"&nbsp;alpha&nbsp;values.<br/>};<br/>LUMP_DISP_TRIS:<br/>class&nbsp;CDispTri<br/>{<br/>public:<br/>unsigned&nbsp;short&nbsp;m_uiTags;&nbsp;//&nbsp;Displacement&nbsp;triangle&nbsp;tags.<br/>};<br/>LUMP_DISPINF:<br/>class&nbsp;ddispinfo_t<br/>{<br/>public:<br/>int&nbsp;NumVerts()&nbsp;const{&nbsp;return&nbsp;NUM_DISP_POWER_VERTS(power);&nbsp;}<br/>intNumTris()&nbsp;const{&nbsp;return&nbsp;NUM_DISP_POWER_TRIS(power);&nbsp;} <p></p><p>public:<br/>Vector&nbsp;startPosition;//&nbsp;start&nbsp;position&nbsp;used&nbsp;for&nbsp;//orientation&nbsp;--&nbsp;(added&nbsp;BSPVERSION&nbsp;6)<br/>int&nbsp;m_iDispVertStart;//&nbsp;Index&nbsp;into&nbsp;LUMP_DISP_VERTS.<br/>int&nbsp;m_iDispTriStart;//&nbsp;Index&nbsp;into&nbsp;LUMP_DISP_TRIS.</p><p>int&nbsp;power;&nbsp;//&nbsp;power&nbsp;-&nbsp;indicates&nbsp;size&nbsp;of&nbsp;map&nbsp;//(2^power&nbsp;+&nbsp;1)<br/>int&nbsp;minTess;&nbsp;//&nbsp;minimum&nbsp;tesselation&nbsp;allowed<br/>float&nbsp;smoothingAngle;&nbsp;&nbsp;&nbsp;//&nbsp;lighting&nbsp;smoothing&nbsp;angle<br/>int&nbsp;contents;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;surface&nbsp;contents<br/>unsigned&nbsp;short&nbsp;m_iMapFace;&nbsp;//&nbsp;Which&nbsp;map&nbsp;face&nbsp;this<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//displacement&nbsp;comes&nbsp;from.<br/>int&nbsp;m_iLightmapAlphaStart;//&nbsp;Index&nbsp;into&nbsp;ddisplightmapalpha.</p><p>int&nbsp;m_iLightmapSamplePositionStart;//&nbsp;Index&nbsp;into.<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS&nbsp;&nbsp;&nbsp;<br/>CDispNeighbor&nbsp;m_EdgeNeighbors;//&nbsp;Indexed&nbsp;by&nbsp;NEIGHBOREDGE_&nbsp;defines.<br/>CDispCornerNeighbors&nbsp;m_CornerNeighbors;&nbsp;//&nbsp;Indexed&nbsp;by&nbsp;CORNER_&nbsp;defines.</p><p>enum&nbsp;{&nbsp;ALLOWEDVERTS_SIZE&nbsp;=&nbsp;PAD_NUMBER(&nbsp;MAX_DISPVERTS,&nbsp;32&nbsp;)&nbsp;/&nbsp;32&nbsp;};<br/>unsigned&nbsp;long&nbsp;m_AllowedVerts;&nbsp;//<br/>//&nbsp;This&nbsp;is&nbsp;built&nbsp;based&nbsp;on&nbsp;the&nbsp;layout&nbsp;and&nbsp;<br/>//sizes&nbsp;of&nbsp;our&nbsp;neighbors<br/>//&nbsp;and&nbsp;tells&nbsp;us&nbsp;which<br/>&nbsp;//vertices&nbsp;are&nbsp;allowed&nbsp;to&nbsp;be&nbsp;active.<br/>};</p><p>在HL2中规定一个displacement最大由17*17个顶点组成,每一个displacement都将左下角看作是自身坐标系的原点,并按照顺时针方向进行记数。如下:<br/><img src="http://www.valve-erc.com/srcsdk/Levels/images/displacement_02.png" border="0" alt=""/>&nbsp;</p><p>在ddispinfo_t类中我们可以看到为了记录displacement的邻接关系,它使用了两个数组,m_EdgeNeighbors记录四条边的邻接关系,而m_CornerNeighbors记录了四个角的邻接关系。必须注意的是在HL2中邻接的displacement并不一定都是一样大的,也就是说邻接的displacement不一定是一个,也可以是两个,如果邻接的为两个的话那么邻接的displacement的大小必须小一倍。如下:<br/><img src="http://www.valve-erc.com/srcsdk/Levels/images/disp_sew_04.png" border="0" alt=""/>&nbsp;</p><p>在displacement和surface中都有一个变量来指定它们相互之间的对应关系,你可以这样认为,如果一个surface是一个displacement&nbsp;surface的话,那么在surface所保存的顶点信息就是displacement所在的基准面信息,实际上就是记录基准面的四角坐标,当产生实际顶点时过程如下:通过这四角的坐标和displacement的power信息可以插值获得每一个真实顶点的基准点坐标,然后通过CdispVert所保存的信息获得真实的顶点坐标。<br/>3、如何处理地形系统<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在HL2中为了高效的渲染地形定义了大量的类,其中最重要的为CdispInfo,它几乎包含了地形的大部分处理,还有一个类CpowerInfo,这是一个予计算的模版类,它预先计算了每一种大小displacement的信息,这样做是为了加速地形的渲染,而类CcoreDispInfo只用来载入数据时使用,它的功能就是将BSP文件中的信息转换为程序可以处理数据。其实在HL2中比较难懂的是它的LOD算法,下面是它具体的算法:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先是它的更新策略,在正常情况下(LOD的控制台变量为默认值)对displacement的更新有两种情况,它规定了一个更新半径(由控制台变量r_DispFullRadius指定)凡是位于此半径内的displacement的LOD等级都为最大。另外在每次更新时每一个displacement内部都记录了更新时camera的位置,如果当前camera的位置减去上一次更新时camera位置的大小大于一个规定值(由控制台变量r_DispRadius指定),对其进行更新,必须注意的是此时更新的是位于更新半径外的displacement。<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下面是它的LOD算法,还是要提醒此时是对更新半径外的displacement进行处理。通常现在LOD算法都是基于距离进行计算的,但是HL2中没有采用这种方法,而是使用一种称为屏幕误差的方法进行。具体方法如下:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先假设将要更新的displacement位于屏幕的中心,然后求出此时的camera变换矩阵,遍历displacement中的所有顶点(不包含当前displacement的中心点和四个角上的点),对于每一个顶点,用它的坐标和它所在error&nbsp;edge的算术中点坐标相减,然后用这个值和当前displacement的AABB中心点相加,结果通过camera矩阵进行变换然后将X,Y部分分别除以Z值得到的就是这个顶点在屏幕上的视觉误差了。这是一个非常不容易理解的部分,尤其是除以Z值的部分,其实这样做是为了将其投影到Z为1.0的平面上,省略了进行投影变换的过程,提高了效率。当这个误差大于预定义值就将这个顶点渲染出来。<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;通过上面的介绍可以看出在HL2中并没有象其它LOD算法一样对每一个displacement硬性的规定一个LOD级别,而是通过每一个顶点的屏幕误差来确定它是否可以被渲染,这样做在渲染非常大的地形时效率不会太好,但是在处理HL2这样的小场景时效率确实非常不错。同时也要注意这种地形方法的优点,它非常适合处理落差非常大的室外场景,如地牢围攻这样比较夸张的地形,不过此时就需要对它的LOD算法进行一下改进,另外在HL2中地形一般都比较小,如果地图设计良好的话基本上很少会渲染hidden&nbsp;surface,因此上不需要做地形tile的HSR,而将这种地形系统扩展到无限地形系统时,必然需要使用一定OC算法来做HSR的工作,这可能是将其扩展到无限地形系统的一个非常具有挑战性的工作了。</p>
页: [1]
查看完整版本: Half Life2 Eisplacement Terrain System !