Scene Management --- Culling
仅供个人学习使用,请勿转载,勿用于任何商业用途。
上次说到场景很大,物体很多时,每帧都把所有物体传递给渲染系统是不可能的。考虑到所见区域只是场景中的一小部分,因此,剔除视野之外的物体,就显得尤为重要,也就是常说的裁剪。本质上,裁剪算是碰撞检测的一部分,通过碰撞检测判断物体是否在视锥之内,但他和游戏里一般的碰撞检测还是有很大区别的,所以不能混为一谈。当然,上次一的引擎流程图也稍稍进化了一点:
具体如何实现裁剪的过程呢?假设1w×1w米的区域内有1w个物体,最初级的算法就是遍历1w个物体,计算他们是否与视锥相交。非常不幸的是这个过程同样非常缓慢,也许只能让我们的程序从n spf(seconds per frame)提升到1或者2fps。于是有聪明人发现把整个区域细分为n个子区域,只要子区域在视锥之内,那么子区域内的物体也必然可见,这种分割方法通常称为均匀网格(uniform grid)。对均匀网格来说,决定网格的大小是个难题,如果把场景细分为1×1的区域,那么相比最初的方法,几乎没有改进。而如果子网格太大,当整个视锥都在某个网格内,或者与网格相交时,仍然必须计算子网格内的每个物体是否在视锥内,最不幸的情况下,也许恰好1w个物体都在这个子区域中,那么性能依然很糟。所幸这样的情况很少发生,所以总的来说,均匀网格在效率上有了很大提升。
按照细分网格的思想,完全可以对子网格进一步进行细分,所以类似四叉树,八叉树等等的空间划分算法被引入了裁剪。为了简单,下面仅以四叉树为例。显然,四叉树解决了均匀网格划分粒度大小的问题,它的多级层次划分,既能保证让大量物体迅速被剔除,又能保证裁剪精度。
四叉树非常适合户外场景(比如wow大部分主城以外的区域),但对于室内场景,地下城,或者物体相互遮挡非常严重的室外场景(比如城市),却有些力不从心。四叉树对空间所有方向进行划分,而室内场景/地下城通常只沿几个方向延伸(区域a---区域b---区域c),户外城市虽然方向均匀,但遮挡严重,处在视锥内的物体并不一定是可见的。
Portal也许是室内场景最好的划分方法:把空间分为大量相连的区域,并且预计算出在某个区域内可以看到的其他区域,比如在A方间内只能看到走廊B,那么显然其他区域内的物体就不用渲染了。这种裁剪过程更像是数据库查询而不是碰撞检测,效率非常高。但预计算可见性的过程却非常麻烦,而且对关卡设计的要求也很较高,我们希望在某个区域内可看到的额外区域尽可能少(比如1个),同时又要保证关卡的合理性,艺术性。
对城市来说,视锥内物体的可见性计算也非常重要,比如NFS这样的游戏,视锥内的物体也许非常之多,真正可见的却只有临街的那些。于是有了遮挡剔除(Occlusion cull)的概念,计算某个物体是否被其他物体挡住。概念虽然很简单,但相对前面几种裁剪方法,实现起来要复杂的多,超出了本文讨论范围。
现在到了最有趣的部分,我们可以把上面几种算法组合起来!比如在最高层次,把世界划分为较大的匀网格(比如500×500),这样的分割除了能快速裁剪大部分区域外,还让我们获得了动态加载场景的功能,1w×1w的区域内,只加载潜在可视范围内的n*n个子网格,随着视点的移动,不停加载/卸载子区域。接下来,为了保证裁剪的高效,对每个子网格再进行四叉树细分。更进一步,如果是室内场景,四叉树节点内可以包含portal节点,最后,如果protal节点所占的空间区域很大,还能继续用四叉树或者Occlusion cull细分对protal节点细分!实现细节上,只要保证所有方法都继承于Culler,我们可以派生出任意多种可互相嵌套的裁剪算法。
最后还要说一点,如果某个物体既在视锥之内,又没有被遮挡,那是否就一定要渲染它呢?考虑视锥内一把离观察者很远的杀猪刀:),也许投影光栅化之后,它只占屏幕中的几个像素,观察者完全无法分辨究竟是杀猪刀还是指甲剪。这种情况下,完全可以忽略这个物体。最简单的方法:用物体的包围盒面积与观察者之间距离的关系来作为参考。
下一次,将是关于碰撞检测的内容,to be continue………………