之前我们了解过AO、SSAO、HBAO(可参照之前文章Vulkan_SSAO—屏幕空间环境光遮蔽、DirectX11进阶9_AO、SSAO、Particle System(GPU)、Global Illumination_Horizon-Based Ambient Occlusion(HBAO))。
我们本次来看一下一种基于屏幕空间的全局光照算法。SSAO因为与SSDO、HBAO极其相似,仅是在最后一步时充分利用了随机查询点处的信息来模拟间接光照。
一、基于屏幕空间的间接光照算法
从上图(左数前两个)看出,对于着色点p在生成随机的点ABCD时,只有处于实际物体下方的点(A,B,D)都是用来计算SSAO的,算法认为可以把处于实际物体上方的点(C点)作为虚拟点光源(VPL)用于计算着色点p的间接光照。
从上图(左数第三个)看出看出,本算法也是存在一定缺陷的:比如A点实际未在物体内部,但因为深度遮挡关系导致改点会出错,B点虽然未被遮挡,但却无法接收环境光的光照效果。虽然有这些问题但问题不大,毕竟CG的一大定律:看着没问题就行。
其中,
-
di 为随机点 pi 到着色点 p 的距离;
-
如果随机点 pi 在实际物体下方 ,则 L 返回0;
-
Qsi 为直接光源方向与随机点 pi 的夹角;
-
Qri 为随机点 pi 到着色点 p 向量与着色点 p 法线的夹角;
-
As 为 (当然这个公式这是一个参考,可以根据具体情况对公式进行改变):
二、算法实现
很简单基于延迟渲染,下边简述其shader流程:
2.1 G-Buffer提取
顶点着色器:
#version 430 core
layout(location = 0) in vec3 _Position;
layout(location = 1) in vec3 _Normal;
layout(location = 2) in vec2 _TexCoord;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
uniform mat4 u_ModelMatrix;
out vec2 v2f_TexCoords;
out vec3 v2f_Normal;
out vec3 v2f_FragPosInViewSpace;
void main()
{
vec4 FragPosInViewSpace = u_ViewMatrix * u_ModelMatrix * vec4(_Position, 1.0f);
gl_Position = u_ProjectionMatrix * FragPosInViewSpace;
v2f_TexCoords = _TexCoord;
v2f_Normal = normalize(mat3(transpose(inverse(u_ViewMatrix * u_ModelMatrix))) * _Normal);
v2f_FragPosInViewSpace = FragPosInViewSpace.xyz;
}
片元着色器:
#version 430 core
in vec3 v2f_FragPosInViewSpace;
in vec2 v2f_TexCoords;
in vec3 v2f_Normal;
layout (location = 0) out vec3 Albedo_;
layout (location = 1) out vec3 Normal_;
layout (location = 2) out vec4 Position_;
uniform sampler2D u_DiffuseTexture;
uniform vec4 u_DiffuseColor;
uniform float u_Near = 0.1;
uniform float u_Far = 1000.0f;
uniform vec3 u_LightPos = vec3(-18.5264, 19.4874, 78.5421);
//暂未用,多光源时处理
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
float LinearizeDepth(float vDepth)
{
float z = vDepth * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
void main()
{
Position_ = vec4(v2f_FragPosInViewSpace.xyz,LinearizeDepth(gl_FragCoord.z));
Normal_ = normalize(v2f_Normal);
Albedo_ = texture(u_DiffuseTexture, v2f_TexCoords).xyz ;
}
2.2 SSAO计算
常规SSAO计算流程,不再赘述,想具体了解的看一下之前的详细教程。
顶点着色器:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
片元着色器:
#version 430 core
in vec2 v2f_TexCoords;
layout (location = 0) out float FragColor_;
uniform sampler2D u_PositionTexture;
uniform sampler2D u_NormalTexture;
uniform sampler2D u_NoiseTexture;
uniform float u_Near = 0.1;
uniform float u_Far = 100.0f;
uniform float u_WindowWidth;
uniform float u_WindowHeight;
uniform vec3 u_Samples[64];
int KernelSize = 64;
float Radius = 1.0;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
float LinearizeDepth(float vDepth)
{
float z = vDepth * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
void main()
{
vec2 NoiseScale = vec2(u_WindowWidth/4.0f, u_WindowHeight/4.0f);
vec3 FragPos = texture(u_PositionTexture, v2f_TexCoords).xyz;
vec3 Normal = texture(u_NormalTexture, v2f_TexCoords).rgb;
vec3 RandomVec = texture(u_NoiseTexture, NoiseScale * v2f_TexCoords).xyz;
vec3 Tangent = normalize(RandomVec - Normal * dot(RandomVec, Normal));
vec3 Bitangent = cross(Normal, Tangent);
mat3 TBN = mat3(Tangent, Bitangent, Normal);
float Occlusion = 0.0;
for(int i = 0; i < KernelSize; ++i)
{
vec3 samples = TBN * u_Samples[i];
samples = FragPos + samples * Radius;
vec4 Offset = vec4(samples, 1.0);
Offset = u_ProjectionMatrix * Offset;
Offset.xyz /= Offset.w;
Offset.xyz = Offset.xyz * 0.5 + 0.5;
float SampleDepth = -texture(u_PositionTexture, Offset.xy).w;
float RangeCheck = smoothstep(0.0, 1.0, Radius / abs(FragPos.z - SampleDepth ));
Occlusion += (SampleDepth >= samples.z ? 1.0 : 0.0) * RangeCheck;
}
Occlusion = 1.0 - (Occlusion / KernelSize);
FragColor_ = Occlusion;
}
具体的滤波操作与下面的SSDO一样便不再赘述,仅见下图:
2.3 SSDO计算
顶点着色器:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
片元着色器:
#version 430 core
in vec2 v2f_TexCoords;
layout (location = 0) out float FragColor_;
uniform sampler2D u_PositionTexture;
uniform sampler2D u_NormalTexture;
uniform sampler2D u_NoiseTexture;
uniform float u_Near = 0.1;
uniform float u_Far = 100.0f;
uniform float u_WindowWidth;
uniform float u_WindowHeight;
uniform vec3 u_Samples[64];
int KernelSize = 64;
float Radius = 1.0;
layout(std140, binding = 0) uniform u_Matrices4ProjectionWorld
{
mat4 u_ProjectionMatrix;
mat4 u_ViewMatrix;
};
float LinearizeDepth(float vDepth)
{
float z = vDepth * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
void main()
{
vec2 NoiseScale = vec2(u_WindowWidth/4.0f, u_WindowHeight/4.0f);
vec3 FragPos = texture(u_PositionTexture, v2f_TexCoords).xyz;
vec3 Normal = texture(u_NormalTexture, v2f_TexCoords).rgb;
vec3 RandomVec = texture(u_NoiseTexture, NoiseScale * v2f_TexCoords).xyz;
vec3 Tangent = normalize(RandomVec - Normal * dot(RandomVec, Normal));
vec3 Bitangent = cross(Normal, Tangent);
mat3 TBN = mat3(Tangent, Bitangent, Normal);
float Occlusion = 0.0;
for(int i = 0; i < KernelSize; ++i)
{
vec3 samples = TBN * u_Samples[i];
samples = FragPos + samples * Radius;
vec4 Offset = vec4(samples, 1.0);
Offset = u_ProjectionMatrix * Offset;
Offset.xyz /= Offset.w;
Offset.xyz = Offset.xyz * 0.5 + 0.5;
float SampleDepth = -texture(u_PositionTexture, Offset.xy).w;
float RangeCheck = smoothstep(0.0, 1.0, Radius / abs(FragPos.z - SampleDepth ));
Occlusion += (SampleDepth >= samples.z ? 1.0 : 0.0) * RangeCheck;
}
Occlusion = 1.0 - (Occlusion / KernelSize);
FragColor_ = Occlusion;
}
此部分为算法主要实现,根据首先判断点的贡献度,然后根据点类型作为次级光源贡献到着色点。
2.3 滤波
顶点着色器:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
片元着色器:
#version 430 core
in vec2 v2f_TexCoords;
layout (location = 0) out float FragColor_;
uniform sampler2D u_SSAOTexture;
void main()
{
vec2 TexelSize = 1.0 / vec2(textureSize(u_SSAOTexture, 0));
float Result = 0.0;
for (int x = -2; x < 2; ++x)
{
for (int y = -2; y < 2; ++y)
{
vec2 Offset = vec2(float(x), float(y)) * TexelSize;
Result += texture(u_SSAOTexture, v2f_TexCoords + Offset).r;
}
}
FragColor_ = Result / (4.0 * 4.0);
}
2.4 显示
顶点着色器:
#version 430 core
layout (location = 0) in vec2 _Position;
layout (location = 1) in vec2 _TexCoords;
out vec2 v2f_TexCoords;
void main()
{
gl_Position = vec4(_Position, 0.0, 1.0);
v2f_TexCoords = _TexCoords;
}
片元着色器:
#version 430 core
in vec2 v2f_TexCoords;
out vec4 Color_;
uniform sampler2D u_SSAOTexture;
uniform sampler2D u_Albedo;
uniform sampler2D u_SSDOTexture;
uniform sampler2D u_DirectLightSSDOTexture;
void main()
{
float Ambient = (texture(u_SSAOTexture, v2f_TexCoords, 0).r);
vec3 Albedo = texture(u_Albedo, v2f_TexCoords).rgb;
Color_ = vec4( texture(u_SSDOTexture, v2f_TexCoords).rgb,1);
}
直接来看一下环境光部分:
再看一下直接使用SSAO的效果:
最后看一下SSDO的效果: