Hidden Surface Removel Algorithms — Occlusion Culling

     隐藏面剔除算法的出现是基于以下的现实,在游戏场景中你不必每帧都将所有的物体渲染到屏幕上,它的作用主要有两个:一个是减少GPU的屏幕填充率,另一个是减少CPU和GPU的传输带宽。根据当前的硬件现状GPU的填充率已经不存在任何问题,而现在唯一的限制位于AGP上,它那可怜的一点带宽已经成为图形渲染的瓶颈,因此当前HSR算法大部分都放在CPU端来做以尽量减少每帧需要传输的数据量,这里你不需要对CPU的负担感到担忧,因为现在的CPU完全可以适应这样的要求。当你将场景采用适当的场景分割算法如BSP、OC tree算法组织起来后,就需要在每帧渲染时使用适当的HSR算法对其中无用的信息进行剔除,现在的HSR算法基本上可以分为四种:backface culling、frustum culling、portal culling、occlusion culling。它们的作用分别如下:backface culling用于剔除mesh背面的triangle,frustum culling用于剔除屏幕之外的物体,portal culling用于剔除不能通过portal看到的物体,而occlusion culling用于剔除场景中被自己前面的物体所遮挡的物体。其中前两种算是最常用的算法了,对于backface culling来说它已经在GPU的硬件上实现,不过为了减少传输带宽,在适当的地方你也可以将其使用到CPU部分,主要是指地形这样的大规模MESH上,如何使用这需要看你是如何组织地形数据的,当你使用triangle strip渲染地形时,尽量按块来对地形进行culling,而当你使用triangle list来渲染地形时,你就可以尝试使用逐triangle来对其进行culling,关于实际使用应该使用哪种方法,需要你自己试验一下。frustum culling由于太普遍这里不再解释,而portal culling如果详细介绍的话那就是另外一篇文章了,这里只是简单的介绍一下,通常的portal culling实际分为两种,一个是用于bsp的PVS算法,这种方法的优点是通过预计算获得可见性信息,速度非常快,而且场景中的portal可以通过程序获得,不需要手工进行指定,但是缺点也非常明显,基本上它只能用于室内场景,而且由于PVS是一个可能的可见集合因此不能对不可见场景作到完全的剔除。另一个可以被称为实时的portal culling,这种方法的优点很明显,如果场景安排合理的话它可以作到完全的剔除,而且它可以用于任意的场景中,indoor、city、terrain它都可以使用,缺点也非常明显,portal必须手工进行指定,而且由于portal culling每帧都需要计算因此每帧出现在屏幕上的portal必须进行限制,还有最重要一点通常由于场景的限制尤其是在outdoor场景中它也不能完全作到完全剔除,下面我举一个简单的例子来看看它为什么不能作到完全的剔除:

  上面可以看作是一个房子,B为房间的门,也就是一个portal,A是场景中的一个物体如果我们做portal culling计算时,如果一个npc位于D点那么可见的,但是当位于C点时,由于前面存在物体A,应当是不可见的,但是portal culling并没有将其剔除因此npc还是会被传送到GPU进行渲染。
  这个时候就需要可以使用occlusion culling技术来弥补这个缺憾了,因此在一个portal引擎中必然需要OC技术来配合使用,这里再举一个outdoor的例子,在室外存在一个building,我们可以使用building的门和窗户作为portal来对building内部进行culling,将building作为一个occluder对整个场景进行OC操作。根据相关文献提供的资料,当场景使用OC技术后会比不使用OC的场景提高30%到70%不等的FPS,因此如果有可能的话应尽量将OC技术加入到你的引擎中,而且OC技术并不是只能使用到portal引擎中,它可以被使用到任意技术构建的引擎中。
  现在的OC算法非常多,比较优秀的如HZB、HOM算法虽然比较强悍,但是放在CPU进行计算的话,效率还是非常低的,达不到实时的要求,而当前的GPU还没有对其实现硬件的支持,因此不是本文介绍的重点,这里介绍的OC算法称为区间扫描线Z缓冲器算法,这是一种可以进行完全剔除并且效率非常高的一种OC算法。在进行介绍前我们必须明确几个概念,在OC中遮挡物被称为occluder,被遮挡物称为occludee,OC算法就是检查occludee是否被occluder所遮挡。场景中那些比较大的物体可以作为occluder,如房屋,城堡等,occluder上位于同一plane上的polygen称为surface,surface上由edge首尾相连构成一个闭合的空间。
  进行OC计算之前我们首先需要获得场景中的所有occluder,这是非常重要的一个步骤,这一步通常在场景的预处理时进行,根据场景来产生一个occluder列表并保存到文件中。
  下面我们讨论一下如何获得occluder,场景中的occluder通常需要手工进行指定,这是因为开发一个自动获得occluder的算法非常复杂,同时由于OC运算效率问题同一帧中出现的occluder数量必须进行限制。下面我们分情况来考虑一下:对于一个标准的BSP引擎来说,整个场景都由brush构成,因此获得occluder相对简单一些,在对场景建模时手工指定哪些brush作为occluder,然后在进行BSP分割时对这部分brush单独进行处理,按照和场景获得可见面一样方法获得这些brush的可见面来作为occluder。而对于其他技术构建的引擎获得occluder也相对简单,例如现在MMO比较流行的基于terrain的引擎,场景中可以作为occluder的物体其实都是一个个单独的model,因此求occluder实际上就变成获得model的convex hull,这类算法也比较多如qhull算法,但是如果使用程序获得hull的话对model的外形存在一定的限制,一个替代的方法是在美工对物体进行建模时同时为其指定一个hull并保存到文件中,这样的话就不会限制model的形状了,因此对于第二种情况概括起来说就是在编辑场景时指定适当的物体作为occluder,然后将物体的convex hull保存到occluder list中。
  当我们获得场景中的occluder就可以进行OC运算了,先来看看区间扫描线Z缓冲器算法是依照什么样的原理来实现OC的。

  在上图中物体N为occluder,A、B、C为occludee,我们假定它们投影到屏幕上都为一个矩形,我们这里称N的上下两条edge a、b为扫描线,从上图中可以看出如果物体A位于N的后面那么A一定被N所遮挡,这可以通过比较N和A所在surface的z值来进行比较,这也就是本算法中Z缓冲器的来历,现在我们假设物体A、B、C都位于N的后面,可以看出A被完全遮挡,B是部分遮挡,C没有被遮挡,如何判断呢,先看A的情况,N和A存在四条扫描线按照y从小到大顺序分别是b,d,c,a,在扫描线b和d构成的区间中由于不存在A的edge因此跳过,检查扫描线d和c构成的区间,我们称位于两条扫描线之间的edge为活化边,它们保存在活化边列表active edge list中,每一条活化边保存了两个surface指针,分别是左surface和右surface,例如对于N的左edge而言,它的左surface不存在因此指向屏幕的background,也就是z值为最大的surface,而右surface为N所在的surface,其他类似。现在我们检查扫描线d和c之间的活化边列表,首先获得A的x值最小的火花边为A的左edge,接着获得N的活化边列表中x大于A的左edge的活化边,为N的右edge,比较两条edge的左surface,A的左edge的左surface的z大于N的右edge的左surface的z,因此A的左edge被N所遮挡,接着同样的方法检查A的右edge还是被遮挡,这样我们就可以判断A完全被N所遮挡。使用同样的方法检查B,在扫描线f和b构成的区间中,由于N不存在活化边列表可以判断B没有被N完全遮挡。再检查C,在扫描线h和g之间N的活化边列表中所有的edge的x都小于C的左edge,可以判断C没有被N遮挡。对于其它复杂的情况例如存在多个occluder的情况你自己可以验证,这种算法在任何情况下都可以对物体是否被完全遮挡进行精确的判断。
  通过上面的介绍相信你已经完全理解区间扫描线Z缓冲器算法的基本原理,下面看一下具体如何来实现这个算法,步骤如下:
1、获得场景中所有的occluder,剔除occluder上所有背对camera的surface。
2、使用near clip plane对所有的surface进行clip操作,剔除位于near clip plane之外的surface,如果surface和near clip plane相交计算交点,这样也就剔除了那些位于nearclip plane之外的edge。
3、将所有的顶点变换到投影空间,这里需要注意投影空间的性质,投影变换实际上就是将frustum变换为一个BOX,对于openGL来说就是一个立方体,最小拐点为(-1,-1,-1),最大拐点为(1,1,1),对于DX来说是一个长方体,最小拐点为(-1,-1,0),最大拐点为(1,1,1)。在投影空间所有物体投影到屏幕上形状不会发生改变,这也是为什么要变换到投影空间的原因。计算每一个surface在屏幕上的投影面积,剔除那些面积过小的surface,剔除位于frustum之外的surface,但是并不对surface求交,这是为了防止因为计算精度的原因出现错误。将surface所在的plane也变换到投影空间,这样做是为了方便计算指定顶点的z值。
4、将变换到投影空间中的所有顶点保存到edge list中,注意edge保存方式,edge起点的y值应当小于终点的y值,并且我们称起点处的y值为edge的y值。在edge list中按照edge的y对所有的edge按照从小到大的顺序进行排序,如果y相同比较x值,如果x也相同比较edge的斜率。因为edge list完全按照y进行排序,因此我们也可以称其为Y桶。需要注意的是如果edge和X轴平行的话,并不需要将其加入到edge list中。
5、查找扫描线并计算扫描线之间的活化边列表。从上面的原理介绍中我们也能大致了解如何获得扫描线,扫描线必然位于edge的端点上,而且如果edge存在相交的话,那么交点也需要做为扫描线,那么我们需要对Y桶进行遍历来查找位于扫描线Y=-1和Y=1之间所有的扫描线,当获得所有的扫描线后接着需要计算扫描线区间的活化边列表,注意活化边列表中的edge需要使用edge与扫描线之间的交点作为端点,并且活化边列表是按照x进行排序,因此也可以称为X桶。
6、对场景需要检查是否被遮挡的物体传入它的AABB作为occludee,首先检查occludee在屏幕上的投影面积是否足够大,如果太小不需要对其进行OC,接着将AABB转换为一个BOX,计算BOX的surface和edge,将其变换到投影空间并按照上面的方法查找扫描线并获得活化边列表。从occluder和occludee中y最小的扫描线出发,逐区间对活化边列表进行比较检查occludee是否被完全遮挡。
  在这种算法中,几乎每帧都需要对活化边列表进行更新,因此计算量最大的部分也在这里,不过一旦活化边列表建立以后对occludee进行遮挡计算非常方便快捷,适合对大量的动态物体进行OC运算,尤其是那些渲染代价非常大的角色模型。

 

      对于通用场景来说我以前介绍的区间扫描线Z缓冲器算法可以剔除大部分的不可见物

体,但是在基于heightmap的大规模地形场景下会发现作用不是太大,区间扫描线Z

缓冲器算法需要在场景中手工指定occluder,occluder必须为规则物体,而在地形

场景中这种occluder非常少,也就是场景中的建筑物之类的物体。实际上地形场景

中最适合作为occluder的是连绵起伏的丘陵、山脉,它遮挡住了场景中的大部分物

体,但是区间扫描线Z缓冲器算法对这种情况下的OC就无能为力了,需要使用其它算

法进行OC计算,现在网上完全公开的适用于地形场景的OC技术主要有以下几种:
Voxel column culling、Hierarchical visibility、incremental horizon。这些

算法都需要进行一些预处理,其中最流行是incremental horizon(增量地平线)技

术,这种技术要求在渲染前对每一个地形块计算一个潜在轮廓线,在渲染时将这些

潜在轮廓线合并为地平线进行OC处理。由于这种算法需要进行预处理因此也不太适

用游戏开发,此后我自己又开发了一个realtime计算的incremental horizon算法,

但是发现开销太大,根本无法用于realtime rendering(和Pascal Junod在论文
《Implementation of a O(na(n)log(n)) Point
Visibility Algorithm on Digital Terrain Models》中使用的算法相同,这篇论

文我也是后来才发现的,有兴趣的可以翻看一下我以前在gameres发表的文章)。
下面列出的是网上相关的论文,有兴趣可以自己看一下。
Lloyd B, Egbert P. Horizon occlusion culling for real-time rendering of

hierarchical terrains. In: Gross M, Joy KI, Moorhead RJ, eds. Proc. of

the IEEE Visualization. Boston: IEEE Computer Society Press, 2002. 403-

410.
Stewart J. Hierarchical visibility in terrains. In: Dorsey J, Slusallek

P, eds. Eurographics Workshop on Rendering. Vienna: Springer-Verlag,

1997. 217-228.
Zaugg B, Egbert P. Voxel column culling: Occlusion culling for large

terrain models. In: Ebert D, Favre JM, Peikert R, eds. Proc. of the Joint

Eurographics-IEEE TCVG Symp. on Visualization. Vienna: Springer-Verlag,

2001. 85-93.
Stewart J. Fast horizon computation at all points of a terrain with

visibility and shading application. IEEE Trans. on Visualization and

Computer Graphics, 1998,4(1):82-93.
Daniel Archambault. All the Distant Horizon Edges of a Terrain. B.Sc.

(Hons.) in Computing Science, Queen’s University (Kingston), 2001
Pascal Junod. Implementation of a O(na(n)log(n)) Point
Visibility Algorithm on Digital Terrain Models. October 1999

后来我仔细观察farcry的editor sandbox,经过差不多两个多月的试验终于开发出

一个可以realtime运行的用于地形环境的OC算法,这个算法的开销非常小,经过我

在OGRE平台上的试验,此算法可以做到非常精确的剔除,FPS提升明显。由于这个算

法的核心是线段求交,因此我暂时称其为线段求交OC算法。
在介绍这个算法前先明确一下坐标系,一张heightmap的行为X,列为Y,高度方向为

Z。
先考虑单点OC的情况,如下图所示:
         P
         |
         |C
A-------------------B
         |
         |
         O
假设plane AB为一个occluder,O为camera位置,如果要判断点P是否被AB遮挡,只

需要简单的判断线段OP是否和occluder AB相交即可,如果存在相交点C则点P即被遮

挡,问题被简化为线段和平面的求交。下面继续考虑在地形环境下如何简化这个问

题,在地形环境下heightmap的每一行和列都可以看作一个occluder,这里假设AB为

行或列相临两个顶点组成的线段,如果要判断点P是否被AB遮挡,可以通过比较交点

C在线段OP和线段AB上的z值来确定:
Zc_op > Zc_ab   点P没有被线段AB遮挡
Zc_op <= Zc_ab  点P被线段AB遮挡
现在检查点P是否被遮挡在地形环境下简化为一个简单的2D线段求交问题,这也是本

算法之所以高效的原因。
下面继续来看一下如何将算法由一个点推广到整个地形。在基于heightmap的地形系

统中通常将地形分成一块块小的tile,根据LOD算法的不同tile大小可以为17*17或

者33*33不等,这里假设使用17*17的tile。
首先来看如何剔除场景中被遮挡的tile。对于tile来说如果所有的顶点都位于其前

方行和列的下方,那么它一定被遮挡。换句更精确的定义,对于tile的每一个顶点

,如果与camera所在位置所形成的线段,全部与位于tile和camera之间的行或列中

任意一条线段相交,那么可以确认tile被完全遮挡。
按照上面的定义,一个tile如果被检查到完全遮挡需要检查17*17=289次,虽然2D线

段求交运算开销非常小,但是一个tile就需要进行289次运算仍然是不可接受的,需

要更简化的算法。考虑一下区间扫描线Z缓冲器算法,occludee使用都是物体的AABB

,是否可以使用tile的AABB进行运算呢?由于地形环境的性质可以不用考虑AABB最

下面的四个顶点,但是直接使用AABB最上面的四个顶点进行运算绝对不行,如图所

示:

四个顶点虽然被完全遮挡,但是occludee并没有被完全遮挡,如果解决这个问题需要将AABB的up表面分割成16*16的格子,这样的话运算次数并没有发生变化。这里可以使用一个取巧的方法,如下图所示:

将AABB投影到camera空间,直接获得线段AB,将AB线段16等分,获得17个新的顶点,注意这些顶点z值全部相等,现在ocludee是否可见只需要检查17次就可以了。
下面看一下算法复杂度,对于一个完全遮挡的tile只需要进行17个顶点的计算,完全未遮挡的tile只需要计算一个顶点,部分遮挡的tile大约是2-16个顶点左右,由于tile在进行OC运算之前首先要做frustum culling,剔除被frustum culling的tile,然后剔除那些没有被遮挡的tile,实际上的运算量非常少。
注意这里的每个顶点的计算不是指简单的17次线段求教运算,根据tile距离camera位置的远近每一个顶点求交的数量是不一样的,例如下图所示:

图中线段OA需要检查三条行和列,一共是六个交点,需要进行六次线段求交运算.
      对于场景中的模型进行OC运算时,也需要按照上面的方法将模型的AABB变换到camera空间,获取一条occludee线段,然后根据地形相临顶点之间的距离确定顶点数。
      使用这个算法和区间扫描线Z缓冲器算法相配合可以获得在室外场景中最大限度的剔除被遮挡物体,先用线段相交OC算法剔除被地表遮挡的tile和模型,然后用区间扫描线Z缓冲器算法剔除被建筑物遮挡的tile和模型,完美的室外场景OC解决方案,very nice!!!!

原创文章,转载请注明出处!!!!!!!

线段相交OC算法演示程序下载(使用OGRE平台,运行前先看readme文件):
/Files/dreams/ogrenew.part01.rar
/Files/dreams/ogrenew.part02.rar
/Files/dreams/ogrenew.part03.rar

/Files/dreams/ogrenew.part04.rar
/Files/dreams/ogrenew.part05.rar

/Files/dreams/ogrenew.part06.rar
由于cnblog上传文件大小限制,因此文件被分割成1M大小上传,抱歉!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值