图形4.2 SSAO算法
GAMES202 中的SSAO
https://xzyw7.github.io/post/CbZTf-uM4/#screen-space-ambient-occlusionssao
一、SSAO介绍
SSAO相关术语,简要理解及历史
- AO
- 环境光遮蔽Amibent Occlusion,用于模拟光线到达物体的能力的一种粗略的全局方法,描述光线到达物体表面的能力
- SSAO
- 屏幕空间环境光遮蔽Screen Space Ambient Occlusion,一种用于实施近似AO的渲染技术。通过获取像素的深度缓冲、法线缓冲来计算实现,近似地表现物体在间接光下产生的阴影。
- 历史
- AO最早在Siggraph 2002年会上有ILM(工业光魔)的技术主管Hayden Landis所展示,当时就被叫做Ambient Occlusion
- 2007年,Crytek发布了SSAO的技术,并用在了孤岛危机上。
二、SSAO原理
简要理解SSAO算法原理
- 深度缓冲
- depth用于当前视点下场景的每一个像素距离相机的粗略表达,用于重构像素相机空间中的坐标Z,来近似重构该视点下的三维场景。
- 法线缓冲
- 相机空间中的法线信息,用于重构每个像素的TBN坐标轴,用于计算发现半球中的采样随机向量,随机向量用于判断和描述该像素的AO强度。
- 法向半球
- 黑色表示我们需要计算的样本
- 蓝色项链表示样本的法向量
- 白色、灰色为采样点。灰色表示被遮挡采样点,据此判断最终AO的强度
三、算法实现
根据原理结合UnityC#&Shader实现SSAO
实现过程环境
- Unity2019.3.5f1
- 透视模式
- 前向渲染。如果为延迟渲染,则由对应的G-buffer生成,在shader中作为全局变量访问
- 使用OnRenderImage()来处理后期,进而实现SSAO
获取深度&法线缓冲数据
- C#部分
1 | private void Start() { |
- Shader部分
1 | //获取深度法线图 |
重建相机空间坐标
- 重建方法
- 参考链接
- https://zhuanlan.zhihu.com/p/92315967
- 本例实现使用其中的“从NDC空间中重建”方法得到样本在相机空间中的向量,乘以深度值得到样本的坐标
- 从NDC空间中重建
1.计算样本屏幕坐标
1 | // 利用Unity内置函数 |
2.转化至NDC空间
1 | float4 ndcPos = (screenPos/screenPos.w)*2-1; |
3.计算相机空间中至远平屏幕方向(内置变量_ProjectionParams.z存放相机远平面值far)
1 | float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0) * _ProjectionParams.z; |
4.矩阵变换至相机空间中的样本相对相机的方向
1 | o.viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz; |
5.重建相机空间中的样本左边(在像素着色器)
1 | float3 viewPos = linear01Depth * i.viewVec; |
构建法向量正交基
- 设置法向量
1 | //获取像素相机屏幕法线,法线z方向相对于相机为负(ao需要乘以-1置反),并处理成单位向量 |
- 生成随机向量(用于构建的正交基随机,而非所有样本计算得到的正交基一致)(先处理成统一)
1 | //randvec法线半球的随机向量 |
- 求出切向量,再用函数叉积求副切线向量
1 | //Cramm-Schimidt处理创建正交基 |
AO采样
- 传入给定的随机采样向量,并通过法向量正交基转化至法线半球中的向量(在C#中计算出采样的随机点)
1 | //随机向量,转化至TBN空间 |
- 获取随机坐标点
1 | //计算随机法线半球后的向量 |
- 转换至屏幕空间坐标
1 | float3 rclipPos = mul((float3x3)unity_CameraProjection, randomPos); |
- 计算随机向量转化至屏幕空间后对应的深度值,并判断累加AO
1 | float randomDepth; |
四、效果改进
效果后期改进说明
随机正交基(增加随机性)
- 为了不使求得的法向半球的正交基一致,我们引入随机向量。
1 | //Cramm-Schimidt处理创建正交基 |
- 利用uv采样一张noise贴图(如4x4像素(可选择其他尺寸)的noise贴图)或者随机向量
1 | //铺平纹理 |
在C#中传入噪声图
1 | ssaoMaterial.SetTexture("_NoiseTex", Noise) |
AO累加平滑优化
范围判定(模型边界)
- 样本采样可能会采集到深度差非常大的随机点,导致边界出现不该有的AO
- 加入样本深度和随机点的深度值范围判定
1 | float range = abs(randomDepth - linear01Depth) > _RangeStrength ? 0.0 : 1.0; |
效果如下
自身判定(同一深度值情况下)
如果随机点深度值和自身一样或非常接近,可能导致虽在同一平面,也会出现AO
- 判断深度值大小的时候,增加一个Bias来改善该问题
1 | float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0; |
AO权重
AO深度判断非0即1,比较生硬,为其增加一权重
本例中权重为:法线半球中随机采样后的点x,y(切平面)距离样本的距离为参考
1 | float weight = smoothstep(0, 0.2, length(randomVec.xy)); |
结合
1 | ao += range * selfCheck * weight; |
模糊(只展示效果对比)
采用基于法线的双边滤波(Bilateral Filtering)
五、对比模型烘焙AO
同模型烘焙AO方式对比,了解SSAO优缺点
三维建模软件烘焙AO
通过DCC设定好渲染参数,对模型烘焙AO到纹理
- 优点
- 单一物体可控性强(通过单一物体的材质球上的AO纹理贴图),可以控制单一物体的AO强弱
- 弥补场景烘焙的细节,整体场景的烘焙(包含AO信息),并不能完全包含单一物体细节上的AO,而通过DCC烘焙到纹理的方式,增加物体的AO细节
- 不影响其(Unity场景中)静态或者动态
- 缺点
- 操作较其他方式繁琐,需要对模型进行UV处理,再烘焙到纹理
- 不利于整体场景整合(如3Dmax烘焙到纹理只能选择单一物体,针对整体场景的处理工作量巨大)
- 增加AO纹理贴图,不利于资源优化(后期可通过其他纹理通道利用整合资源)
- 只有物体本身具有AO信息,获取物体之间的AO信息工作量巨大(不是不可能)
游戏引擎烘焙AO(Unity3D Lighting)
通过Unity的Lighting功能(主菜单/Window/Rendering/Lighting Settings) 进行整体场景的烘焙,AO信息包含于此
- 优点
- 操作简易,整体场景的烘焙,包含AO的选择
- 不受物体本身UV的影响,unity可以通过Generate Lightmap UVs生成模型第二个纹理坐标数据
- 可生产场景中物体与物体之间的AO信息
- 缺点
- 缺少单一物体的细节(可调整参数提高烘焙细节,但将增加烘焙纹理数量和尺寸以及烘焙时间)
- 受物体是否静态影响,动态物体无法烘焙,获得AO信息
SSAO
- 优点
- 不依赖场景的复杂度,其效果质量依赖于最终图片像素大小
- 实时计算,可用于动态场景
- 可控性强,灵活性强,操作简单
- 缺点
- 性能消耗较上述两种方式更多,计算昂贵
- AO质量弱于离线烘焙
六、性能消耗
主要性能消耗点

- AO法向半球的随机采样
- 双边滤波的多重采样
AO核心采样消耗说明
本例SSAO中,主要核心为计算AO随机法向半球的采样点
- 使用For结构进行半球随机法向的采样,If,For对GPU计算性能上不友好
1 | //采样核心 |
作业
实现SSAO
使用其他算法实现进行对比
参考资料
[1] https://www.bilibili.com/video/BV16q4y1U7S3
【技术美术百人计划】图形 4.2 SSAO算法 屏幕空间环境光遮蔽
[2] https://learnopengl-cn.github.io/05%20Advanced%20Lighting/09%20SSAO/