9 Shadow Mapping
文本统计:约 550 个字 • 57 行代码
整个阴影贴图的思路就是先在光源角度将场景渲染到帧缓冲中,得到一张深度图,然后正常渲染,将片段的位置与深度图中的位置进行比较,检测该片段是否在阴影中。
这里阐述一下得到深度图的过程
光源空间的变换,这里采用的是正交投影矩阵,视图矩阵则是看向场景的中央。然后利用 simpleDepthShader
进行渲染,这样就得到了相应的深度缓冲。
simpleDepthShader.Use();
glUniformMatrix4fv(lightSpaceMatrixLocation, 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix));
//注意设置屏幕分辨率与深度贴图的分辨率相同
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
RenderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
再讲一下利用该深度图做阴影测试的过程,包括了几个要点
- 由于经过光源空间的变换后得到的坐标是在 \([-1,1]^3\) 上的,我们需要把它转化到 \([0,1]^3\) 中去
- 受限于纹理的采样率,可能会有阴影失真与锯齿状等等情况;需要加入 bias 来处理阴影失真,计算周围的阴影情况得到平均的shadow
- 然后由于之前采用的是正交投影,有一个远平面,超出远平面的物体的深度值是大于1的,这是应当设置其不为阴影
float ShadowCalculation(vec4 fragPosLightSpace)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
float closestDepth = texture(shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
vec3 normal = normalize(fs_in.normal);
vec3 lightDir = normalize(lightPos - fs_in.fragPos);
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
float shadow = 0;
vec2 texelSize = 1.0/ textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
然后如果是点光源的阴影贴图的话,不再使用二维的贴图,而是使用立方体贴图存储深度缓冲
而对于如何将阴影贴图渲染到立方体贴图中,可以选择渲染六次到各个面上,或者利用几何着色器,将顶点着色器输入的三个顶点分别利用不同的视图矩阵输出到六个面上
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 18) out;
uniform mat4 shadowMatrices[6];
out vec4 FragPos;
void main()
{
for (int face = 0; face < 6; face++)
{
gl_Layer = face;
for (int i = 0; i < 3; i++)
{
FragPos = gl_in[i].gl_Position;
gl_Position = shadowMatrices[face] * FragPos;
EmitVertex();
}
EndPrimitive();
}
}
几何着色器有一个内建变量叫做 gl_Layer
,它指定发散出基本图形送到立方体贴图的哪个面。当不管它时,几何着色器就会像往常一样把它的基本图形发送到输送管道的下一阶段,但当我们更新这个变量就能控制每个基本图形将渲染到立方体贴图的哪一个面。当然这只有当我们有了一个附加到激活的帧缓冲的立方体贴图纹理才有效