阴影映射技术,也就是在场景中产生物体阴影的技术。增加场景的真实感,和空间感。
主要思路:
首先,我们以光的位置作为视角进行场景的渲染,由于在程序中设置了深度缓冲,所以被遮挡的物体,或者物体的一部分不会被看到,也就是说看不到的地方,就是场景中物体的阴影。我们可以使用深度缓冲技术,获得每个fragment的深度值(一般为0到1之间),阴影部分的深度值肯定比对应位置的遮挡物的深度值大。获得的同时,把深度值信息存储到纹理中,也就是阴影贴图。
题外1:深度缓冲记录的是每个像素的深度值,当两个物体相互遮掩,深度值记录的是没被遮掩的像素的深度值。
其次,在程序运行时,判断一个fragment的深度值(在T空间中的z值),与阴影贴图相应的像素位置进行比较,如果大于该相应位置的深度值,说明在阴影中,可以把该像素点的值设置为黑色。如下图所示。
我们先得使用T(下面有介绍)把P变换到光源的坐标空间里。既然点P是从光的透视图中看到的,它的z坐标就对应于它的深度,例子中这个值是0.9。使用点P在光源的坐标空间的坐标,我们可以索引深度贴图,来获得从光的视角中最近的可见深度,结果是点C,最近的深度是0.4。因为索引深度贴图的结果是一个小于点P的深度,我们可以断定P被挡住了,它在阴影中了。
具体实现细节
首先创建一个帧缓存空间,并且创建一个纹理指向这个缓存空间。这样这个空间的数据,既可以当作缓存的数据,也可以当作纹理贴图的数据。
//创建一个帧缓存对象
GLuint depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
//创建一个纹理对象
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
GLuint depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,SHADOW_WIDTH,SHADOW_HEIGHT,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//将纹理与帧缓存联系在一起
glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
然后是以光源位置作为视角渲染场景,需要用到将世界坐标系转化为光源空间坐标系,这里需要生成一个转变矩阵T。具体如下。
GLfloat near_plane = 1.0f, far_plane = 7.5f;
glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
glm::mat4 lightView = glm::lookAt(glm::vec(-2.0f, 4.0f, -1.0f), glm::vec3(0.0f), glm::vec3(1.0));
glm::mat4 lightSpaceMatrix = lightProjection * lightView;
然后渲染场景。即获得场景中的深度信息,生成深度贴图。
glViewport(0,0,SHADOW_WIDTH,SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
.....
//创建T矩阵,并将T矩阵传递到着色器中
.....
.....
//绘制场景
.....
glBindFramebuffer(GL_FRAMEBUFFER, 0);
仅仅用来生成深度贴图的着色器,并不需要太多设置,仅仅获取物体坐标,并将物体坐标从世界坐标转化为T坐标。
顶点着色器。
#version 330 core
layout (location = 0) in vec3 position;
uniform mat4 lightSpaceMatrix;
uniform mat4 model;
void main()
{
gl_Position = lightSpaceMatrix * model * vec4(position, 1.0f);
}
片段着色器。
#version 330 core
void main()
{
gl_FragDepth = gl_FragCoord.z;
}
接下来就是渲染阴影了。为每个片段着色时,需要先将该片段转化为T空间上的坐标,将T空间坐标的z值与深度贴图相应像素的值做比较,如果大于深度贴图的值,说明该片段在阴影中。则在片段着色器中可以渲染成黑色。
顶点着色器。
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
out vec2 TexCoords;
out VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
vec4 FragPosLightSpace;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f); vs_out.FragPos = vec3(model * vec4(position, 1.0));
vs_out.Normal = transpose(inverse(mat3(model))) * normal;
vs_out.TexCoords = texCoords;
vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
}
片段着色器。
#version 330 core
out vec4 FragColor;
in VS_OUT {
vec3 FragPos;
vec3 Normal;
vec2 TexCoords;
vec4 FragPosLightSpace;
} fs_in;
uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;
uniform vec3 lightPos;
uniform vec3 viewPos;
float ShadowCalculation(vec4 fragPosLightSpace)
{
// 执行透视除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// 变换到[0,1]的范围
projCoords = projCoords * 0.5 + 0.5;
// 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标)
float closestDepth = texture(shadowMap, projCoords.xy).r;
// 取得当前片元在光源视角下的深度
float currentDepth = projCoords.z;
// 检查当前片元是否在阴影中
float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
return shadow;
}
void main()
{
vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
vec3 normal = normalize(fs_in.Normal);
vec3 lightColor = vec3(1.0);
// Ambient
vec3 ambient = 0.15 * color;
// Diffuse
vec3 lightDir = normalize(lightPos - fs_in.FragPos);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * lightColor;
// Specular
vec3 viewDir = normalize(viewPos - fs_in.FragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = 0.0;
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
vec3 specular = spec * lightColor;
// 计算阴影
float shadow = ShadowCalculation(fs_in.FragPosLightSpace);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; FragColor = vec4(lighting, 1.0f);
}