作者:i_dovelemon
日期:2017 / 03 / 4
来源:CSDN
主题:SSAO
引言
GraphicsLab项目已经进行了一段时间,期间经过了几次代码重构,现在的框架要比我初次编写的框架更加容易让我实验一些新的算法。今天,就来和大家讨论下最近集成到框架里面的一个新算法:Screen Space Ambient Occlusion。
文章中将只会提出使用OpenGL的哪种技术来实现,不会给出具体的使用OpenGL指令的方式,这些内容可以自行通过网络搜索到,或者可以查看
GraphicsLab项目的源码来了解。
Ambient Occlusion
在讲解具体的算法之前,我们先来了解下什么是Ambient Occlusion(AO)。我们知道,在实时渲染当中,光照模型主要是以Diffuse+Specular+Ambient的形式来进行光照计算。而其中的Ambient项主要模拟的是从环境中反射到物体上的光照。对于实时渲染来说,这种由周围环境反射到物体上的光照很难精确的计算出来,所以在光照模型中,Ambient项对于一个模型来说就是一个固定的颜色。也就是说,如果在不考虑Diffuse和Specular的情况下,仅仅使用Ambient来对物体进行着色的话,你会得到类似如下所示的一种很扁平的光照结果:
从上图可以看到,对于只有简单Ambient着色的物体来说,就是一个扁平的色块,没有任何的明暗对比,所以看不出来任何的立体效果。但是,在现实世界中,由于物体本身的遮挡关系,必然会导致有些地方接受到的光要少,有些地方较多,也就是会形成一种明暗对比关系,而Ambient Occlusion就是为了体现这种由于遮挡而产生明暗对比关系的效果而提出的。同样的图片,在经过AO处理之后(即本文将要介绍的SSAO算法),将会更加有立体的感觉:
Screen Space Ambeint Occlusion
传统的AO计算方法,是通过对模型上每一个点投射多个射线的方法,并以此来统计哪些射线被遮挡,哪些射线指向了空白区域。但是这种方法计算量过高,会随着场景复杂度的提高而提高,无法在实时计算中使用。Crytek提出的该算法,它仅仅依靠深度信息,在屏幕空间计算屏幕上每一个像素的AO值,生成一张屏幕空间的AO贴图,当我们在渲染物体的时候,就可以在这张贴图上查找对应像素的AO值。
AO计算方法
SSAO算法是对中心像素的周围进行采样,然后判断哪些采样像素会遮挡中心像素,哪些不会遮挡。下图即这个过程的示意图:
上图所示,我们要计算点P的AO值,可以在一定区域内采样指定数目的深度值,然后判断:采样深度值大于点P的时候,表示该采样点不会遮挡点P,反之则会遮挡。通过未遮挡采样像素的数目占总采样像素的比例来决定点P的AO值(这里是很粗糙的做法,读者自己实现的时候,可以加上距离衰减之类的控制)。
通过上面的描述,我们就大体的知道了如果计算每一个屏幕空间中像素的AO值。而该计算的条件是:一张Depth Map,一种采样内核。下面,我们就分别来讲述下,如何获取这些信息。
Depth Map
首先要获得Depth Map,我们需要有一种能够渲染到纹理的技术,该技术可以通过OpenGL中的FBO来实现。我们创建一张深度纹理,然后将该纹理绑定到FBO上。然后,我们就可以简单的绘制下场景,这个时候绘制的场景我们仅仅需要的是Depth信息,所以可以关闭对Color Buffer的写入。当关闭对Color Buffer的写入的时候,系统会以更加快速的方式来绘制。除此之外,我们还需要明确的一点是,我们在Depth Map中,保存的不是NDC下的Z坐标,而是在Camera Space中的Z坐标值。由于在Camera Space中,物体的Z值是线性变化的,而在NDC中1/Z值是线性变化的,为了方便以后我们进行深度值的比较,可以保存下Camera Space中的Z坐标值。
下面我们一一的来给出相应的代码,首先是创建深度图,创建FBO,然后绑定FBO的代码:
void RenderImp::PrepareDepthMap() {
// Create depth map
texture::Texture* depth_map = texture::Texture::CreateFloat16DepthTexture(m_Width, m_Height);
if (depth_map != NULL) {
m_DepthMap = texture::Mgr::AddTexture(depth_map);
} else {
GLB_SAFE_ASSERT(false);
}
// Create render target
m_DepthTarget = RenderTarget::Create(m_Width, m_Height);
if (m_DepthTarget != NULL) {
m_DepthTarget->AttachDepthTexture(depth_map);
} else {
GLB_SAFE_ASSERT(false);
}
// Create shader
m_DepthShader = shader::Mgr::AddShader("..\\glb\\shader\\depth.vs", "..\\glb\\shader\\depth.ps");
}
接下来,我们来看下depth shader的代码:
depth.vs
//----------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[1322600812@qq.com]
// Date: 2017 / 02 / 16
// Brief: Depth map
//----------------------------------------------------
#version 330
in vec3 glb_Pos;
uniform mat4 glb_ProjM;
uniform mat4 glb_ViewM;
uniform mat4 glb_WorldM;
out float vs_DepthInViewSpace;
void main() {
vec4 pos_in_view_space = glb_ViewM * glb_WorldM * vec4(glb_Pos, 1.0);
gl_Position = glb_ProjM * pos_in_view_space;
vs_DepthInViewSpace = pos_in_view_space.z / pos_in_view_space.w;
}
depth.ps
//----------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2017. All right reserved.
// Author: i_dovelemon[1322600812@qq.com]
// Date: 2017 / 02 / 16
// Brief: Depth map
//----------------------------------------------------
#version 330
// Input attributes
in float vs_DepthInViewSpace;
uniform float glb_FarClip;
void main() {
// Note: In opengl, camera look at -z axis
// so the depth value in view space is negative value
gl_FragDepth = -vs_DepthInViewSpace / glb_FarClip;
}
从shader中我们可以看出,我保存的是Came