OpenGL:Framebuffer实现Shadow mapping

Framebuffers中,我们学习了 Framebuffer,知道了 Framebuffer 是我们自己控制的一个缓存,可以用于存储颜色深度模版这些数据的信息。

我们可以利用 Framebuffer 存储当前渲染场景的深度信息,然后通过比较深度来决定阴影的渲染。

具体步骤如下

  1. 创建一个 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);
  1. 从光源你的角度渲染一遍场景,但是这次渲染不会渲染任何颜色信息,也可以叫做“离屏渲染”,主要是为了将深度信息存储到我们刚刚创建的 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 的设计,和正常渲染一样,需要 projectionviewmodel 这三个矩阵进行坐标空间的转换。

// 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 不需要进行任何操作,但是还是需要实现的。

  1. 正常渲染场景,这一次需要和刚刚保存的深度进行进行比较,然后决定是否渲染阴影。
// 将视口转换为原来窗口的长宽
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;  
}

请添加图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ht巷子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值