本篇文章不是特别完整,所以放上在公司做的内部分享的PPT。

SSAO

什么是SSAO

什么是AO?

AO(Ambient Occlusion, 环境光遮蔽)可以认为是一种全局光照模型(不同于Phong光照模型等局部光照模型)

AO用来计算表面上任意一点暴露于环境光中的程度,反过来说,就是这一点被其他物体''遮挡的程度'',从而决定这一点的受环境光的光照强度,最终使得渲染结果更具层次感

老外一句话概括:AO = shadow created by ambient illumination

看两个栗子:

右图没有环境光产生的阴影,感觉很不真实,车像浮在空中。

左图只有直接光照产生的阴影,右图加上环境光产生的阴影,老头的皱纹更明显、更立体。

如何计算AO?

先说结论:很费,知道这一点就足够了,不信看公式。

A({P},\hat{n}) = \frac{1}{\pi}\int_{\Omega}(V(\hat{\omega}, P))max(\hat{\omega} \cdot \hat{n}, 0)d\hat{\omega}

上述公式表示表面上法线为\hat{n}的一点P,受到周边物体的遮挡值A的计算过程。

其中,\hat{\omega}表示点P到其切平面正方向的单位半球\Omega表面的各个方向。V是在\hat{\omega}方向点P的可见性函数,如果在\hat{\omega}方向点P被遮挡值就是1,否则就是0(在上图中,可以看到红色虚线被阻挡了,V=1;绿色实线没有被遮挡,V=0)。

In traditional Ray Tracing ambient occlusion is simulated by sampling rays from a certain point, which takes a shape of a hemisphere, and then is checked for intersection with the scene (also called Object Space AO).

所以说,AO不适合在游戏内实时计算。

SSAO应运而生

SSAO(Screen Space Ambient Occlusion, 屏幕空间环境光遮蔽)是用于实时高效的模拟上述AO效果的一种计算机图形学技术。由Vladimir Kajalin在Crytek工作时开发,并在2007年由Crytek开发Electronic Arts发行的电子游戏Crysis(孤岛危机)中第一次使用。

SSAO的基本原理

将深度缓冲(depth buffer)看做是场景的一种近似。

在屏幕空间(2D)中,对每个像素进行处理(片元着色器),对该像素周围的像素进行采样,并通过对比当前像素和采样像素的深度来计算最终的AO。

如何采样?对每个采样点,如何计算其贡献的AO?这是SSAO中最核心的两个问题。先来看几种常见的采样算法:

Crysis中的SSAO

Crysis中的SSAO在一个完整的球体内进行采样:

  • 将每个采样点投影到屏幕空间内,以获取在深度缓冲中的坐标。
  • 根据上述坐标,对深度缓冲进行采样,获取对应的深度。
  • 如果样本位置在采样深度之后(即样本在几何体内),则贡献AO。

这种方法的最终的表现跟采样的数量成正比,但为了性能需要尽量降低采样的数量,采样数低了又会产生“条带状”的感觉。

但是, 对于这种现象我们有一个Trick ---- 可以引入噪声,将每个采样点以原点法线方向为旋转轴旋转随机的角度。 这样的新采样点会变得极其不规则,更加离散化。将低频的条纹转化成高频的噪声

在Unity中实现SSAO

准备工作:获取像素的深度、法线、坐标

  1. 首先要让Camera构建屏幕空间的深度和法线纹理。以便在shader中对_CameraDepthNormalsTexture进行采样,以获取深度和法线信息。
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
  1. 获取像素深度、法线(摄像机空间)、坐标(摄像机空间
inline float samplePositionAndNormal(float2 uv, out float3 p, out float3 n)
{
    float depth;
    DecodeDepthNormal(tex2Dlod(_CameraDepthNormalsTexture, float4(uv, 0, 0)), depth, n);

    float3 ray = (half3(-0.5f,-0.5f,0) + half3(uv.xy,-1)) * _FarCorner;
    p = ray * depth;

    return depth;
}

因为上述函数在后续步骤会用在for循环里,所以采样时一定要使用tex2Dlod而不是tex2D,具体原因看这里

根据uv以及depth获取摄像机空间下的坐标的具体解释:根据深度重建像素在摄像机空间的坐标

  1. 在像素周围进行采样

优化

优化采样算法

  • 使用采样纹理进行随机采样
    上述采样算法对每个像素都是在水平和垂直方向上采样的
    将采样轴的x、y坐标以及采样半径分别存储于纹理的RGB通道中。
static void GenerateAxisPattern()
{
    Texture2D axisPattern = new Texture2D(3,3);

    Color[] colors = axisPattern.GetPixels();

    // 90 degrees / 9 samples = 10 degrees increment. 
    // interleave increments per row and column so that they are evenly spread out.

    float[] angles = new float[9]
    {
        0,  30, 60,
        40, 70, 10,
        80, 50, 20
    };

    float[] lenghts = new float[9]
    {
        0.7f,  0.4f,   0.2f,
        0.3f,  0.5f,   0.9f,
        0.0f,  0.6f,   0.1f
    };

    for (int i = 0; i < 9; ++i)
    {
        Vector2 axis = Quaternion.Euler(0, 0, angles[i]) * Vector2.right;
        colors[i] = new Color(axis.x, axis.y, lenghts[i], 1);
    }

    axisPattern.SetPixels(colors);
    axisPattern.Apply();

    AssetDatabase.CreateAsset(axisPattern, "Assets/SSAOResources/AxisPattern.asset");
}
  • 限制采样半径、范围
  • 控制采样数量
  • 降采样,提升效率

优化AO算法

  • 遮挡偏移
  • 遮挡方向
  • 遮挡强度
  • 调制光照
  • 距离衰减

进一步优化结果

  • 遮挡颜色
  • 模糊

附加效果:渗色

参考

https://en.wikipedia.org/wiki/Ambient_occlusion

http://gamedev.stackexchange.com/questions/23/what-is-ambient-occlusion

https://santisanchez28.wordpress.com/2011/07/16/ambient-occlusion-2/

https://zhuanlan.zhihu.com/p/25038820

http://perso.telecom-paristech.fr/~boubek/papers/SAO/

https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/a-simple-and-practical-approach-to-ssao-r2753

https://forum.unity3d.com/threads/lightning-fast-quality-occlusion-simple-ssao.388820/

http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html

https://developer.valvesoftware.com/wiki/SFM/Ambient_Occlusion

https://learnopengl.com/#!Advanced-Lighting/SSAO