图中右上角小地图黑色部分为当前摄像机看不见的区域
Blog开通了,在思考第一篇文章该写点啥。最近在写地形编辑器,当然就从里面挑个功能点来写一下。刚完成了地形系统的PVS(可能可见集),原创的成份比较多,就拿来做开篇吧。之前看过《游戏编程精粹4》中的“地平线剔除”算法,很不错,可惜结构太过复杂,对编辑器现有架构要做大的改动,而且CPU开销很大,所以放弃。在浏览GameDev上的一篇帖子时有人提到PVS,突然想到我现在以Patch为基本单位的数组结构很适合做PVS,相对于摄像机所在的Patch,周围需要绘制的Patch排列成一个正方形数组,每个Patch的可见性可以用一个字节序列中的一位置0或置1来表示,这样围绕当前摄像机所在Patch的(50*50)个Patch的可见性可以用320个字节来存储。按一个格子2米见方,一个Patch 16*16个格子计算,10平方公里的地形大概是100*100个Patch,这些Patch的PVS数据加在一起大概3M多,在可以接受的范围。结构很简单,看上去很美,现在问题只剩下PVS该如何生成。PVS数据要求尽量精确,剔除尽可能多的不可见Patch,又不能错误剔除任何可见Patch,因为要对10000个Patch做PVS计算,所以运算速度也很关键,否则只能等上一两个小时再看结果了。最后我采用了一种非常简单快速,但不那么精确的算法来生成PVS信息(当一个Patch的可见性不能精确判定时就将其视为可见,这样可以保证最后的渲染结果不会出现破面),试验证明效果良好。具体算法请参考下图。
A、B、C分别为三个Patch,假设摄像机当前在A Patch,我们要检测摄像机在A Patch上的任何位置是否能看到C Patch。A Patch上的下面一
根蓝线是A Patch上最大高度所在的水平面,我们加上摄像机可能离地面的最大高度h,得到A Patch上摄像机可能的最大高度水平面(P1、P2之间的蓝线),在这个平面上我们选择A Patch的边界点P1和P2(在三围空间是四个点)作为可见性扫描点。在C Patch上我们同样找到它的最大高度水平面并在这个面上取它的边界点P3、P4作为检测点。通过从P1、P2分别向P3、P4连接一条线段,看这些线段是否穿过障碍物,如果有任何一条线段没有穿越障碍物则判定A、C之间为可见,如果所有的线段都穿越了障碍物则判定A、C之间不可见。同样扫描包围A的所有Patch,得到A Patch的PVS。很显然,通过上述方法得到的PVS信息只有在摄像机高度位于P1、P2水平面之下时才是可靠的,所以游戏中要通过摄像机的高度是否超过这个平面来动态决定是否使用PVS信息。如果我们在预处理时对h取比较大的值,比如6米,那么当摄像机采用跟随游戏主角的第三人称视角时PVS信息在大部分时间内都是有效的。
我已经在我的地形编辑器里实现了上述算法,实际效果相当另人满意,在大部分场景都能剔除50%左右的不可见Patch,在一些峡谷,盆地之类的地方剔除效果更好。但是要指出的是帧速率的提高通常在10%到40%之间,没有达到平均50%。这是因为被剔除的50%的Patch一般距离较可见Patch远,都使用了比较低的LOD级别,并且投影在屏幕空间所占的面积也比较小(对像素填充率要求低)。另外不可见Patch中有相当一部分是背面Patch,对绘制影响不大。我们很容易看到这个算法将来可能的扩展空间,如果预处理时考虑到Patch上可能有水面和草丛,那么得到的PVS信息也可以用来剔除不可见Patch上的水面和草丛,这样对帧速率的提高会更明显。在我的机器上对10000个Patch做PVS计算大概用了3、4分钟。我的机器配置是 CPU 3.0G,RAM 1G,显卡 6600LE。
![](https://img2024.cnblogs.com/blog/35695/202408/35695-20240826070647288-243310440.jpg)