SSAO介绍
日常生活中一些角落里、光线很难到达的地方会很暗(缝隙),AO就是用来模拟这些效果的。
在图形学中间接光照比较耗性能,有一种简单的方案是直接假设物体四面八方接收到的环境光是一致的(粗暴的实现是直接加上一个光使画面变亮),显然是不符合我们对现实的观察,而使用AO可以模拟计算那些环境光由于被遮蔽而有所损耗的部分,使画面更加接近真实。
AO
环境光遮蔽,全称Ambient Occlusion,是计算机图形学中的一种着色和渲染技术,模拟光线达到物体的能力的粗略的全局方法,描述光线到达物体表面的能力。
SSAO
屏幕空间环境光遮蔽,全称Screen Space Ambient Occlusion,一种用于计算机图形中实时实现近似环境光遮蔽效果的渲染技术。通过获取像素的深度缓冲、法线缓冲来计算实现,来近似的表现物体在间接光下产生的阴影。
SSAO历史
AO这项技术最早是在Siggraph 2002年会上由ILM(工业光魔)的技术主管Hayden Landis所展示,当时就被叫做Ambient Occlusion。
2007年,Crytek公司发布了一款叫做屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)的技术,并用在了他们的看家作孤岛危机上。
SSAO原理
- 计算深度、法线缓冲
- 深度->像素坐标
- 法线->法向半球随机向量
- 计算像素随机后的坐标(多次采样)
- 获取随机后深度并比较
- 判断加权AO
- 后期(模糊等)
AO核心:for循环
- 每一次for循环都会在法线半球中随机取一个向量,根据这个向量求出对应点的深度值,和当前深度做比较,如果大于说明未遮挡则舍去,小于认为有遮蔽,算进加权中,根据结果合成AO,然后加上一些后期处理优化效果。
深度缓冲
深度缓冲中的depth值用于当前视点下的场景的每一个像素距离相机的一个粗略表达,用于重构像素相机空间中的坐标(Z),来近似重构该视点下的三维场景。
深度缓冲的目的:得到场景中物体与相机的远近关系
法线缓冲
相机空间中的法线信息,用于重构每个像素的“法线-切线-副法线”构成的坐标轴,用于计算法线半球中的采样随机向量(随机向量用于判断和描述该像素的AO强度)。
法线半球
- 黑色表示我们需要计算的样本
- 蓝色向量表示样本的法向量
- 白色、灰色为采样点(很明显,采样点的多少影响最后的渲染的效果),其中灰色表示被挡采样点(深度大于周围),据此判断最终Ao的强度
- 图2表示法向球形采样(后背抛弃),原因是该方式采样导致平整的墙面也会显得灰蒙蒙的,因为核心中一半的样本都会在墙这个几何体上
补充:从渲染方程看
参考GAMES202 L8
将Visible项从积分中拆出来,后面那部分积分都可看做常数
为什么选择cosθdw积分(使得空积分部分更好算)
这里对Visible项的积分其实就是求平均遮挡程度
SSAO算法实现
下面以Unity Builtin管线为例
获取深度&法线缓冲数据
Builtin 中需要对相机设置深度纹理模式来获取深度和法线缓冲数据
1 | //获取深度&法线缓存数据 |
使用Unity自带Api获取深度和法线
采样使用的uv是屏幕空间的uv,
具体可以参考入门精要13.1
1 | //获取深度法线图 |
注:算法实现过程环境:
- Unity版本:Unity 2019.3.5f1;
- 场景中的项目为透视模式;
- 相机渲染路径为:Forward,如果设置为Deferred渲染路径,则由对应的g-buffer生成,在shader中作为全局变量访问;
- 使用OnRenderImage()来处理后期,进而实现SSAO;
重建相机空间坐标
下面使用从NDC空间中重建的方法(计算量和复杂度偏高),入门精要13.1有详细过程
1 | //第一步:获得相机空间下的像素方向 |
构建法向量正交基(TBN)
1 | fixed4 frag_Ao(v2f i) : SV_Target{ |
怎么求切线和副切线:
- 归一化的法线和随机向量进行点乘,得到投影在法线方向的标量长度a
- 使用归一化的法线乘以长度a,得到法线方向上的向量
- 使用随机向量randvec减去向量a,得到切线
- 对切线和法线进行叉乘操作,得到副切线,完成正交基的构建
AO采样核心
第一步:在C#部分生成采样核心
1 | private void GenerateAOSampleKernel() |
第二步:从每一个采样位置的深度变化程度累计ao
1 | fixed4 frag_Ao(v2f i) : SV_Target{ |
补充内容
关于_ScreenParams分量意义
分量 | 意义 |
---|---|
x | 1.0(或-1.0,如果当前使用翻转投影矩阵进行渲染) |
y | 相机的近平面 |
z | 相机的远平面 |
w | 1/FarPlane,在上面代码中用于归一化 |
UNITY_MATRIX_P和unity_CameraProjection有什么不一样看这篇:https://zhuanlan.zhihu.com/p/114729671
SSAO效果改进
随机正交基(增加随机性)
通过引入随机变量或者采样噪音贴图来使得法向半球的正交基不一致(原先使用float3(1,1,1)代替随机向量)
利用uv至采样一张Noise贴图(如右图4x4像素(可选择其他尺寸)的Noise贴图),或者随机向量
1 | float2 noiseScale = _ScreenParams.xy / 4.0;//噪声纹理的缩放 |
并在C#中传入噪声贴图
1 | ssaoMaterial.SetTexture("_NoiseTex", Noise); |
AO累加平滑优化
-
差距巨大的深度值,如果差值大于某个阈值,可以认为是天空(无穷远),则不需要遮蔽
1
float range = abs(randomDepth - linear01Depth) > _RangeStrength ? 0.0 : 1.0;//深度变化
-
同一平面深度值由于精度问题造成的深度变化,自我遮挡问题
1
float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0;//深度变化过大的归零
-
根据距离Smooth权重,一般是原理当前像素的分配权重会小一点
1
float weight = smoothstep(0, 0.2, length(randomVec.xy));//针对不同的采样方向分配权重
-
双边滤波模糊(C#部分)
1
2
3
4
5
6RenderTexture blurRT = RenderTexture.GetTemporary(rtW, rtH, 0);//获取模糊渲染纹理
ssaoMaterial.SetFloat("_BilaterFilterFactor", 1.0f - bilaterFilterStrength);
ssaoMaterial.SetVector("_BlurRadius", new Vector4(BlurRadius, 0, 0, 0));//x方向
Graphics.Blit(aoRT, blurRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);
ssaoMaterial.SetVector("_BlurRadius", new Vector4(0, BlurRadius, 0, 0));//y方向
Graphics.Blit(blurRT, aoRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);
对比模型烘焙AO
三维建模软件烘焙AO方式
通过三维建模软件(如3DMax),设定好渲染参数,对模型(单一选择模型实体),烘焙AO到纹理。
优点:
- 单一物体可控性强(通过单一物体的材质球上的AO纹理贴图),可以控制单一物体的AO的强弱;
- 弥补场景烘焙的细节,整体场景的烘焙(包含AO信息),并不能完全包含单一物体细节上的AO,而通 过三维建模软件烘焙到纹理的方式,增加物体的AO细节;
- 不影响其(Unity场景中)静态或者动态;
缺点:
- 操作较其他方式繁琐,需要对模型进行UW处理,再进行烘焙到纹理;
- 不利于整体场景的整合(如3DMax烘焙到纹理,只能选择单一物体,针对整体场景的处理工作量巨大);
- 增加AO纹理贴图,不利于资源优化(后期可通过其他纹理通道利用整合资源);
- 只有物体本身具有AO信息,获取物体之间的AO信息工作量巨大(不是不可能)。
游戏引擎烘焙AO方式(Unity3D Lighting)
通过Unity的Lighting功能(主菜单/Window/Rendering/Lighting Settings)进行整体场景的烘焙,AO信息包含于此。
优点:
- 操作简易,整体场景的烘焙,包含AO的选择;
- 不受物体本身的UW影响,Unity通过Generate Lightmap UVs生成模型第二个纹理坐标数据;
- 可生成场景中物体与物体之间的AO信息;
缺点:
- 缺少单一物体的细节(可调整参数提高烘焙细节,但换之将增加烘焙纹理数量和尺寸,以及烘焙时间);
- 受物体是否静态影响,动态物体无法进行烘焙,获得AO信息。
SSAO方式
优点:
- 不依赖场景的复杂度,其效果质量依赖于最终图片像素大小;
- 实时计算,可用于动态场景;
- 可控性强,灵活性强,操作简单;
缺点:
- 性能消耗较之上述2种方式更多,计算非常昂贵;
- AO质量上要比较离线式烘焙(上述2种)不佳(理论上)。
SSAO性能消耗
主要方面
- AO法向半球的随机采样
- 双边滤波的多重采样
AO核心采样消耗说明
主要核心为计算AO随机法向半球的采用点,并加以半段计算AO权值
-
使用For结构代码进行半球随机向量的采用,If、For等对于GPU计算性能上并不友好;
-
采用数的数量(上图中的_SampleKernelCount,针对For循环的次数),过低的采用数得不到好的结果;
- 以64为例,1334 x 750 的分辨率,每个像素计算循环64次,合计1334 * 750 * 64次AO核心计算;
-
循环体重的采样,同意以64为例,每个像素计算需要采样64次来求得屏幕深度值法线值。
滤波采样消耗说明
双边滤波(Bilateral Filter),为保证边缘不被模糊,采样基于法线的双边滤波
-
C#后期脚本中,Blit两次(横向和纵向),合计调用两次滤波渲染Pass;
-
单一滤波渲染Pass中,多重采样;
- 包括7次主纹理的采用和7次屏幕像素的法线信息的采用,屏幕中每个像素合计14次纹理采用。
- 包括7次主纹理的采用和7次屏幕像素的法线信息的采用,屏幕中每个像素合计14次纹理采用。
扩展链接
- 【环境遮罩之SSAO原理】 https://zhuanlan.zhihu.com/p/46633896
- 【游戏后期特效第四发 – 屏幕空间环境光遮蔽(SSAO)】 https://zhuanlan.zhihu.com/p/25038820
- https://www.iquilezles.org/www/articles/ssao/ssao.htm
- 【learnopengl-cn】 https://learnopengl-cn.github.io/05 Advanced Lighting/09 SSAO/
- 【屏幕空间环境光遮蔽(SSAO)算法的实现】 https://blog.csdn.net/qq_39300235/article/details/102460405
- 【Unity Shader-Ambient Occlusion环境光遮蔽】 https://blog.csdn.net/puppet_master/article/details/82929708
- 【SSAO 与深度重构】 https://wiki.jikexueyuan.com/project/modern-opengl-tutorial/tutorial46.html
- 【Ambient Occlusion(AO)使用指南】https://zhuanlan.zhihu.com/p/150431414
- 【图形和滤波】 http://www.ruanyifeng.com/blog/2017/12/image-and-wave-filters.html
- 【双边滤波】 https://blog.csdn.net/puppet_master/article/details/83066572
- 【GAMES202 L8】
https://www.bilibili.com/video/BV1YK4y1T7yY?p=8 - 【Unity Shader-Ambient Occlusion环境光遮蔽(AO贴图,GPU AO贴图烘焙,SSAO,HBAO)】
https://blog.csdn.net/puppet_master/article/details/82929708 - 【SSAO与HBAO与HDAO-真正的区别是什么?】
https://cn.bikiniclinic.net/901505-ssao-vs-hbao-vs-hdao-JBPNNX - 【Ambient Occlusion环境遮罩1】https://zhuanlan.zhihu.com/p/43105945
- 【UE4 Mobile GTAO 实现(HBAO续)】https://zhuanlan.zhihu.com/p/145339736
- 大佬笔记
- https://www.yuque.com/yikejinyouzi/aau4tk/gs7n7d#sJF8T
- https://www.yuque.com/6527chen/ldyt32/sh3p6g#5ad7f5a8
- https://www.yuque.com/xiaohen-ecwjj/vegbg9/md8bre#XrCHZ
- https://note.youdao.com/ynoteshare/index.html?id=c1ee84493cf1a80a399da3fff7f87dc8&type=note&_time=1677045667612
- https://www.yuque.com/hejincan/shader/crp2fa#DPCNf
- https://www.yuque.com/zhuying-7uqy4/kipb65/gdyzgi#IyJ0M
作业
实现SSAO效果
跟着工程在urp实现了一遍(抄× ( Ĭ ^ Ĭ )
没有加SSAO之前
加上了SSAO
可以看到直角旮旯的地方多了阴影,使画面更有立体感,下面是生成的AO图
和Unity的方案对比下,显然Unity的算法好一点,对于地上那些小物件的AO效果非常显著(尝试调参好久也没调到这种效果)
在URP实现遇到的问题:
-
深度图的获取,在URP Assets文件勾选Depth Texture即可用
_CameraDepthTexture采样,同时Unity也提供相关代码帮助我们采样,具体位置在1
#include "Packages/com.unity.render- pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
-
法线图的获取,需要在自定义的Pass中开启法线图的输入,
1
ConfigureInput(ScriptableRenderPassInput.Normal);
开启后可以用
_CameraNormalsTexture采样,同样Unity也提供了采样接口1
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
需要注意的是,这里存储的是世界空间的法线,而原builtin方法获取的是观察空间,所以如果要用课上的算法的话,要转换一下法线
-
关于课上算法的疑问,采样获得随机屏幕位置后,再去获取深度,此时是非线性的深度,而ao比较是和当前像素的线性深度去对比,感觉不太对劲,改成统一线性后效果好一点。
参考的文章
- 官方关于世界坐标的重建:https://docs.unity3d.com/cn/Packages/[email protected]/manual/writing-shaders-urp-reconstruct-world-position.html
- unity urp 获取屏幕深度法向颜色纹理以及从深度纹理重构世界空间坐标
- 大佬分析UnityUrp实现方案:https://github.com/HHHHHHHHHHHHHHHHHHHHHCS/MyStudyNote/blob/main/MyNote/URP的SSAO.md
其他AO方案
todo:HBAO、GTAO
参考文章:
各个AO技术性能分析