找回密码
 立即注册
查看: 662|回复: 9

适合于移动平台的预计算遮挡剔除

[复制链接]
发表于 2021-4-20 18:40 | 显示全部楼层 |阅读模式
近两年移动游戏的场景越来越大、细节越来越丰富,渲染的压力非常高。远处物体通常是借助LOD(LevelOfDetail)技术来降低绘制面数和Drawcall,而对于细节丰富的室内、城区、集市场景则主要依赖遮挡剔除技术来做优化。本文将针对移动平台,介绍下预计算遮挡剔除方案的实现流程和开发中的一些优化。
如有任何问题,欢迎大家在评论区和我们交流和讨论哦~
遮挡剔除

遮挡剔除就像它名字所说,如果一个物体完全被其他物体遮挡,绘制这个物体不会对最终画面有任何影响。我们可以通过尽可能低的开销找出被遮挡的物体,不再绘制这些物体,来降低渲染上的压力。
实现遮挡剔除有很多技术方案,知乎上有很多讨论,比如这篇洛城:剔除:从软件到硬件就写的比较全面,这里不再赘述。


视锥剔除游戏引擎都有统一的实现,遮挡剔除则有不同的算法
Unity自带的Umbra就是一种非常优秀的遮挡剔除方案,在很多3A作品中被使用,这套遮挡剔除在移动平台CPU和内存上的开销还比较高,而且难以和流式加载、Instancing等功能结合,性价比不够高。
基于GPU的遮挡剔除算法对硬件有一定要求,在移动平台还无法广泛使用,而基于CPU的实时剔除开销往往比较高。Unreal中提供了一种预计算遮挡剔除算法,实时开销低并且实现思路非常简单:
  首先将玩家相机的活动区域划分成很多小空间(Cell)
  离线计算出相机在每个Cell内各个角度都被遮挡的模型集合,保存起来
    在运行时根据相机的位置查询对应Cell的遮挡模型集合,把对应模型隐藏




因为是完全离线烘焙,运行时只有查询的开销,非常适合移动端,下面就介绍一下预计算遮挡剔除的实现。
1.空间Cell的划分
首先是划分空间Cell,Cell是我们烘焙、记录数据的最小节点,运行时相机在一个空间范围内时可以读取相应的遮挡数据来显隐模型。
为了运行时方便查询通常使用平行坐标轴包围盒(AABB)来划分。Cell数量划分的太多会导致烘焙时间过长,并且烘焙生成的数据过多,增加包体大小和内存压力,而划分的太粗则会使剔除的效率降低。
划分Cell我们要先找到场景里的地面,玩家不会到地面以下,不需要在地下生成Cell。如果场景有多层则要找到每一层的表面,因为每一层看到的物件都不相同,Cell要划分开。可以通过射线检测或是像Unreal中使用的软光栅方式找到各层面的高度,依据这些高度来做划分。




如果层表面是斜面会比较麻烦,中间的Cell能看到上下两边的模型,剔除率会非常低,生成时要限制下这类Cell的大小,尽可能降低影响。
当玩家飞的比较高时,地面附近的小物件都已经消失了,而且飞起后遮挡视野的物体变少,空中Cell数据性价比比较低,所以只需在地面附近范围划分Cell,相机移动到没有Cell的区域时关闭剔除功能。
水平方向上城区的遮挡比较多,空间关系变化大,而野外空间比较开阔,可以对城区和野外使用不同大小的Cell,并且剔除空气墙外的Cell,最终将Cell数量控制在合适的范围内。


空旷区域使用低密度Cell,城区使用高密度Cell
2.准备烘焙数据
因为是离线烘焙,所以无论是遮挡物还是被遮挡物都要求是静态的,一个物体既可以是遮挡物也可以是被遮挡物。
被遮挡物(Occludee)就是我们要记录可见性信息的物体,场景里的静态物体都可以作为被遮挡物,通常只拿被遮挡物的包围盒来计算可见性。像树、灯笼、旗子之类的动态模型只有小幅度的摆动,也可以选做被遮挡物。
为每个被遮挡物体分配一个id,保持id从0开始并连续,这样可以不需要额外的索引数据,每个物体只需要一个bit就可以记录可见性信息了。
遮挡物(Occluder)就是能起到遮挡作用的物体,除了要求是静态的之外还对材质有一些要求,像是半透或是AlphaTest的物件就不能做为遮挡体使用,可以通过检查材质类型过滤掉这些模型。另外像是比较小的物件,或是奇形怪状面又比较多的物体(比如树枝)也可以不作为被遮挡物,可以提高烘焙的效率。
植被基本都是AlphaTest的材质,会导致森林场景的剔除效果不佳,可以手动的给一些茂密的植被手动添加遮挡体。


给植物添加手动遮挡体
给遮挡物增加MeshCollider供后面做可见性检测。
3.数据烘焙
通过射线检测来判断Cell和被遮挡物中间是否有遮挡物。
首先在Cell和模型之间生成一些射线,通常的做法是在Cell和模型包围盒相对的面上取一些采样点相连为射线。采样点越多烘焙精确性就越高,烘焙速度也就越慢,可以根据Cell和被遮挡物的相对大小和距离来调整采样点的数量。


Cell与模型之间射线检测
随机取点容易出现漏采样的问题,均匀取点的方式不方便自由的调整采样点个数,可以使用低差异序列,即能自由的设定个数采样点分布又比较均匀。
随机取点,均匀取点,低差异序列
生成射线后利用Raycast来判断Cell和被遮挡物之间是否有物体遮挡就可以了。Unity2019中才有DOTS的多线程物理,我们使用的2018版单线程Raycast烘焙速度非常慢,烘焙一个场景通常需要一整晚的时间。
为了加快烘焙速度,我们借助了第三方的物理引擎来实现独立的多线程烘焙程序,将遮挡体、被遮挡体和空间格子数据导出,配台18核机器,把单场景的烘焙时间降低到了半小时以内。


跑满
如果场景变的更大,还可以组建多机器分布式来进行并行烘焙提高速度,另外还有知乎大佬分享的DXR加速烘焙的方法也可以参考 zhing2006:使用Unity DXR加速PVS烘焙。
4.运行时
文件大小
我们一个场景有7500+个模型,划分了20000+个Cell,算下7500*20000bit有18MB,同级别的场景还有一打那么多,文件大小需要优化。
当玩家在地图一边时地图另一边的小物件是不会被加载的,保留他们的剔除数据更没什么意义,所以可以把临近的小物件分为一组,当一组的物件都不可见时就不再记录这一组数据。


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

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


与内置遮挡剔除耗时对比
内存占用上实现流式加载,可以控制在0.5M以下。
剔除效率上也还算优秀,在静态物体上完全可以替代Unity自带的Umbra剔除。




一些缺点

不支持动态物体剔除,预计算遮挡剔除已经解决了大部分静态物件的可见性问题,对于剩余少量的动态物体可以使用OcclusionQuery方案来做,这种方案原理也很简单:绘制模型极简模(一般用包围盒),不着色(ColorMask 0),通过API查询这次绘制有没有像素通过Depth和Stencil测试来确定模型是否需要绘制。
这种方案的优点是市面上的手机基本都支持这个特性(gles3.0支持)。
缺点也有的:
    因为是GPU查询,再加上异步渲染,逻辑上要延迟1~2帧才能拿到查询的结果,在相机有大幅移动时会有穿帮,不过一般还能接受每个查询需要一次Drawcall,所以结果很有可能啥都没减Drawcall却变多了,使用时可以把一些模型的查询合并(比如只用在角色上,一个角色一次查询),并且只在有需要的区域开启Unity没实现这个功能,需要自己去实现每个平台的支持(Unreal有支持且在堡垒之夜有使用)
OcclusionQuery遮挡剔除查询
不支持实时阴影剔除,如果开启了实时阴影,会出现能看到影子但模型被遮挡的情况,如果把模型隐藏了,影子也会消失出现穿帮。
如果实时影子的方向不变,我们可以在烘焙的时候对影子单独烘焙一版剔除数据,如果阴影方向会实时变化这个方案就无法解决了。
预计算遮挡剔除方案原理和实现上都非常简单,在移动平台上性价比比较高,有很大的优化空间,比如包围盒可以改用OBB或是其它更精细的结构来提高烘焙精度,利用渲染光栅化替代射线检测实现像素级的遮挡烘焙等等。
如果大家有什么想法和建议,欢迎在评论区和我们讨论。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-4-20 18:45 | 显示全部楼层
话说为啥我测下来renderer.enable比设置layer要快,好像prepareSceneNode之类的时间会少点.........2017.4的版本
发表于 2021-4-20 18:52 | 显示全部楼层
感谢您的评论,从实现上看SetEnable的实现比SetLayer要复杂挺多,先入为主了缺少一些测试,简单测试了下两者开销差距不大,后续会做更多测试。另外SceneCulling在2018上为多线程实现,这块耗时的影响会少一些。
发表于 2021-4-20 18:55 | 显示全部楼层
还有用GPU光栅化的方式进行烘培。方法是先预渲染场景深度,然后再次渲染。将当前像素的深度与buffer的深度比较,如果小于则测试通过,写入另一个buffer。
 楼主| 发表于 2021-4-20 19:01 | 显示全部楼层
一个不用unreal的公司官方号发帖讨论unreal的技术[飙泪笑]
发表于 2021-4-20 19:05 | 显示全部楼层
某技术大牛说,射线拖不动
发表于 2021-4-20 19:07 | 显示全部楼层
预渲染的话直接使用obj id来获得那些non-occludee似乎更简单些,只是camera pos的采样要讲究,但是这个和ray tracing的问题一样的,都可能undersampling。
发表于 2021-4-20 19:12 | 显示全部楼层
Unity 自带的遮挡剔除是不是不适合像工厂车间的这种环境的应用没有很多明显的墙体遮挡的情况???
@网易游戏雷火事业群
发表于 2021-4-20 19:21 | 显示全部楼层
遮挡剔除都是偏保守的,没有明显墙体遮挡的场景剔除率都不会很高,各种方案之间差距不会很大,还要看场景做具体分析
发表于 2021-4-20 19:24 | 显示全部楼层
后面我采用了RT的方案,速度还行
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-21 00:35 , Processed in 0.097966 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表