当前位置: 首页 > news >正文

【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)
其思路就是:

  1. 我们首先在光源的位置沿光源方向渲染一张深度图
  2. 正常渲染时,获取片段到光源的距离d (将片段转换到光源空间,可以理解为把光源视作一个摄像机),以及片段基于光源空间的坐标xy
  3. 通过xy在深度图上进行采样,获得深度depth,比较ddepth的大小,如果d大于depth则认为此处无法接收到光照,反之则可以

如下图所示 (图片来自于LearnOpenGL),这里的LIT BY LIGHTdepthIN SHADOW则为d
阴影映射ShadowMap
阴影映射ShadowMap
这里第一步渲染的深度图即为 阴影映射 (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);
}

编译运行,顺利的话可以看见以下图像
ShadowMap阴影映射

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值作为容差,当 currentDepthclosestDepth 的差异小于 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);

Bias解决自遮挡

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 的阴影章节
PCF
现在已经渲染出正交的平行光阴影,下一节将会学习点光源阴影的做法

完整代码可在顶部Git仓库中找到
下接:https://blog.csdn.net/weixin_44506615/article/details/151876995?spm=1001.2014.3001.5502

http://www.dtcms.com/a/391087.html

相关文章:

  • MongoDB文档规范
  • 让设计、办公、创作效率翻倍的技术文章大纲
  • 能不能写一个linux下类vim的编辑器
  • Linux02: 编辑器nano的常用技巧
  • UDP和TCP对比通俗讲解
  • 【ReText】1.3 Python multiprocessing 库详解
  • Liunx系统下出现“Could not resolve host: mirrorlist.centos.org; 未知的错误”地解决方案
  • CentOS Stream 9安装系统(LVM扩容案例)
  • Docusign AI 全球化:构建安全、合规的多语言协议管理
  • C# 基于halcon的视觉工作流-章37-零件测量
  • 第二部分:VTK核心类详解(第38章 vtkPointData点数据类)
  • 木卫四科技 × 一汽解放商用车开发院: 共驱商用车 AI 研发新程
  • 【C++闯关笔记】STL:stack与queue的学习和使用
  • [HCTF 2018] WarmUp
  • Vue 学习随笔系列二十六 —— 动态表头
  • BIM 可视化运维平台 + IBMS 中央集成系统一体化解决方案:构建虚实融合的智慧运营中枢
  • XSUN_DESKTOP_PET(桌面宠物)
  • 具身智能VR遥操开发记录
  • 构建AI智能体:三十八、告别“冷启动”:看大模型如何解决推荐系统的世纪难题
  • [重学Rust]之结构体打印和转换
  • 数据结构(陈越,何钦铭) 第十一讲 散列查找
  • 2025年JBD SCI2区TOP,基于改进蚁群算法的应急路径规划,深度解析+性能实测
  • UIKit-layer
  • 一物一码公司推荐再互动平台
  • Wireshark捕获MQTT报文
  • Docker镜像核心作战手册:镜像命令全解析+离线迁移实战+压缩共享储存,打造无缝跨环境部署!
  • Static Deinitialization Order Fiasco
  • 如何使用 Qt Creator 高效调试
  • 保障路灯用电安全!配电箱漏电检测,为城市照明筑牢防线
  • 不同版本tensorflow推理报错解决方法