winform 判断控件有没有被遮挡_适合于移动平台的预计算遮挡剔除

f1e570537c5649d9cab5dabd9bfdaa08.png

近两年移动游戏的场景越来越大、细节越来越丰富,渲染的压力非常高。远处物体通常是借助LOD(LevelOfDetail)技术来降低绘制面数和Drawcall,而对于细节丰富的室内、城区、集市场景则主要依赖遮挡剔除技术来做优化。本文将针对移动平台,介绍下预计算遮挡剔除方案的实现流程和开发中的一些优化。

如有任何问题,欢迎大家在评论区和我们交流和讨论哦~


遮挡剔除

遮挡剔除就像它名字所说,如果一个物体完全被其他物体遮挡,绘制这个物体不会对最终画面有任何影响。我们可以通过尽可能低的开销找出被遮挡的物体,不再绘制这些物体,来降低渲染上的压力。

实现遮挡剔除有很多技术方案,知乎上有很多讨论,比如这篇洛城:剔除:从软件到硬件就写的比较全面,这里不再赘述。

5394e72dd9361bc98565c0be1ce2694f.png
视锥剔除游戏引擎都有统一的实现,遮挡剔除则有不同的算法

Unity自带的Umbra就是一种非常优秀的遮挡剔除方案,在很多3A作品中被使用,这套遮挡剔除在移动平台CPU和内存上的开销还比较高,而且难以和流式加载、Instancing等功能结合,性价比不够高。

基于GPU的遮挡剔除算法对硬件有一定要求,在移动平台还无法广泛使用,而基于CPU的实时剔除开销往往比较高。Unreal中提供了一种预计算遮挡剔除算法,实时开销低并且实现思路非常简单:

• 首先将玩家相机的活动区域划分成很多小空间(Cell)

• 离线计算出相机在每个Cell内各个角度都被遮挡的模型集合,保存起来

  • 在运行时根据相机的位置查询对应Cell的遮挡模型集合,把对应模型隐藏

b86494fbe0370c3211423bca3dee3a1f.png

因为是完全离线烘焙,运行时只有查询的开销,非常适合移动端,下面就介绍一下预计算遮挡剔除的实现。

1.空间Cell的划分

首先是划分空间Cell,Cell是我们烘焙、记录数据的最小节点,运行时相机在一个空间范围内时可以读取相应的遮挡数据来显隐模型。

为了运行时方便查询通常使用平行坐标轴包围盒(AABB)来划分。Cell数量划分的太多会导致烘焙时间过长,并且烘焙生成的数据过多,增加包体大小和内存压力,而划分的太粗则会使剔除的效率降低。

划分Cell我们要先找到场景里的地面,玩家不会到地面以下,不需要在地下生成Cell。如果场景有多层则要找到每一层的表面,因为每一层看到的物件都不相同,Cell要划分开。可以通过射线检测或是像Unreal中使用的软光栅方式找到各层面的高度,依据这些高度来做划分。

89fb1e4874f338e98b80d9564ffead76.png

如果层表面是斜面会比较麻烦,中间的Cell能看到上下两边的模型,剔除率会非常低,生成时要限制下这类Cell的大小,尽可能降低影响。

当玩家飞的比较高时,地面附近的小物件都已经消失了,而且飞起后遮挡视野的物体变少,空中Cell数据性价比比较低,所以只需在地面附近范围划分Cell,相机移动到没有Cell的区域时关闭剔除功能。

水平方向上城区的遮挡比较多,空间关系变化大,而野外空间比较开阔,可以对城区和野外使用不同大小的Cell,并且剔除空气墙外的Cell,最终将Cell数量控制在合适的范围内。

eac5803de1793146d59f7f74d2a59f22.png
空旷区域使用低密度Cell,城区使用高密度Cell

2.准备烘焙数据

因为是离线烘焙,所以无论是遮挡物还是被遮挡物都要求是静态的,一个物体既可以是遮挡物也可以是被遮挡物。

被遮挡物(Occludee)就是我们要记录可见性信息的物体,场景里的静态物体都可以作为被遮挡物,通常只拿被遮挡物的包围盒来计算可见性。像树、灯笼、旗子之类的动态模型只有小幅度的摆动,也可以选做被遮挡物。

为每个被遮挡物体分配一个id,保持id从0开始并连续,这样可以不需要额外的索引数据,每个物体只需要一个bit就可以记录可见性信息了。

遮挡物(Occluder)就是能起到遮挡作用的物体,除了要求是静态的之外还对材质有一些要求,像是半透或是AlphaTest的物件就不能做为遮挡体使用,可以通过检查材质类型过滤掉这些模型。另外像是比较小的物件,或是奇形怪状面又比较多的物体(比如树枝)也可以不作为被遮挡物,可以提高烘焙的效率。

植被基本都是AlphaTest的材质,会导致森林场景的剔除效果不佳,可以手动的给一些茂密的植被手动添加遮挡体。

0dfee2d6de50a8e52cf28d0e73bd082c.png
给植物添加手动遮挡体

给遮挡物增加MeshCollider供后面做可见性检测。

3.数据烘焙

通过射线检测来判断Cell和被遮挡物中间是否有遮挡物。

首先在Cell和模型之间生成一些射线,通常的做法是在Cell和模型包围盒相对的面上取一些采样点相连为射线。采样点越多烘焙精确性就越高,烘焙速度也就越慢,可以根据Cell和被遮挡物的相对大小和距离来调整采样点的数量。

cabaf288dddf2953d49681f307e9a8a1.png
Cell与模型之间射线检测

随机取点容易出现漏采样的问题,均匀取点的方式不方便自由的调整采样点个数,可以使用低差异序列,即能自由的设定个数采样点分布又比较均匀。

d0556e2519e36e4266e170bb3cde88e6.png
随机取点,均匀取点,低差异序列

生成射线后利用Raycast来判断Cell和被遮挡物之间是否有物体遮挡就可以了。Unity2019中才有DOTS的多线程物理,我们使用的2018版单线程Raycast烘焙速度非常慢,烘焙一个场景通常需要一整晚的时间。

为了加快烘焙速度,我们借助了第三方的物理引擎来实现独立的多线程烘焙程序,将遮挡体、被遮挡体和空间格子数据导出,配台18核机器,把单场景的烘焙时间降低到了半小时以内。

312ea05ae3d555811914a4d24b715a5e.png
跑满

如果场景变的更大,还可以组建多机器分布式来进行并行烘焙提高速度,另外还有知乎大佬分享的DXR加速烘焙的方法也可以参考 zhing2006:使用Unity DXR加速PVS烘焙。

4.运行时

文件大小

我们一个场景有7500+个模型,划分了20000+个Cell,算下7500*20000bit有18MB,同级别的场景还有一打那么多,文件大小需要优化。

当玩家在地图一边时地图另一边的小物件是不会被加载的,保留他们的剔除数据更没什么意义,所以可以把临近的小物件分为一组,当一组的物件都不可见时就不再记录这一组数据。

a71ed670840c93e8a326d810fc18f16b.png
一片区域内的小物件分为一组

另外,烘焙的数据有很多连续的1和0,压缩比很高,利用LZ4HC做分块压缩,最终这个场景的剔除数据文件大小不到4M。

CPU和内存

因为Cell是AABB的,实现简单的树形结构来加速查找,几乎是零开销。运行时主要耗时在流式加载和修改模型显隐状态上。

2d8519258e5b053d45c4d4adc10d24af.png
与内置遮挡剔除耗时对比

内存占用上实现流式加载,可以控制在0.5M以下。

剔除效率上也还算优秀,在静态物体上完全可以替代Unity自带的Umbra剔除。

2530b380c76dfe1917844c186721f383.png

一些缺点

不支持动态物体剔除,预计算遮挡剔除已经解决了大部分静态物件的可见性问题,对于剩余少量的动态物体可以使用OcclusionQuery方案来做,这种方案原理也很简单:绘制模型极简模(一般用包围盒),不着色(ColorMask 0),通过API查询这次绘制有没有像素通过Depth和Stencil测试来确定模型是否需要绘制。

这种方案的优点是市面上的手机基本都支持这个特性(gles3.0支持)。

缺点也有的:

  • 因为是GPU查询,再加上异步渲染,逻辑上要延迟1~2帧才能拿到查询的结果,在相机有大幅移动时会有穿帮,不过一般还能接受
  • 每个查询需要一次Drawcall,所以结果很有可能啥都没减Drawcall却变多了,使用时可以把一些模型的查询合并(比如只用在角色上,一个角色一次查询),并且只在有需要的区域开启
  • Unity没实现这个功能,需要自己去实现每个平台的支持(Unreal有支持且在堡垒之夜有使用)

5b6f3d28eab49066b891226478e2e665.gif
OcclusionQuery遮挡剔除查询

不支持实时阴影剔除,如果开启了实时阴影,会出现能看到影子但模型被遮挡的情况,如果把模型隐藏了,影子也会消失出现穿帮。

如果实时影子的方向不变,我们可以在烘焙的时候对影子单独烘焙一版剔除数据,如果阴影方向会实时变化这个方案就无法解决了。

预计算遮挡剔除方案原理和实现上都非常简单,在移动平台上性价比比较高,有很大的优化空间,比如包围盒可以改用OBB或是其它更精细的结构来提高烘焙精度,利用渲染光栅化替代射线检测实现像素级的遮挡烘焙等等。

如果大家有什么想法和建议,欢迎在评论区和我们讨论。

已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页