【OpenGL】LearnOpenGL学习笔记23 - ShadowMap、PCF
上接:https://blog.csdn.net/weixin_44506615/article/details/151746607?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL
一、阴影映射 (ShadowMap)
当空间中的某处被其他物体挡住了光线,此处就会产生阴影
渲染出阴影,可以给场景极大的增加立体感,如下图所示
目前常见的一种渲染阴影的技术就是 阴影映射 (ShadowMap)
其思路就是:
- 我们首先在光源的位置沿光源方向渲染一张深度图
- 正常渲染时,获取片段到光源的距离d (将片段转换到光源空间,可以理解为把光源视作一个摄像机),以及片段基于光源空间的坐标xy
- 通过xy在深度图上进行采样,获得深度depth,比较d和depth的大小,如果d大于depth则认为此处无法接收到光照,反之则可以
如下图所示 (图片来自于LearnOpenGL),这里的LIT BY LIGHT为 depth,IN SHADOW则为d
这里第一步渲染的深度图即为 阴影映射 (ShadowMap)
二、平行光阴影
我们首先来绘制正交的平行光阴影,之后再绘制透视的点光源阴影
1. 阴影测试场景
先抛弃之前的背包,来绘制一个新的场景,直接上代码
BuiltInData.h
float planeVertices[] = {// positions // normals 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f,-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f,25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f,-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f,-25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f,25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f,
};float cubeVertices[] = {// back face-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f,1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f,1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f,1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f,-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f,-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f,// front face-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,// left face-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f,-1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f,-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f,-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f,-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f,-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f,// right face1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f,1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f,// bottom face-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f,1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f,1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f,1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f,-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f,-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f,// top face-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f,1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
};
Main_Shadow.cpp 新建
#include <iostream>
#include <map>#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <gtc/type_ptr.hpp>
#include "stb_image.h"#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>#include "Define.h"
#include "BuiltinData.h"
#include "Shader.h"
#include "Camera.h"
#include "Texture.h"
#include "Model.h"
#include "TextureCube.h"float screenWidth = 800;
float screenHeight = 600;float deltaTime = 0.0f;
float lastFrame = 0.0f;Camera camera;void ProcessKeyboardInput(GLFWwindow* window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE)){glfwSetWindowShouldClose(window, true);}if (glfwGetKey(window, GLFW_KEY_LEFT_ALT)){glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);}else{glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);}camera.ProcessKeyboardInput(window, deltaTime);
}void ProcessMouseInput(GLFWwindow* window, double x, double y)
{camera.ProcessMouseInput(window, x, y, deltaTime);
}void ProcessMouseWheelInput(GLFWwindow* window, double x, double y)
{camera.ProcessMouseWheelInput(window, x, y, deltaTime);
}void OnSetFrameBufferSize(GLFWwindow* window, int width, int height)
{screenWidth = width;screenHeight = height;glViewport(0, 0, screenWidth, screenHeight);camera.UpdateCameraVector();
}GLFWwindow* InitEnv()
{glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "OpenGLRenderer", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;return nullptr;}glfwMakeContextCurrent(window);if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return nullptr;}glfwSetFramebufferSizeCallback(window, OnSetFrameBufferSize);glfwSetCursorPosCallback(window, ProcessMouseInput);glfwSetScrollCallback(window, ProcessMouseWheelInput);glViewport(0, 0, screenWidth, screenHeight);return window;
}void InitCamera()
{Transform cameraTransform;cameraTransform.position = glm::vec3(0, 0, 3);cameraTransform.front = glm::vec3(0, 0, -1);cameraTransform.up = glm::vec3(0, 1, 0);cameraTransform.rotate.yaw = -90;camera = Camera(cameraTransform);camera.far = 100;
}unsigned int planeVAO;
unsigned int cubeVAO;void RenderScene(Shader shader)
{shader.Use();shader.SetVec3("dirLight.ambient", glm::vec3(0.1f));shader.SetVec3("dirLight.diffuse", glm::vec3(0.9f));shader.SetVec3("dirLight.specular", glm::vec3(0.6f));glm::mat4 model = glm::mat4(1);shader.SetMat4("model", model);shader.SetVec3("color", glm::vec3(0.5f, 0.5f, 0.5f));glBindVertexArray(planeVAO);glDrawArrays(GL_TRIANGLES, 0, 6);shader.SetVec3("color", glm::vec3(0, 0.7f, 0.7f));glBindVertexArray(cubeVAO);model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(0.0f, 1.5f, 0.0));model = glm::scale(model, glm::vec3(0.5f));shader.SetMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0));model = glm::scale(model, glm::vec3(0.5f));shader.SetMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(-1.0f, 0.0f, 2.0));model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0)));model = glm::scale(model, glm::vec3(0.25));shader.SetMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(-2.4, 0, -1.5));model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(0.0, 0.0, 1.0)));model = glm::scale(model, glm::vec3(0.25));shader.SetMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);
}int main()
{GLFWwindow* window = InitEnv();if (window == nullptr){EXIT;}InitCamera();glEnable(GL_DEPTH_TEST);stbi_set_flip_vertically_on_load(true);unsigned int planeVBO;glGenVertexArrays(1, &planeVAO);glGenBuffers(1, &planeVBO);glBindVertexArray(planeVAO);glBindBuffer(GL_ARRAY_BUFFER, planeVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glBindVertexArray(0);unsigned int cubeVBO;glGenVertexArrays(1, &cubeVAO);glGenBuffers(1, &cubeVBO);glBindVertexArray(cubeVAO);glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glBindVertexArray(0);glm::vec3 lightPos(4, 4, 4);glm::vec3 lightDir = lightPos - glm::vec3(0.0f);Shader planeShader("Shader/PlaneVertex.glsl", "Shader/PlaneFragment.glsl");while (!glfwWindowShouldClose(window)){float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);ProcessKeyboardInput(window);glm::mat4 view = camera.GetViewMatrix();glm::mat4 projection = glm::mat4(1);projection = glm::perspective(glm::radians(camera.fov), screenWidth / screenHeight, camera.near, camera.far);planeShader.Use();planeShader.SetMat4("projection", projection);planeShader.SetMat4("view", view);planeShader.SetVec3("lightDir", lightDir);planeShader.SetVec3("viewPos", camera.transform.position);RenderScene(planeShader);glfwSwapBuffers(window);glfwPollEvents();}glfwTerminate();return 0;
}
PlaneVertex.glsl 新建
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;out vec3 Normal;
out vec3 FragPos;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);Normal = mat3(transpose(inverse(model))) * aNormal;FragPos = vec3(model * vec4(aPos, 1.0));
}
PlaneFragment.glsl 新建
#version 330 corestruct DirLight {vec3 ambient;vec3 diffuse;vec3 specular;
};out vec4 FragColor;in vec3 Normal;
in vec3 FragPos;uniform vec3 color;
uniform vec3 lightDir;
uniform vec3 viewPos;uniform DirLight dirLight;vec3 CalcDirectionalLight(DirLight light, vec3 normal, vec3 viewDir)
{vec3 lightDirection = normalize(lightDir);vec3 ambient = light.ambient * color;float diff = max(dot(normal, lightDirection), 0);vec3 diffuse = light.diffuse * diff * color;vec3 reflectDir = reflect(-lightDirection, normal);float spec = pow(max(dot(reflectDir, viewDir), 0), 32);vec3 specular = light.specular * spec * color;return ambient + diffuse + specular;
}void main()
{vec3 normal = normalize(Normal);vec3 viewDir = normalize(viewPos - FragPos);FragColor = vec4(CalcDirectionalLight(dirLight, normal, viewDir), 1.0);
}
编译运行,顺利的话可以看见以下图像
2. 绘制ShadowMap
要绘制ShadowMap,我们首先需要一个FBO
这个FBO只需要深度缓冲纹理附件即可
Main_Shadow.cpp
// 阴影深度图宽高
const unsigned int shadowWidth = 2048, shadowHeight = 2048;// [main]
// ShadowMap FBO
unsigned int depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
unsigned int depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
// GL_DEPTH_COMPONENT
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowWidth, shadowHeight, 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);
接着是Shader,直接输出深度即可
ShadowMapVertex.glsl 新建
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);
}
ShadowMapFragment.glsl 新建
#version 330 corevoid main()
{ gl_FragDepth = gl_FragCoord.z;
}
渲染ShadowMap
Main_Shadow.cpp
// [main]
Shader shadowShader("Shader/ShadowMapVertex.glsl", "Shader/ShadowMapFragment.glsl");// [主循环]
// 渲染ShadowMap
{// 把光源当作相机,这里基于光源计算变换矩阵glm::mat4 lightProjection, lightView;lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, 0.1f, 15.0f);lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));shadowShader.Use();shadowShader.SetMat4("projection", lightProjection);shadowShader.SetMat4("view", lightView);glViewport(0, 0, shadowWidth, shadowHeight);glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);glClear(GL_DEPTH_BUFFER_BIT);RenderScene(shadowShader);
}// 转换回来
glViewport(0, 0, screenWidth, screenHeight);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
为了查看ShadowMap,我们使用屏幕后处理的思路,将ShadowMap绘制到屏幕Quad上
Main_Shadow.cpp
// [main]
// screenQuadVertices之前在BuiltInData.h中已定义
unsigned int screenQuadVAO, screenQuadVBO;
glGenVertexArrays(1, &screenQuadVAO);
glGenBuffers(1, &screenQuadVBO);
glBindVertexArray(screenQuadVAO);
glBindBuffer(GL_ARRAY_BUFFER, screenQuadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(screenQuadVertices), &screenQuadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));Shader screenShader("Shader/ScreenVertex.glsl", "Shader/ScreenFragment.glsl");//[主循环]
// ...
// 显示ShadowMap
{glBindFramebuffer(GL_FRAMEBUFFER, 0);glClearColor(1.0f, 1.0f, 1.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);screenShader.Use();screenShader.SetInt("screenTexture", 0);glBindVertexArray(screenQuadVAO);glDisable(GL_DEPTH_TEST);glBindTexture(GL_TEXTURE_2D, depthMap);glDrawArrays(GL_TRIANGLES, 0, 6);glEnable(GL_DEPTH_TEST);
}
ScreenFragment.glsl
屏幕上输出r通道,即深度
// ...void main()
{ FragColor = vec4(vec3(texture(screenTexture, TexCoords).r), 1.0);
}
编译运行,顺利的话可以看见以下图像
3. 绘制阴影
ShadowMap准备好后,接下来就可以开始绘制阴影了
首先修改物体的着色器,我们传入光源的VP变换矩阵,计算变换后的片段NDC坐标,接着在片段着色器中采样ShadowMap并比较距离得出shadow值
PlaneVertex.glsl
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;uniform mat4 lightProjection;
uniform mat4 lightView;out vec3 Normal;
out vec3 FragPos;out vec4 FragPosLightSpace;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);Normal = mat3(transpose(inverse(model))) * aNormal;FragPos = vec3(model * vec4(aPos, 1.0));// 光源空间下的片段坐标FragPosLightSpace = lightProjection * lightView * vec4(FragPos, 1.0f);
}
PlaneFragment.glsl
#version 330 corestruct DirLight {vec3 ambient;vec3 diffuse;vec3 specular;
};out vec4 FragColor;in vec3 Normal;
in vec3 FragPos;
// 光源空间的VP变换矩阵
in vec4 FragPosLightSpace;uniform vec3 color;
uniform vec3 lightDir;
uniform vec3 viewPos;// ShadowMap
uniform sampler2D shadowMap;uniform DirLight dirLight;vec3 CalcDirectionalLight(DirLight light, vec3 normal, vec3 viewDir, float shadow)
{vec3 lightDirection = normalize(lightDir);vec3 ambient = light.ambient * color;float diff = max(dot(normal, lightDirection), 0);vec3 diffuse = light.diffuse * diff * color;vec3 reflectDir = reflect(-lightDirection, normal);float spec = pow(max(dot(reflectDir, viewDir), 0), 32);vec3 specular = light.specular * spec * color;// 漫反射和镜面反射乘以shadow值return ambient + diffuse * shadow + specular * shadow;
}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 normal = normalize(Normal);vec3 viewDir = normalize(viewPos - FragPos);float shadow = ShadowCalculation(FragPosLightSpace);FragColor = vec4(CalcDirectionalLight(dirLight, normal, viewDir, (1 - shadow)), 1.0);
}
接着在cpp中传入需要用到的uniform变量并绘制场景
Main_Shadow.cpp
// [主循环]
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::mat4(1);
projection = glm::perspective(glm::radians(camera.fov), screenWidth / screenHeight, camera.near, camera.far);planeShader.Use();
planeShader.SetMat4("projection", projection);
planeShader.SetMat4("view", view);
planeShader.SetMat4("lightProjection", lightProjection);
planeShader.SetMat4("lightView", lightView);
planeShader.SetVec3("lightDir", lightDir);
planeShader.SetVec3("viewPos", camera.transform.position);
planeShader.SetInt("shadowMap", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthMap);RenderScene(planeShader);
编译运行,顺利的话可以看见以下图像
4. 自遮挡
可以看到,目前我们绘制出来的场景中布满了一条一条的条纹,形成摩尔纹的样式
这是由于ShadowMap分辨率的影响而导致的,如下图所示 (图片来自于LearnOpenGL)
如图,每个斜线代表ShadowMap中一个单独的纹素,分辨率越高,上图的锯齿就会越密集
可以发现斜线有一部分位于地下,另一部分位于地上,从而导致当我们进行采样ShadowMap的时候,不同的片段采样得到的深度值是一样的,最终产生结果中的条纹样式
我们可以定义一个Bias值作为容差,当 currentDepth
和 closestDepth
的差异小于 Bias
时就认为没有阴影
float bias = 0.005;
float shadow = currentDepth -bias > closestDepth ? 1.0 : 0.0;
还可以根据片段朝向光线的角度来调整Bias
,这样在倾角过大的时候也不会失真
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
5. 阴影悬浮 (彼得潘 Peter Panning)
看起来好多了,但是拉近后仔细观察会发现新的问题
由于不合适的Bias
值会让物体看起来悬浮起来了一样,这个现象称为 阴影悬浮,也俗称 彼得潘 (Peter Panning)
这里我们可以使用 剔除 来解决这一点
在渲染ShadowMap的时候,我们对物体进行正面剔除,绘制场景时改回背面剔除或不剔除
去掉Bias
相关代码,并增加面剔除
Main_Shadow.cpp
// [main]
glEnable(GL_CULL_FACE);// [主循环]
// 渲染阴影时
glCullFace(GL_FRONT);
RenderScene(shadowShader);
glCullFace(GL_BACK);
6. 过采样
阴影悬浮问题解决了,目前还存在两个问题
第一是不远处我们还可以发现有一样的阴影投影到地面上,这是由于ShadowMap为Repeat模式导致的,我们可以换为 GL_CLAMP_TO_BORDER
并设置Border颜色为白色 (无限远)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
第二是远处仍然还可以看见大面积的阴影,这是由于距离已经超过了渲染ShadowMap所用的正交视锥的远平面导致的
我们可以在片段着色器计算shadow值前判断一下深度是否已超出视锥体
float ShadowCalculation(vec4 fragPosLightSpace)
{// ...if (projCoords.z > 1.0)return 0;// ...
}
现在效果好了很多了,但是拉近一点我们还会发现有很多锯齿,接下来采用PCF的方式再优化一下
三、百分比近似滤波 PCF (Percentage Closer Filtering)
由于ShadowMap的分辨率固定且有限,一个纹素可能会覆盖多个片段,导致多个片段会从ShadowMap中采样得到相同的深度值,结果就出现了上图中的锯齿
我们可以使用 PCF 的方式来生成更为柔和的阴影
其主要思想就是多次采样ShadowMap,每一次采样的坐标稍有不同,最后将结果取平均或加权平均,类似后处理中用到的卷积核
修改shadow计算的函数
PlaneFragment.glsl
float ShadowCalculation(vec4 fragPosLightSpace)
{// 执行透视除法vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;if (projCoords.z > 1.0)return 0;// 变换到[0,1]的范围projCoords = projCoords * 0.5 + 0.5;float shadow = 0.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 += projCoords.z > pcfDepth ? 1.0 : 0.0; } }shadow /= 9.0;return shadow;
}
这里 textureSize
会返回采样器纹理在0级mipmap的宽高向量,取倒数则为纹素大小
我们对目标坐标上下左右共9个位置进行采样,最后除以9取平均
这样,我们就得到了更为柔和的阴影
若想要更好的效果,还可以使用 PCSS 等方法,具体算法可以去看 GAMES202 的阴影章节
现在已经渲染出正交的平行光阴影,下一节将会学习点光源阴影的做法
完整代码可在顶部Git仓库中找到
下接:https://blog.csdn.net/weixin_44506615/article/details/151876995?spm=1001.2014.3001.5502