URP | 后处理-SSAO效果
内容偏多

使用版本 Unity 2021.3.15
Unity 2022.1.0
目的
学习AO相关的基础知识,AO计算都有那些?
实现的方法和原理,都需要注意什么?
URP管线下获取Depth Normals方法?

原理
AO
Ambient Occlusion(以下简称"AO")是一种基于全局照明中的环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的光照强度系数的算法. AO描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比, 因此使得渲染的结果更加富有层次感, 对比度更高.
遮蔽(Ambient Occlusion,下面简称AO) 是计算机图形学中的一种着色和渲染技术,用来计算场景中每一点是如何接受环境光的。例如,一个管道的内部显然比外表面更隐蔽(因此也更暗),越深入管道光线就越暗。环境光遮蔽可以被看作是光线能到达表面上每一点的能力的数值。[1]在拥有开放天空的场景中,这是通过估算每个点的可看见天空的大小来完成的;而在室内环境中,只考虑一定范围内的物体,并假设墙壁是环境光源。处理结果是一个漫反射、非定向的着色效果,并不会形成明确的阴影,只是能让靠近物体及被遮蔽的区域更暗,并影响渲染图像的整体色调。环境光遮蔽常被用作后期处理。 与局部方法如Phong着色法不同,环境光遮蔽是一种全局方法,意味着每个点的照明是场景中其他几何体的共同作用。然而,这只是一个非常粗略的近似全局光照。仅通过环境光遮蔽得到的物体外观与阴天下的物体相似。“
AO可以理解为一张贴图,是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光、飘和阴影不实等问题,解决或改善场景中缝隙、褶皱与墙角、角线以及细小物体等的表现不清晰问题,综合改善细节尤其是暗部阴影,增强空间的层次感、真实感,同时加强和改善画面明暗对比,增强画面的艺术性。
单独的物体也是有AO贴图,处理一个物体之间的阴影物体。

计算得出的AO Texture,越黑的地方代表环境光越不可能照到该点,该点是环境光的影响就越小。
没有SSAO

但是这样的解决方法,可以解决单物体自己,不能解决两个物体之间。
为什么了解决这个问题,开发出屏幕空间环境光屏蔽(Screen Space Ambient Occlusion,SSAO)
AO计算公式
处理

SSAO计算方法
SSAO背后的原理很简单:屏幕上的每一个像素,我们都会根据周边深度值计算一个遮蔽因子(Occlusion Factor)。这个遮蔽因子之后会被用来减少或者抵消片段的环境光照分量。遮蔽因子是通过采集片段周围球型核心(Kernel)的多个深度样本,并和当前片段深度值对比而得到的。高于片段深度值样本的个数就是我们想要的遮蔽因子。
计算方法

基于Ray-Tracing的AO计算模型. 红色的射线表示V = 1, 绿色的射线表示V = 0.

这样我们需要对屏幕上的每一个像素进行采样,所以SSAO很费。并且出现一些其他问题。
出现摩尔纹

所以需要进行模糊,这里使用双边滤波(Bilateral filter)来解决
双边滤波是一种非线性滤波器,它可以达到保持边缘、降噪平滑的效果。和其他滤波原理一样,双边滤波也是采用加权平均的方法,用周边像素亮度值的加权平均代表某个像素的强度,所用的加权平均基于高斯分布[1]。最重要的是,双边滤波的权重不仅考虑了像素的欧氏距离(如普通的高斯低通滤波,只考虑了位置对中心像素的影响),还考虑了像素范围域中的辐射差异(例如卷积核中像素与中心像素之间相似程度、颜色强度,深度距离等),在计算中心像素的时候同时考虑这两个权重。
获取到AO贴图

得到AO贴图后,事情就轻松很多了,只需要一一对应游戏画面与AO贴图,改变游戏画面上每一个像素的GI值,就可以实现SSAO效果
不同的AO效果
SSAO
HBAO
HDAO
本次只是实现SSAO效果

SSAO理论知识
实现原理
先利用深度图和屏幕UV坐标反算出当前顶点世界坐标的位置,
再使用沿着法线正方向半球内的随机点进行采样, 获得新的坐标点.
两个点进行比较, 判断是否被遮挡, 然后加权处理获得AO.
AO这时候充满噪点, 需要高斯模糊一下.
最后和场景颜色RT进行混合叠加.
SSAO原理流程图

获取深度我们是可以获取到的,法线深度URP是没有的我们需要自己获取。
Depth + Normals 实现
unity URP 管线官方不支持深度法线,我们需要自己实现深度法线,
URP | 后处理-描边 - 哔哩哔哩 (bilibili.com)
上面我们制作描边的时候,也是使用SSAO的深度法线,这次我们单独实现,深度法线。
Unity URP 不支持深度法线

使用自定义渲染器功能来实现
Scriptable Renderer Feature 自定义渲染器功能
创建一个脚本 DepthNormalsFeature 继承 ScriptableRendererFeature
这里有两种方法,
第一种方法
第二种方法

URP默认SSAO的法线效果 和我们第二种方法一样。

扩展 他们两种方法有什么不同吗?有什么区别?
DepthNormalsPass 是深度和法线一起获取的,在一起需要进行解码使用。
DepthNormals 只是获取像素深度的法线信息。

SSAO | AO实现
设置渲染管线
我们当前准备好渲染管线 ,
这里是使用基础的颜色来测试管线是否正确
Shader部分
Volume
效果

获取世界空间位置,法线,切线数据
获取世界空间中的顶点的位置
我们定义一个库用来放放一些我们自定义的函数
这个函数是输入屏幕UV坐标,从深度中转换为齐次剪裁空间坐标(ndc),使用矩阵转换成世界空间。
那这里我们需要获取两个数据,
深度 _CameraDepthTexture
转换世界空间的矩阵 sampler_CameraDepthTexture
深度我们URP管线自带,勾选就可以调用, 转换世界空间的矩阵我们要在管线中获取,传到Shader中。
Shader中 定义一个矩阵变量,等等我们在后处理管线中传入。
SSAO.cs中
我们需要获取摄像机,在由摄像机来生成矩阵,传到我们的Shader
在SSAOPass 中我们前定义一个摄像机的变量
Execute 中进行初始化
定义一个函数 ,这个函数用来后面传递控制变量。
在渲染里调用这个函数,
当前完成
回去到Shader中我们调用 函数求处顶点位置。
效果

2. 获取世界空间中法线
我们需要需要深度法线来计算出像素在世界空间下的位置信息。
深度Unity管线为我们提供了。
这里我们使用上面的第二种方法。
在管线中增加

在库中输入我们的 贴图
在定义一个函数
Shader中我们调用函数
效果

3. 获取世界空间中切线
切线就是我们需要生成的随机向量部分,
在库里定义两个函数
shader中调用
注意:这些都是在世界空间下计算的,后面需要转换成View空间
效果
这就是我们生成的一些随机噪点

都获取到了,那计算我们需要的副切线,构造一个矩阵。
矩阵准备好了.
注意:wTan = cross(wBin, wNor); 还需要cross在计算一次。
计算AO效果
定义两个变量,一个变量是我们的ao 效果,一个是我们控制噪点多少。
定义一个循环遍历所有采样点。
使用随机向量生成一个半球向量 offDir 通过 scale控制范围
在HLSL库中
增加GetRandomVecHalf 函数
这里增加控制半球控制大小, 世界空间转换裁剪空间,这里需要 wPos 世界空间顶点位置和矩阵。
这里我们需要两个 矩阵,
v_Matrix 视图矩阵
是世界空间到观察者空间的变换矩阵。它描述了相机的位置、朝向和上方向等信息,可以用于将场景从世界坐标系转换到相机坐标系。
p_Matrix 投影矩阵
是将相机坐标系中的场景投影到屏幕坐标系的变换矩阵。它描述了相机的视锥体、投影方式和屏幕尺寸等信息,可以用于将场景从相机坐标系转换到屏幕坐标系。
SetMatData函数 使用同样的方法
采样深度
SampleSceneDepth函数获取屏幕坐标对应的深度值,然后通过LinearEyeDepth函数将其转换为线性深度。该函数使用了_ZBufferParams参数,它包含了一些与深度缓冲相关的信息,如深度缓冲的近、远裁剪面等。
采样AO
这里我们需要获取默认的深度值
首先定义一个sampleZ变量,表示采样点的深度值。然后通过计算sampleDepth和sampleZ之间的距离,并将其与_Radius参数相除,得到一个范围检查的值rangeCheck。这个值用于控制采样点的范围,使得只有距离当前像素一定范围内的采样点才会对AO值产生影响。
接下来的代码中,通过判断sampleDepth是否小于depth - 0.08来进行自遮蔽检查。如果采样点比当前像素更靠近观察者,则返回1,否则返回0。这个值用于控制自遮蔽的影响。
最后,将rangeCheck、selfCheck、_AOInt和weight相乘,得到一个权重值,并将其乘以1(即对AO值产生1的影响),最终得到AO值。
采样深度
这里在库定义一个获取深度的函数
在Sahder中定义
输出AO
我们在Shader部分输出ao效果
现在的效果是白色的,啥都没有。
我们的SSAOVolume还没有关联属性
SSAOVolume
在这里我们关联属性
RendererFeature
关联属性,我们把属性都放到 SetMatData()函数中
增加完代码
效果

AO效果就计算完成,
我们增加一个判断,控制开关AO效果,方便对比。
效果

AO部分就算完成。
SSAO | 模糊算法
SSAO我们需要多个Pass
AO
水平模糊
垂直模糊
AO + 原图
整理Shader
我们需要4个pass这里不适合放到一起,为了方便,我们 重新创建 hlsl文件,放我们的不同的执行过程。
Shader 只执行我们渲染设置,
Hlsl 文件执行计算,

这里重新创建两个hlsl文件
AOpass
BlurPass

Shader
SSAOFunLib.hlsl
AOPass
BlurPass
这个部分查看
URP | 后处理-模糊算法总结 - 哔哩哔哩 (bilibili.com)
在 Volume这里增加上控制模糊的变量
Volume
主要是在管线里处理。
RendererFeature
这里我们来处理模糊
需要定义两个临时RT用来处理模糊
获取临时RT
注意:这里RT1 和R2是储存是不一样的,RT2是一半是我们中间过程,RT是输出结果所以使用高精度。
释放临时RT
在else里面增加逻辑
这里在执行一遍AO效果,装修完储存到 bufferid1中,
cmd.SetGlobalTexture("_AOTex", bufferid1) 把AO效果传递到Shader中进行计算。
执行两次 水平一次,垂直一次,输出查看效果

AO原图混合
我们把处理的结果传入到Shader
这样就可以实现完成了。

可以改变阴影的颜色

这个效果其实还可以在模糊一次,
SSAO.cs
Volune
总结
在实现AO效果的时候比较麻烦,平常是有两种方法,第一种方法都是在 View视角下计算,这里使用第二种方法,在世界空间下计算完成,在转换成View视角,
Vertex position vectors in view space

Vertex normal vectors in view space

Sample vectors in tangent space

Noise vectors in tangent space

2. 计算AO具体流程,使用UV计算出需要的 位置,法线,切线构建矩阵, 遍历所有的采样点,使用随机向量计算出半求,扩展半球范围大小。
计算完成转换到裁剪空间,根据深度判断是否在阴影范围。
3. 在管线里使用摄像机获取矩阵传递给Shader
_VPMatrix_invers 视图投影矩阵的逆矩阵
_VMatrix 视图矩阵是将场景从世界坐标系转换到相机坐标系的变换矩阵
_PMatrix 投影矩阵则是将相机坐标系中的场景投影到屏幕坐标系的变换矩阵
