在Framebuffers中,我们学习了 Framebuffer,知道了 Framebuffer 是我们自己控制的一个缓存,可以用于存储颜色、深度和模版这些数据的信息。
我们可以利用 Framebuffer 存储当前渲染场景的深度信息,然后通过比较深度来决定阴影的渲染。
具体步骤如下:
- 创建一个
Frambuffer且绑定一个Texture作为它的一个附件,用于存储当前渲染场景的深度信息。
GLuint shadowFBO, shadoTex;
glGenFramebuffers(1, &shadowFBO);
glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);
glGenTextures(1, &shadowTex);
glBindTexture(GL_TEXTURE_2D, shadowTex);
// 这里只需要深度信息,其他的信息不需要
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F,
SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
shadowTex, 0);
// 将读和绘制缓存设置为 GL_NONE
// 这是告诉OpenGL不需要渲染任何颜色数据
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
// 检查帧缓冲完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cout << "Shadow framebuffer is not complete!" << std::endl;
}
// 启用默认的 Framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
- 从光源你的角度渲染一遍场景,但是这次渲染不会渲染任何颜色信息,也可以叫做“离屏渲染”,主要是为了将深度信息存储到我们刚刚创建的
shadowTex中。
glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO);
// 设置视口和我们刚刚设置的 Texture 大小一致
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
// 清空深度缓存
glClear(GL_DEPTH_BUFFER_BIT);
// 设置光源视角下的 view 和 projection 矩阵
// Your code
// 渲染场景
renderScene(shadow_depthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
需要注意的是,这里使用的 Shader 和后续正常渲染所使用的 Shader 是不一致的,这里的 Shader 是从光源的视角来进行渲染(并不会渲染到屏幕上),主要是为了获取场景深度信息,我们不关心颜色和模板信息。

这里以定向光为例子,由于光线是一个方向,所以对于投影来说,我们可以使用正交投影,这里的定向光的方向是看向了原点。
float near_plane = 1.0f, far_plane = 10.5f;
lightProj = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPosition, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
lightSpaceMat = lightProj * lightView;

对于 Shader 的设计,和正常渲染一样,需要 projection、view 和 model 这三个矩阵进行坐标空间的转换。
// shadow_depth.vert
#version 440
layout (location = 0) in vec3 position;
uniform mat4 lightSpaceMatrix;
// 这里的 model 矩阵不需要改变,用的是和模型一样的
uniform mat4 model;
void main()
{
gl_Position = lightSpaceMatrix * model * vec4(position, 1.0);
}
// shadow_depth.frag
#version 440
void main { // 这里我们不需要输出颜色信息,所以可以进行空实现 }
[!Note]
虽然 Fragment Shader 不需要进行任何操作,但是还是需要实现的。
- 正常渲染场景,这一次需要和刚刚保存的深度进行进行比较,然后决定是否渲染阴影。
// 将视口转换为原来窗口的长宽
glViewport(0, 0, screenWidth, screenHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 启用深度贴图
glActiveTexture(GL_TEXTURE0 + binding);
glBindTexture(GL_TEXTURE_2D, shadowTex);
renderScene(shader);
在以前的 Shader 中我们需要添加用于将模型坐标系转换到灯光坐标系的矩阵 lightSpaceMatrix,还有用于对深度贴图进行采样的采样器 shadow_coord。
// normal shader
#version 440
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 aNormal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
// 将坐标从模型坐标转换到灯光坐标系的矩阵
uniform mat4 lightSpaceMatrix;
out vec2 tc;
out vec4 shadow_coord;
out vec3 fragPos;
out vec3 normal;
void main()
{
fragPos = vec3(model * vec4(position, 1.0f));
normal = transpose(inverse(mat3(model))) * aNormal;
// 用来定位刚刚存储的深度信息在 Texture 中的位置
shadow_coord = lightSpaceMatrix * vec4(fragPos, 1.0);
tc = texCoord;
gl_Position = projection * view * vec4(fragPos, 1.0);
}
// normal shader 不是完整的...
#version 440
out vec4 fragColor;
in vec4 shadow_coord;
// 用于对保存了深度信息的贴图进行采样
uniform sampler2D shadowTex;
void main
{
// 计算阴影的影响因子
float shadowFactor = shadowCalc(shadow_coord, lightDir);
// 计算环境光
vec3 ambient = (globalAmbient * material.ambient).xyz;
// 计算灯光
// ... lightColor;
// 输出的颜色
fragColor = vec4(ambient + (1 - shadowFactor) * lightColor.xyz, 1.0f);
}
对于阴影影响因子的计算,我们是通过当前片段的深度与深度贴图中的深度进行比较,如果当前片段的深度大于贴图中的深度,则说明当前的片段是在阴影中的,否则说明当前片段被光照射到。
float shadowCalc(vec4 shadowCoord, vec3 lightDir)
{
// 执行透视除法
vec3 projCoords = shadowCoord.xyz / shadowCoord.w;
// 将深度贴图中的坐标st的范围从 [0, 1] 转换到 [-1, 1]
projCoords = projCoords * 0.5 + 0.5;
// 取得最近点的深度
float closestDepth = texture(shTex, projCoords.xy).r;
// 获取当前片段在光源下的深度
float currentDepth = projCoords.z;
// 这是改进阴影的方法,这里不讲
float bias = max(0.05 * (1.0f - dot(normalize(normal), lightDir)), shadowBias);
// 检查当前片段是否在阴影中
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0f;
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}

561

被折叠的 条评论
为什么被折叠?



