近些年,随着画面表现要求越来越高,游戏渲染加入的细节也越来越多。本文将介绍两个项目常用渲染效果在Unity中的实现思路:地形贴图融合以及HBAO。
一、地形贴图融合
这个效果本质上是处理模型与地形之间在视觉上形成硬接缝的问题,这里先放一下效果开关的对比图:
效果开
效果关
个人认为,这个效果对于整体观感的影响还是非常显著的,而代价可以说是比较低廉。目前的做法是在渲染场景物体之前,把地形的albedo,smoothness,normal,以及depth,先渲染到三张1/4分辨率的rt上。其中,albedo和depth是必须的,其他量可以根据项目的性能要求和美术要求进行取舍。然后,在渲染场景物体时,根据当前点地形深度与着色点深度差(即视方向深度)作为权重来混合对应的量。
此效果是使用RenderFeature接口,LightMode接管pass的方式来实现的。需要注意的是,在地形Layer数量大于4的时候,unity会自动调用TerrainLitAdd shader把地形再渲染一遍,所以相关的计算要同时加在TerrainLit和TerrainLitAdd中,以及注意相关的宏设置。至于TerrainBase,只是会在远视距下低精度渲染地形,就不需要添加了。
先说一下做这玩意时候的几个坑点吧:
- 地形prepass深度图的类型应该为TEXTURE2D_FLOAT,使得编译出来的shader代码中使用highp精度采样(TEXTURE2D类型为mediump精度,在深度比较的时候精度不匹配会出现一条缝)。
- 注意reversed_z判断。这里由于效果区域比较小,深度比较写的不正确的话其实有点难直接看出来。
- 推荐使用视空间线性深度进行比较,调参比较直观,做各种过渡效果也会可控很多。
地形部分的修改没啥好说的,把原来的pass拷一份,把不要的计算去掉,输到自己声明的rt上就完了。接下来就在需要融合的场景物体shader中加入采样代码:
这里设置了一个逐材质的_BlendDiffer参数,可以逐材质调整深度差阈值,从而调节融合区域的大小。Smoothstep计算出来的权重用于参数柔和过渡效果。
二、HBAO
此效果属于是SSAO的升级版。虽然说性能开销在移动端上依然是不可忽视,但是确实能够极大提升场景的立体感,如下两图:
效果开
效果关
看过这个后处理的渲染效果之后,咱们来简单地说一下背后的理论和计算。实时渲染的所谓AO(ambient occlusion,环境光遮蔽),其实本质上是一种全局光照效果的简单近似。直观地说,它来自于一个很简单的生活观察,即物体与物体接触的一定区域内会显得比较暗(contact shadow,接触阴影)。在光线比较充足的情况下,在这块区域中甚至会染上另一个物体表面的颜色(color bleeding)。这种现象背后的原因自然是光线在空间中的无数次弹射,在渲染中对应的是近来很火的光线追踪技术。但是光追啊,好是好,就是太费了,移动端那点捉襟见肘的性能根本不够看。所以,大伙就把全局光照效果拆成好几种渲染技术,对效果和性能进行了不同的取舍,在实际使用的时候进行灵活组合。AO就属于是这一类效果中比较好实现,性能不错,对画面观感提高比较大的效果之一,性价比很高,所以得到了很普遍的使用。而这个效果本来就是一种近似,输出也比较低频(糊),所以各种AO技术基本都是在屏幕空间(screen space)进行降采样计算。
在这里,有必要说一下,材质面板的AO贴图,插在光照计算中的SSAO,以及后处理的SSAO三者之间的关系。
- AO贴图:美术在使用DCC软件做资产的时候,都有烘焙AO的这个选项,输出结果有点像是一个带扩散衰减效果的描边图。这其实是使用一个均匀环境光照对模型表面的遮挡信息进行预计算然后记录下来。这样做的好处是能得到更高质量的AO效果,而坏处就是贴图要多使用一个通道,并且需要对AO烘焙的参数进行标准化(半径,强度之类),如果后期想要统一更改的话会有较大的返工工作量。
- 插在光照计算中的SSAO:这是比较物理正确的一个做法。前文提到了,AO是间接光照产生的效果,对于直接光照是不应该有影响的。所以在进行光照计算的时候,AO值只需要并且只能和间接光项进行乘算。那也就意味着,我们需要在进行光照计算之前就把AO值给算出来。能比较顺理成章做到这一点的,只有把几何信息和光照计算分离的延迟管线。如果要在前向使用SSAO技术,最起码也要额外渲一遍场景深度(HBAO之类的还要多次采样重构法线,或者最暴力地额外渲一遍场景法线),有点得不偿失的感觉。(PS:第一个和第二个AO做法在目前unity的urp延迟管线中共存的话,光照计算是相比取最小值)
- 后处理SSAO:不太物理正确的做法,但是可以没有额外开销的情况下使用在前向管线。就是把SSAO放在光照计算之后,直接对相机颜色buffer进行乘算。这样做一般都会让场景变得偏暗(毕竟直接光照也被压暗了),但是“可以有SSAO效果”。目前看来前向可能比较适合使用AO贴图的做法(比较物理正确),但还是要看项目需求。
接下来,该说一下咱们标题的HBAO,是个啥玩意了。HBAO(Image-space horizon-based ambient occlusion),总之就是Nvidia在2008年提出的一个对SSAO技术的改进,介绍的PPT链接:https://developer.download.nvidia.cn/presentations/2008/SIGGRAPH/HBAO_SIG08b.pdf。过了这么多年,这个算法各种语言版本的实现可谓是铺天盖地。本着不重复造轮子的理念,核心代码直接从英伟达那边搬了过来。也不是什么复杂的东西,就先大概说一下它的计算逻辑,图片均截取自英伟达的PPT:
1.
首先,我们需要的输入是视空间深度图和法线图。
2.
P为当前着色点,以P为圆心往外均等分布采样方向(如图是四向,越多方向就越费,效果越好),同时给每个像素一个随机值使得采样方向有区别。
3.
然后在每一个方向上,我们就能得到一个一维的高度场。
4.
沿着采样方向,以设定的步进次数与AO半径计算出步进距离,记录每次步进的horizon angle。
5.
比较出最大的horizon angle为h,这个方向上的AO值计算公式如图。
6.
对各个方向计算的AO值取平均,则为当前点AO值。
7.
整体流水线。可以见到后续还有模糊操作(因为在角度旋转时使用了噪声)。
至于原文还提到的一些实现的小细节,譬如法线角度阈值,衰减公式等,这里就不再展开了,感兴趣的读者可以在网上搜到非常多详细的技术文章。接下来主要分享一些HBAO计算并入现有管线的一些需要注意的地方。
- URP管线自带一个SSAO的RenderFeature,使用的是传统的法向半球撒点判断来计算的,效率和效果都不太尽如人意。但是这个feature的存在可以让我们更方便地把HBAO计算带入管线,因为我们可以复用后续光照计算中SSAO定义好的一些宏和变量名,如截图所示:
只要把HBAO最终输出绑到这几个变量上就好了
当然要记得开启一下关键字
2. 既然我们最终都要进行一次模糊,而AO只是占用一个通道,那么如果管线中开启了屏幕空间阴影的话,可以通过ColorMask参数控制AO只写入RT的一个通道,然后与屏幕空间阴影共用一张RT。两个pass都走完之后,再对这张RT进行统一的模糊操作。
3. 如果对于降采样的AO效果噪点有点不可接受的话,可以对于HBAO这张rt单独进行一次TAA。在旋转方向和步进中加入jitter,然后和历史帧进行混合。目前测试使用的序列如下:
当然,AO效果单独走一个TAA什么的,后期优化的时候很容易就被砍掉,所以还是看项目需求。一般来说只要不是降采样太夸张,模糊了之后统一走一遍全屏抗锯齿最后效果应该也还行。
4. 如果一定要在前向管线用,又不想加入深度法线的prepass,可以把AO计算放在光照之后,然后加入一个MultiBounce操作。(来自一个更新的AO技术,GTAO的论文:https://iryoku.com/downloads/Practical-Realtime-Strategies-for-Accurate-Indirect-Occlusion.pdf。)
。A为计算出的AO值,ρ为场景albedo值。虽然这个方法本来不该是这么用的(捂脸),而且咱们前向也拿不到场景albedo值只能拿光照结果来凑数(甚至带着阴影),但总比直接黑一片要强。反正就是要视觉上减弱ao效果,强行乘上系数或者自己拟合曲线都显得有点不太靠谱,不如用人家大佬拟合的曲线,似乎还有几分道理(强行解释)。
5. 除了以上的方法,想要“稍微合理一点”地减弱AO,还看到过一种方案,伪代码大概是:
。最终可以出来一种物体接触部分一定半径内color bleeding的效果(有点SSDO的意思?)。至于这个性能开销值不值得,效果好不好,那就是见仁见智了。
这次的分享到此就结束了。本文权当抛砖引玉,如果上文有任何错误的地方,或者有更好的做法,还请不吝赐教,感谢您的阅读!
欢迎加入我们!
感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com