GeoAO:一种快速的环境光遮蔽方案

GeoAO:一种快速的环境光遮蔽方案

【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。

今天推荐的项目来自UWA开源库:https://lab.uwa4d.com/lab/5b561f86d7f10a201fd8422e


一、概览

AO(Ambient Occlusion)是一种基于全局照明中的环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的光照强度系数的算法。AO描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比,因此使得渲染的结果更加富有层次感,对比度更高。

该项目介绍了一种快速的基于顶点的AO算法,将AO看做模型顶点的一个属性,通过深度相机对模型多位置的采样,结合模型顶点的深度校验来计算对应的AO值。该方法相比较于SSAO,是一种比较小巧快捷的简易方法。通过对该项目的学习,读者可以初步了解AO算法的思想,也可以学到该算法的具体简化策略。

二、原理概述

总体来说,该算法可以分为两个步骤:

1.采样:通过改变深度相机的位置并对目标物体渲染,把各个位置获取到的深度图保存下来作为采样数据。
2.计算AO:将采样数据通过深度校验来识别出需要遮蔽的模型顶点并按照一定权重累加遮蔽值,以达到阴影效果。

该方法并不是一个实时计算的方法,而是一个一步到位的方法,本质上是在运行刚开始时就计算好了一个带有AO的材质并替换掉模型原有的材质。所以它并不会随着光照的改变而改变,通俗来讲就是“把容易变暗的地方的颜色画得暗一点”。

三、具体实现

从geoAO.cs来看,所有的工作都是在Start()中完成的,如下图,而采样和计算AO的主要逻辑大都在DoAO()中。

1. 采样
该步骤需要一个深度摄像机来采样深度图,摄像机的创建在CreateAOCam()中完成:

该摄像机从创建开始就是隐藏状态(148行),因为我们并不需要OnRenderImage()每帧都执行,我们想要它“按需执行”,确切来说是为了让Blit函数“按需执行”(如下图),那么我们可以通过把相机隐藏,用Camera.Render()来手动调用OnRenderImage()。

除此之外,我们也注意到为了模拟平行光效果而把摄像机模式改成了正交(150行),且开启了深度模式(158行),这时一个深度相机就已经准备完成了。

采样环节最核心的环节如下图,该部分在DoAO()函数中:

首先,该for循环的循环次数对应着采样次数,也就是最终渲染出来的深度图样本数量,而样本个数决定了AO的精度高低:

在每一轮采样过程中,AO摄像机(AOCam)作为一个单独的深度摄像机,会到达一个新的已经计算好的位置(256行),然后看向模型(257行),接着把准备好VP矩阵传给Shader,最后渲染。

这里可以注意到276行就是在手动调用OnRenderImage()函数,但其实计算AO这一步就是在276行在Shader(VertexAOCompute.shader)中进行的,相当于每一轮循环都会做一次AO运算,然后每轮得出的AO值最终会累加在一起附给一个新的材质。

2. 计算AO
AO的计算在VertexAOCompute.shader中。下图是片元着色器部分:

判断到底哪些点需要有“遮蔽”的部分就是120-121行。从118行的注释我们也可以知道,变量o“决定了遮蔽会有多暗”,而120行告诉我们,对于vertex.z和z相差过大的点,也就是被遮蔽的点,o不会参与累加(121行),因为o越大模型就会越亮。最后的累加会带上权重,只不过每次采样的权重都一样而已(124行)。

而上一步计算出的AO(65行)作为上图中的_AOTex参与了最终图像插值时的权重值(67行)。最终渲染出的图像会附着在一个新材质球上,而该材质球会替换掉模型原有材质(如下图),至此AO效果就添加到了模型上。

3. Tips
该工程计算AO的过程很直接地体现出了AO算法的思想。

概括来讲,环境遮挡被定义为从表面上某一点能够逃离场景的射线的比例。上文提到的每一轮AO采样计算相当于图中每条绿色的射线,而判断深度是在看该射线是否会被遮挡,最后的累加就是在计算比例,而这里通过样本均值来估算积分的过程也是蒙特卡洛积分思想的体现。

四、性能分析

本次测试用的是低端机型OPPO A32(4G RAM),测试分为开启和不开启GeoAO这两种情况。首先,下图显示的是开启情况下的FPS均值和GPU耗时情况:

下图是不开启该插件的情况:

可以看出即使在低端机型上,该工具的开启也没有为GPU和FPS造成压力,相反的是,GPU耗时反而比不开启的情况下要低,这是因为该插件只是在Start()函数中置换了一下物体材质,而新材质比不开启时的材质(Standard)更轻量化的缘故。

提示:并不建议在游戏中途加上该效果,因为它会由于Shader.CreateGPUProgram和AO计算而引起卡顿。其中,Shader.CreateGPUProgram的耗时可以进一步通过收集变体并预热的方式排除,可以参阅《一种Shader变体收集和打包编译优化的思路》;而剩下的AO计算部分放在场景加载阶段完成则是可以接受的开销。

综上所述,该插件是一个比较轻量级且效果尚可的方案。

作者发布项目时的介绍使用的是英文编写,为了方便大家阅读,UWA开源库已将其翻译成中文版本,欢迎大家共同学习。


今天的推荐就到这儿啦,或者它可直接使用,或者它需要您的润色,或者它启发了您的思路......