首页>热点 > 正文
每天一个shader技巧Day4 - 屏幕空间下的全局光照SSAO(参考了Games202)
2023-02-26 01:03:57来源:哔哩哔哩

什么是全局光照

全局光照 = 直接光照(从光源出来) ➕ 间接光照(称为次级光源,被直接光源照射后经过反弹的地方)

全局光照算法分为世界空间算法和屏幕空间算法。今天要介绍的是屏幕空间下的一种全局光照算法 Screen Space Ambient Occlusion(SSAO)


(资料图片仅供参考)

什么是屏幕空间

我的理解就是当所有渲染流程跑完后,最终输出的那个图像所在的空间,就是屏幕空间。也就是说我们没有大部分世界空间下的信息,比如两个点之前的距离之类的东西可以使用。所以屏幕空间下的全局光照算法可以理解为对图像做一个后处理来完成对间接光照的模拟。

什么是环境光遮蔽(AO)

从名字可以很直白的看出,就是描述一个点所收到的环境光照被周围物体遮蔽了多少的一个量。可以观察一下现实生活中有褶皱,或者拐角的地方,是不是看起来都会暗一些,这就是因为这些地方受到的环境光照会被更多的遮蔽,所以看起来暗了一些。

Screen Space Ambient Occlusion

知道了什么事屏幕空间,什么是AO后,我们的目的就是通过后处理的手段对图像进行这个环境光遮蔽的计算。那么怎么算呢,这就得搬出我们大名鼎鼎的渲染方程了。

简单说下这个方程,其实描述的就是一个着色点在受到一个半球上的所有入射光影响后最终的着色结果,也就是。

= p点接受的入射光 (更具体的定义可以看看辐射度量学)

= 描述入射光在p点是如何进行反射的方程(漫反射,折射,镜面反射)

= p点对于入射光的可见性

但是在屏幕空间下,我们无法知道渲染方程中所需要的任何信息,每个点的入射光,反射方程,可见性等都无法从屏幕空间直接知晓,那咋办呢?发明这个方法的人,做了两个大胆的假设:

入射的间接光是恒定的,也就是是一个常数

每个着色点都只会发生漫反射,没有镜面反射,那么根据之前推导的漫反射这也是一个常数为(因为和入射方向,观察方向都没有关系了)

那这样的话,我们要关注的就只有可见性(V)这一项了。

我们先看我能想到的最简单的方法,那既然和都是常数了,那么我们可以直接提到积分外面就变成了,变成了现在这个样子,但是可见性这个函数的积分结果其实应该还需要做一个加权平均需要除以一个,变成,但是为什么这样做其实有点莫名其妙,下面看看闫老师的做法

用经典拆分方法split sum可将积分变成如下形式。

这个近似方法在右边的函数的积分域很小,或者函数很平滑的时候是十分准确的,现在的情况是满足这个条件的。这样拆之后,左边就会,右边会变成,最后变成,结果是一样的。但是为什么上面连cos这个项都给当成微元的一部分给拆出来了呢,这不是和split sum方法不太一样吗,其实如果单独看这一项的话,它的物理意义其实是半圆上单位立体角的面积对底面圆的投影面积,这样的话就可以理解成对投影到底面圆的面积的积分。同时也在物理意义上解释了为什么的积分是。下面直接上闫老师的图

如何获得可见性

上面一通分析一通假设后,我们要解决的问题就剩下一个了,如何在屏幕空间下获得某个着色点对其他点发射的间接光的可见性呢,自然想到了唯一存储有几何信息的深度缓冲了(Z-buffer)。有了深度信息后怎么办呢,我们当然不可能去真的积分半球上所有的入射光对该着色点的可见性去求出这个值(并且在现在的情况,我们也没有法线信息,也就是cos项无法计算),所以就自然想到了采样的方法。先上理论:

我的理解就是,在每个采样点(白点)的周围撒一些点(红点和绿点),然后用这些点的深度和深度缓冲中的深度进行比较,如果比深度缓冲中记录的深度要深,那就是该采样点对该着色点没有间接光贡献。但是!我们实际不需要考虑整个球的情况,因为任何从与法线方向相反的半球打过来的光线,都不会对该着色点有贡献,只不过我们现在没有法线信息,所以需要在一个球上撒点。那这样的话,就可以得出如果撒的点超过一半都是对着色点有光照贡献的(也就是绿点),那就不需要考虑AO的情况

然后这样做的话还有一个小瑕疵,那就是红色虚线里的那个点,那个点在的深度是比记录在深度缓冲里的深度要深,所以是被认为对着色点没有间接光贡献的,但是实际应该是有的,那怎么办呢,答案就是不管!(都采样了还在意这点误差干嘛,图形学里看起来是ok的那它就是ok的)

那么做法就很清晰了(正向渲染做法)

需要一张从相机出发的记录了场景深度的深度图(在相机空间下的深度信息是线性的)

在第二个pass下使用像素点的采样点坐标和深度信息进行相机空间下该像素点坐标的重建(也就是从屏幕空间反推出相机空间下的位置,下面会提供一种思路),然后有了该点相机空间下的位置,有了后我们可以生成随机向量然后就有了一系列采样点,再将这一系列采样点的x,y坐标给转回屏幕空间下,接着用该坐标对上一个pass的深度图进行采样获得深度,然后用获得的深度和生成的采样点的深度进行对比(被遮挡的点返回0,没被遮挡就是1),接着判断一下需不需要计算AO,需要的话就加起来除以然后除以采样点总和,得到AO的值,然后加以运用便可。

这里介绍一种屏幕空间重新计算相机空间位置的方法:首先我们知道屏幕空间下的坐标是在相机空间下经过了投影变换->透视除法->视口变换后得到的结果,那么我们只需要逆着来就行了。首先将屏幕空间的坐标换回透视除法后的NDC坐标下,构建如下向量

这里z值说一下,z值就是0-1的非线性的深度信息

现在我们就有了NDC空间下的坐标系了,xyz的值都在-1,1的范围内。然后我们再左乘一个透视投影矩阵的逆矩阵再再除以该结果的W分量即可。推导过程如下

联立一下

这里的未知量是clip.w

但是我们知道view.w = 1.0,那么利用这个我们可以得出:

进而得出

这样有了相机空间下的坐标后就能进行后续的计算了(其实有了这个位置信息后还能重建出法线用来生成采样点,不过这个暂时先不说了)

存在的问题

这样做出来的AO图会有一个问题,看图(拿别人的,左边是这个方法做出来的,右边是正确的)

为什么会这样呢,这是因为在计算后面墙壁上的点的AO的时候,在相机空间下撒的采样点会被认为是被前面的佛像遮挡了,从而产生了错误的AO效果。通过引入法线计算的HBAO可以解决,但是下次再说。这次先介绍其他的方法。

我们可以添加一个范围检测机制,我们定义一个长度,如果相机空间下的深度减去深度缓冲中的深度的绝对值远大于这个长度,那么对该点的AO贡献就为0,如果小于等于这个长度,那就说明对AO有贡献,如果略大于的话就说明有一点贡献,可以用个smoothstep函数来做一个平滑过度。最后在相加的时候乘以这个范围检测返回的值即可得到右边的效果。

SSAO的原理做法啥的到这就差不多结束了,下面用shadertoy模拟一下ssao算法生成的AO图

可以看到一堆问题对吧,不该有ao的地方有ao,然后很多噪点。(毕竟只是一个模拟,就这样吧,噪点可以用模糊去解决),知道个思路就行,看看代码吧。

代码如下

标签: 范围检测 镜面反射

相关新闻