图形学基础|环境光遮蔽(Ambient Occlusion)
文章目录
一、前言
在现实世界中,仅有环境光(Ambient Light)的区域也不是所有像素的照明度都相同。
由于自身的遮挡(折痕、皱纹、角落)或被其他物体遮挡,一些区域会呈现出比较暗。
例如,墙角落中的像素比墙中间的像素具有更少的光(由于被更多地遮挡了)。
对于Self-Ambient Occlusion 自身的环境光遮蔽,通常可以通过离线烘焙一张AO贴图,它不会考虑场景其他物体的遮挡。
- AO通常为低频信息,没有必要使用分辨率非常高的纹理。
除了自身的遮挡,来自其他物体的遮挡也是非常重要!
环境光遮蔽(Ambient Occlusion)是计算机图形学中的一种着色和渲染技术,用来计算场景中每一点是如何接受环境光的。
环境光遮蔽是一种全局方法,意味着每个点的照明是场景中其他几何体的共同作用。
然而,这只是一个非常粗略的近似全局光照,仅通过环境光遮蔽得到的物体外观与阴天下的物体相似。
Wiki-环境光遮蔽 给出了实时环境光遮蔽算法分类:
-
SSAO(屏幕空间环境光,Screen space ambient occlusion)
-
SSDO(屏幕空间定向遮蔽,Screen space directional occlusion)
-
RTAO(光线追踪环境光遮蔽,Ray Traced Ambient Occlusion)
-
HDAO(高分辨率环境光遮蔽,High Definition Ambient Occlusion)
-
HBAO+(水平基准环境光遮蔽,Horizon Based Ambient Occlusion+)
-
AAO(Alchemy Ambient Occlusion)
-
ABAO(角度基准环境光遮蔽,Angle Based Ambient Occlusion)
-
PBAO(预烘焙环境光遮蔽,Pre Baked Ambient Occlusion)
-
VXAO(体素基准环境光遮蔽,Voxel Accelerated Ambient Occlusion)
-
GTAO(Ground Truth based Ambient Occlusion)
本篇博客将介绍并实现以下几种环境光遮蔽算法,以下是笔者的一些笔记。
二、SSAO(Screen space ambient occlusion)
如果一个像素被其他表面遮挡,那么到达它位置的环境光就变少了。
AO算法,主要通过构建从表面上一点朝其法线所在上半球的所有方向发出射线,然后检查它们是否与其他对象相交来计算环境光遮蔽因子。
到达背景或天空的光线会增加表面的亮度,而穿过其他对象的光线不会增加亮度。
因而被大量几何体围绕的点显示为较暗,而周围只有少量几何体的点则显示为较亮。这意味着彼此靠近的任何对象之间的阴影效果将更加明显。
基于顶点的AO技术在模型表面顶点足够密的情况下,能够得到很好的效果。但是基于物理精确的AO计算需要进行光线与场景的求交运算 , 十分耗时 ,所以这种方法只能用于离线渲染。
为了达到实时计算的目标,提出基于屏幕空间的环境遮挡技术,对每个像素的邻域进行随机采样快速计算AO的近似值。
屏幕空间环境光遮蔽(SSAO,Screen space ambient occlusion)是实际应用较多的一种AO算法。
顾名思义,SSAO所有的计算都发生在屏幕空间,利用深度缓存上的深度进行比较代替光线与场景物体的求交算法,大大加快了计算速度,该算法可以在实时运行的条件下较为逼真的模拟全局光照的渲染效果。
SSAO示例效果:
- 可以明显看到右图墙角处比墙面处暗了不少,比较符合现实。
3.1 遮挡计算
在SSAO算法中,AO是这么定义的:
- 在场景中的点上产生一个标量值,描述由这点向各个方向出射的光线被遮挡的概率。
可通过在法线上半球对可见性函数进行积分得到,如公式(1)所示:
A p ( n → ) = 1 − 1 π ∫ Ω V P ( w → ) ( n → ⋅ w → ) d w (1) A_p(\overrightarrow{n} ) = 1 - \frac{1}{\pi} \int\limits_{\Omega} V_P(\overrightarrow{w})(\overrightarrow{n}\cdot \overrightarrow{w} )dw\tag{1} Ap(n)=1−π1Ω∫VP(w)(n⋅w)dw(1)
其中:
- Ω \Omega Ω,是着色点p点法线方向所在上半球的方向集合。
- V ( w ) V(w) V(w)是距离衰减函数,衰减函数从1开始衰减并在某个固定距离下衰减到0。
在计算中,我们不会对全部方向都进行计算,而是随机地上半球选取几个方向。
SSAO的基本步骤如下:
对屏幕空间内每一个像素,计算其在三维空间里的位置p。
- 在以p点为中心其法线构成的上半球的空间内,随机地产生若干三维采样点(半径也随机)。
以下为产生64个采样点示例代码:
- 由于在法线上半球(切线空间)采样, Z Z Z的取值区间为 [ 0 , 1 ] [0,1] [0,1]。
Lerp
函数采样点更加接近原点。
// 默认产生的[0,1]之间的浮点数
float RandomFloat(float LO = 0.0f, float HI = 1.0f)
{
float random = LO + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (HI - LO)));
return random;
}
// make it closer to the origin point
float Lerp(float a, float b, float f)
{
return a + f * (b - a);
}
// SSAO Samples
std::vector<Vector4f> Samples;
Samples.resize(64);
for (int i = 0; i < 64; ++i)
{
Vector3f Sample = Vector3f(
RandomFloat() * 2 - 1.0f,
RandomFloat() * 2 - 1.0f,
RandomFloat()
);
Sample = Sample.Normalize();
// Scale
float Scale = RandomFloat();
Scale *= i * 1.0f / 64.0f;
Scale = Lerp(0.1f, 1.0f, Scale * Scale);
Sample = Sample * Scale;
Samples[i] = Vector4f(Sample, 0);
}
</