赞
踩
【项目地址:点击这里这里这里】
在用几何着色器做机器人爆破中出现了一个BUG,此时才发现并修复
(不过代码已经在发现后及早做了修改,以几何着色器那节的代码为准,并且之后的章节都没有再去弄爆破效果,所以除了那节以外没有影响)
详情可点击 几何着色器 的4.1节
本节对应官网学习内容:阴影映射
关于知识部分,也可以结合着看当时学games101的课所做的笔记,为了引入光线追踪而对ShadowMapping进行了介绍
GAMES101课程学习笔记—Lec 13~14(1):Ray Tracing(1) Whitted风格光线追踪
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了。
假设有一个地板,在光源和它之间有一个大盒子。由于光源处向光线方向看去,可以看到这个盒子,但看不到地板的一部分,这部分就应该在阴影中了。
我们会想要得到射线第一次击中的点,也就是最近点,然后这个最近点就可以被拿来和射线上其他点进行对比,射线上除了最近点以外打到的点都应该处于阴影中。
虽然很形象,但是对从光源发出的射线上的成千上万个点进行遍历是极其消耗性能的,我们会进行相似的操作,也就是比较深度缓冲。
在深度测试的章节中,我们知道,深度缓冲指的是,在摄像机视角下,对应一个片段的0到1的深度值。
如果我们从光源来渲染场景,然后把深度值的结果存到纹理中,通过这种方式,我们就能得到光线打到的“最近点”。我们管储存在纹理中的所有这些深度值,叫做深度贴图(depth map)或阴影贴图。
阴影映射由两个步骤组成:
首先,我们渲染深度贴图
然后我们像往常一样渲染场景,使用生成的深度贴图来计算片段是否在阴影之中
我们新建一个cpp文件用来做练习深度贴图的绘制的例子
思路很简单,创建一个2D深度纹理作为帧缓冲的深度缓冲
完成之后进行两个步骤的完整的渲染阶段
渲染思路如下(main函数的思路):
我们会用到两组shader
这组shader在深度缓冲绘制场景
当我们以光的透视图进行场景渲染的时候,我们会用一个比较简单的着色器,这个着色器除了把顶点变换到光空间以外,不会做得更多了。
vertexShadowMappingDepth.vert
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 lightSpaceMatrix;
uniform mat4 model;
void main()
{
gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}
fragmentShadowMappingDepth.frag
这个空片段着色器什么也不干,运行完后,深度缓冲会被默认更新
#version 330 core
void main()
{
// gl_FragDepth = gl_FragCoord.z;
}
下面这组shader绑定深度缓冲,根据深度缓冲绘图,把图画到屏幕上
因为我们使用的是一个所有光线都平行的定向光。
出于这个原因,我们将为光源使用正交投影矩阵,透视图将没有任何变形:
fragment_debug_quad_depth.frag
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D depthMap; uniform float near_plane; uniform float far_plane; // required when using a perspective projection matrix float LinearizeDepth(float depth) { float z = depth * 2.0 - 1.0; // Back to NDC return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane)); } void main() { float depthValue = texture(depthMap, TexCoords).r; // FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective FragColor = vec4(vec3(depthValue), 1.0); // orthographic }
vertex_debug_quad.vert
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 lightSpaceMatrix;
uniform mat4 model;
void main()
{
gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}
最后,在光的透视图视角下,很完美地用每个可见片段的最近深度填充了深度缓冲。
通过将这个纹理投射到一个2D屏幕四边形上(和我们在帧缓冲一节做的后处理过程类似),就能在屏幕上显示出来,我们会获得这样的东西:
以下是man.cpp,对渲染思路进行了实现,相关过程都写了注释
#include <iostream> #define GLEW_STATIC #include <GL/glew.h> #include <GLFW/glfw3.h> #include "Shader.h" #include "Camera.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> //检测窗口变化的回调函数 void framebuffer_size_callback(GLFWwindow* window, int width, int height); //鼠标移动镜头 void mouse_callback(GLFWwindow* window, double xpos, double ypos); //滚轮缩放 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); //检测输入 void processInput(GLFWwindow *window); //导入图 unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format); //渲染场景 void renderScene(const Shader * shader); void renderCube(); //渲染帧缓冲到眼前 void renderQuad(); // settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; #pragma region Camera Declare //建立camera glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); Camera camera(cameraPos, cameraTarget, cameraUp); #pragma endregion #pragma region Input Declare //移动用 float deltaTime = 0.0f; // 当前帧与上一帧的时间差 float lastFrame = 0.0f; // 上一帧的时间 void processInput(GLFWwindow* window) { //看是不是按下esc键 然后退出 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } //更流畅点的摄像机系统 if (deltaTime != 0) { camera.cameraPosSpeed = 5 * deltaTime; } //camera前后左右根据镜头方向移动 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.PosUpdateForward(); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.PosUpdateBackward(); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.PosUpdateLeft(); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.PosUpdateRight(); if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) camera.PosUpdateUp(); if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) camera.PosUpdateDown(); } float lastX; float lastY; bool firstMouse = true; unsigned int planeVAO; //鼠标控制镜头方向 void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse == true) { lastX = xpos; lastY = ypos; firstMouse = false; } float deltaX, deltaY; float sensitivity = 0.05f; deltaX = (xpos - lastX)*sensitivity; deltaY = (ypos - lastY)*sensitivity; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(deltaX, deltaY); }; //缩放 float fov = 45.0f; void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { if (fov >= 1.0f && fov <= 45.0f) fov -= yoffset; if (fov <= 1.0f) fov = 1.0f; if (fov >= 45.0f) fov = 45.0f; } #pragma endregion int main() { #pragma region Open a Window glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Open GLFW Window GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "My OpenGL Game", NULL, NULL); if (window == NULL) { printf("Open window failed."); glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // 关闭鼠标显示 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // 回调函数监听鼠标 glfwSetCursorPosCallback(window, mouse_callback); // 回调函数监听滚轮 glfwSetScrollCallback(window, scroll_callback); // 每当窗口改变大小就会启动 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // Init GLEW glewExperimental = true; if (glewInit() != GLEW_OK) { printf("Init GLEW failed."); glfwTerminate(); return -1; } glEnable(GL_DEPTH_TEST); #pragma endregion #pragma region Init Shader Program // 所需要的shader Shader* simpleDepthShader = new Shader("vertexShadowMappingDepth.vert", "fragmentShadowMappingDepth.frag"); Shader* debugDepthQuad=new Shader("vertex_debug_quad.vert", "fragment_debug_quad_depth.frag"); #pragma endregion // 地面绘制的准备 float planeVertices[] = { // positions // normals // texcoords 25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f, -25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f, 25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f, -25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f, 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 10.0f }; 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, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindVertexArray(0); unsigned int woodTexture = LoadImageToGPU("wood.jpg", GL_RGB, GL_RGB); // 深度贴图 const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; unsigned int depthMapFBO; glGenFramebuffers(1, &depthMapFBO); // 创建深度贴图 unsigned int 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); // 深度贴图绑定到GL_FRAMEBUFFER上 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); // 绑定深度贴图的位置 debugDepthQuad->use(); glUniform1i(glGetUniformLocation(debugDepthQuad->ID, "depthMap"), 0); // 假定的光源位置 // ------------- glm::vec3 lightPos(-2.0f, 4.0f, -1.0f); // 渲染循环 while (!glfwWindowShouldClose(window)) { // 显示相关的数据 float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // 检测输入 processInput(window); // 缓存清理 glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 准备光源的VP矩阵 glm::mat4 lightProjection, lightView; glm::mat4 lightSpaceMatrix; float near_plane = 1.0f, far_plane = 7.5f; lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane); lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0)); lightSpaceMatrix = lightProjection * lightView; // 准备光源渲染用的shader simpleDepthShader->use(); glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader->ID, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix)); // 1. 首选渲染深度贴图 // 阴影的窗口大小 glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); // 绑定到深度缓冲 glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); // 在深度缓冲绘制场景 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, woodTexture); renderScene(simpleDepthShader); // 回到原来的默认缓冲 glBindFramebuffer(GL_FRAMEBUFFER, 0); // 2. 像往常一样渲染场景,但这次使用深度贴图 // 打开正常的窗口 glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 绑定深度缓冲,把图画到屏幕上 debugDepthQuad->use(); glUniform1f(glGetUniformLocation(debugDepthQuad->ID, "near_plane"), near_plane); glUniform1f(glGetUniformLocation(debugDepthQuad->ID, "far_plane"), far_plane); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, depthMap); renderQuad(); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &planeVAO); glDeleteBuffers(1, &planeVBO); glfwTerminate(); return 0; } // renders the 3D scene // -------------------- void renderScene(const Shader * shader) { // floor glm::mat4 model = glm::mat4(1.0f); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); glBindVertexArray(planeVAO); glDrawArrays(GL_TRIANGLES, 0, 6); // cubes 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); } // renderCube() renders a 1x1 3D cube in NDC. // ------------------------------------------------- unsigned int cubeVAO = 0; unsigned int cubeVBO = 0; void renderCube() { // initialize (if necessary) if (cubeVAO == 0) { float vertices[] = { // back face -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left // front face -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left // left face -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right // right face 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left // bottom face -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right // top face -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left }; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); // fill buffer glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // link vertex attributes glBindVertexArray(cubeVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } // render Cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); } // renderQuad() renders a 1x1 XY quad in NDC unsigned int quadVAO = 0; unsigned int quadVBO; void renderQuad() { if (quadVAO == 0) { float quadVertices[] = { // positions // texture Coords -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, }; // setup plane VAO glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); } glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { //确保视口与新的窗口尺寸匹配;注意宽度和高度将大于屏幕显示上指定的尺寸 glViewport(0, 0, width, height); } //加载一般的图片 unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format) { unsigned int TexBuffer; glGenTextures(1, &TexBuffer); glBindTexture(GL_TEXTURE_2D, TexBuffer); // 为当前绑定的纹理对象设置环绕、过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // 加载并生成纹理 int width, height, nrChannel; unsigned char *data = stbi_load(filename, &width, &height, &nrChannel, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { printf("Failed to load texture"); } stbi_image_free(data); return TexBuffer; }
正确地生成深度贴图以后我们就可以开始生成阴影了。
在之前的渲染思路上拓展
渲染思路如下(main函数的思路):
我们对于带上阴影的场景绘制新增的是这组shader
对于顶点着色器来说
这儿的新的地方是FragPosLightSpace这个输出向量。我们用同一个lightSpaceMatrix,把世界空间顶点位置转换为光空间。
顶点着色器传递一个普通的经变换的世界空间顶点位置vs_out.FragPos和一个光空间的vs_out.FragPosLightSpace给片段着色器。
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; 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() { vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); vs_out.Normal = transpose(inverse(mat3(model))) * aNormal; vs_out.TexCoords = aTexCoords; vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0); gl_Position = projection * view * model * vec4(aPos, 1.0); }
片段着色器使用Blinn-Phong光照模型渲染场景。
我们接着计算出一个shadow值,当fragment在阴影中时是1.0,在阴影外是0.0。
然后,diffuse和specular颜色会乘以这个阴影元素。
由于阴影不会是全黑的(由于散射),我们把ambient分量从乘法中剔除。
#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) { // perform perspective divide vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // transform to [0,1] range projCoords = projCoords * 0.5 + 0.5; // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) float closestDepth = texture(shadowMap, projCoords.xy).r; // get depth of current fragment from light's perspective float currentDepth = projCoords.z; // check whether current frag pos is in shadow 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(0.3); // ambient vec3 ambient = 0.8 * 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; // calculate shadow float shadow = ShadowCalculation(fs_in.FragPosLightSpace); vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; FragColor = vec4(lighting, 1.0); }
我们声明了一个shadowCalculation函数,用它计算阴影。
片段着色器的最后,我们我们把diffuse和specular乘以(1-阴影元素),这表示这个片段有多大成分不在阴影中。
这个片段着色器还需要两个额外输入,一个是光空间的片段位置和第一个渲染阶段得到的深度贴图。
首先要检查一个片段是否在阴影中,把光空间片段位置转换为裁切空间的标准化设备坐标。
当我们在顶点着色器输出一个裁切空间顶点位置到gl_Position时,OpenGL自动进行一个透视除法,将裁切空间坐标的范围-w到w转为-1到1,这要将x、y、z元素除以向量的w元素来实现。
由于裁切空间的FragPosLightSpace并不会通过gl_Position传到片段着色器里,我们必须自己做透视除法
我们会得到这样的结果
我们把渲染思路实现的话,就是main.cpp的代码
#include <iostream> #define GLEW_STATIC #include <GL/glew.h> #include <GLFW/glfw3.h> #include "Shader.h" #include "Camera.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> //检测窗口变化的回调函数 void framebuffer_size_callback(GLFWwindow* window, int width, int height); //鼠标移动镜头 void mouse_callback(GLFWwindow* window, double xpos, double ypos); //滚轮缩放 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); //检测输入 void processInput(GLFWwindow *window); //导入图 unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format); //渲染场景 void renderScene(const Shader * shader); void renderCube(); //渲染帧缓冲到眼前 void renderQuad(); // settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; #pragma region Camera Declare //建立camera glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); Camera camera(cameraPos, cameraTarget, cameraUp); #pragma endregion #pragma region Input Declare //移动用 float deltaTime = 0.0f; // 当前帧与上一帧的时间差 float lastFrame = 0.0f; // 上一帧的时间 void processInput(GLFWwindow* window) { //看是不是按下esc键 然后退出 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } //更流畅点的摄像机系统 if (deltaTime != 0) { camera.cameraPosSpeed = 5 * deltaTime; } //camera前后左右根据镜头方向移动 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.PosUpdateForward(); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.PosUpdateBackward(); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.PosUpdateLeft(); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.PosUpdateRight(); if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) camera.PosUpdateUp(); if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) camera.PosUpdateDown(); } float lastX; float lastY; bool firstMouse = true; unsigned int planeVAO; //鼠标控制镜头方向 void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse == true) { lastX = xpos; lastY = ypos; firstMouse = false; } float deltaX, deltaY; float sensitivity = 0.05f; deltaX = (xpos - lastX)*sensitivity; deltaY = (ypos - lastY)*sensitivity; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(deltaX, deltaY); }; //缩放 float fov = 45.0f; void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { if (fov >= 1.0f && fov <= 45.0f) fov -= yoffset; if (fov <= 1.0f) fov = 1.0f; if (fov >= 45.0f) fov = 45.0f; } #pragma endregion int main() { #pragma region Open a Window glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Open GLFW Window GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "My OpenGL Game", NULL, NULL); if (window == NULL) { printf("Open window failed."); glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // 关闭鼠标显示 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // 回调函数监听鼠标 glfwSetCursorPosCallback(window, mouse_callback); // 回调函数监听滚轮 glfwSetScrollCallback(window, scroll_callback); // 每当窗口改变大小就会启动 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // Init GLEW glewExperimental = true; if (glewInit() != GLEW_OK) { printf("Init GLEW failed."); glfwTerminate(); return -1; } glEnable(GL_DEPTH_TEST); #pragma endregion #pragma region Init Shader Program // 所需要的shader Shader* simpleDepthShader = new Shader("vertexShadowMappingDepth.vert", "fragmentShadowMappingDepth.frag"); Shader* debugDepthQuad = new Shader("vertex_debug_quad.vert", "fragment_debug_quad_depth.frag"); Shader* myShader = new Shader("vertexShadowMappingTest.vert", "fragmentShadowMappingTest.frag"); #pragma endregion // 地面绘制的准备 float planeVertices[] = { // positions // normals // texcoords -35.0f, 0.5f ,-35.0f, 0.0f, 1.0f, 0.0f, 0.0f, 35.0f, 35.0f, 0.5f, 35.0f, 0.0f, 1.0f, 0.0f, 35.0f, 0.0f, 35.0f, 0.5f, -35.0f, 0.0f, 1.0f, 0.0f, 35.0f, 35.0f, 35.0f, 0.5f , 35.0f, 0.0f, 1.0f, 0.0f, 35.0f, 0.0f, -35.0f, 0.5f ,-35.0f, 0.0f, 1.0f, 0.0f, 0.0f, 35.0f, -35.0f, 0.5f ,35.0f, 0.0f, 1.0f, 0.0f, 0.0f ,0.0f, }; 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, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindVertexArray(0); unsigned int woodTexture = LoadImageToGPU("wood.jpg", GL_RGB, GL_RGB); // 深度贴图 const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; unsigned int depthMapFBO; glGenFramebuffers(1, &depthMapFBO); // 创建深度贴图 unsigned int 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); // 深度贴图绑定到GL_FRAMEBUFFER上 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 myShader->use(); glUniform1i(glGetUniformLocation(myShader->ID, "diffuseTexture"), 0); glUniform1i(glGetUniformLocation(myShader->ID, "shadowMap"), 1); //绑定深度贴图位置 debugDepthQuad->use(); glUniform1i(glGetUniformLocation(debugDepthQuad->ID, "depthMap"), 0); // 假定的光源位置 // ------------- glm::vec3 lightPos(-2.0f, 4.0f, -1.0f); // 渲染循环 while (!glfwWindowShouldClose(window)) { // 显示相关的数据 float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // 检测输入 processInput(window); // 缓存清理 glClearColor(0.3f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 准备光源的VP矩阵 glm::mat4 lightProjection, lightView; glm::mat4 lightSpaceMatrix; float near_plane = 1.0f, far_plane = 10.0f; lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane); lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0)); lightSpaceMatrix = lightProjection * lightView; // 准备光源渲染用的shader simpleDepthShader->use(); glUniformMatrix4fv(glGetUniformLocation(simpleDepthShader->ID, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix)); // 阴影的窗口大小 glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); // 绑定到深度缓冲 glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); // 在深度缓冲绘制场景 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, woodTexture); renderScene(simpleDepthShader); // 回到原来的默认缓冲 glBindFramebuffer(GL_FRAMEBUFFER, 0); // 打开正常的窗口 glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 带上阴影的场景 // -------------------------------------------------------------- glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); myShader->use(); glm::mat4 projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "view"), 1, GL_FALSE, glm::value_ptr(view)); // set light uniforms glUniform3f(glGetUniformLocation(myShader->ID, "viewPos"), camera.Position.x, camera.Position.y, camera.Position.z); glUniform3f(glGetUniformLocation(myShader->ID, "lightPos"), lightPos.x, lightPos.y, lightPos.z); glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "lightSpaceMatrix"), 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix)); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, woodTexture); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, depthMap); renderScene(myShader); // 预备用:绑定深度缓冲,把图画到屏幕上,将深度贴图渲染到四边形以进行可视化调试 debugDepthQuad->use(); glUniform1f(glGetUniformLocation(debugDepthQuad->ID, "near_plane"), near_plane); glUniform1f(glGetUniformLocation(debugDepthQuad->ID, "far_plane"), far_plane); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, depthMap); //renderQuad(); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &planeVAO); glDeleteBuffers(1, &planeVBO); glfwTerminate(); return 0; } // renders the 3D scene // -------------------- void renderScene(const Shader * shader) { // floor glm::mat4 model = glm::translate(glm::mat4(1.0f), { 0,-2,0 }); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); glBindVertexArray(planeVAO); glDrawArrays(GL_TRIANGLES, 0, 6); // cubes 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); 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)); glUniformMatrix4fv(glGetUniformLocation(shader->ID, "model"), 1, GL_FALSE, glm::value_ptr(model)); renderCube(); } // renderCube() renders a 1x1 3D cube in NDC. // ------------------------------------------------- unsigned int cubeVAO = 0; unsigned int cubeVBO = 0; void renderCube() { // initialize (if necessary) if (cubeVAO == 0) { float vertices[] = { // back face -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left // front face -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left // left face -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right // right face 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left // bottom face -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right // top face -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left }; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); // fill buffer glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // link vertex attributes glBindVertexArray(cubeVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } // render Cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); } // renderQuad() renders a 1x1 XY quad in NDC unsigned int quadVAO = 0; unsigned int quadVBO; void renderQuad() { if (quadVAO == 0) { float quadVertices[] = { // positions // texture Coords -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, }; // setup plane VAO glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); } glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { //确保视口与新的窗口尺寸匹配;注意宽度和高度将大于屏幕显示上指定的尺寸 glViewport(0, 0, width, height); } //加载一般的图片 unsigned int LoadImageToGPU(const char* filename, GLint internalFormat, GLenum format) { unsigned int TexBuffer; glGenTextures(1, &TexBuffer); glBindTexture(GL_TEXTURE_2D, TexBuffer); // 为当前绑定的纹理对象设置环绕、过滤方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // 加载并生成纹理 int width, height, nrChannel; unsigned char *data = stbi_load(filename, &width, &height, &nrChannel, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { printf("Failed to load texture"); } stbi_image_free(data); return TexBuffer; }
我们试图让阴影映射工作,但是也看到了,阴影映射还是有点不真实,我们修复它才能获得更好的效果,这是下面的部分所关注的焦点。
我们看上面的场景,放大看会发现明显的线条样式:
这种阴影贴图的不真实感叫做阴影失真(Shadow Acne),下图解释了成因:
因为阴影贴图受限于分辨率,在距离光源比较远的情况下,多个片段可能从深度贴图的同一个值中去采样。
图片每个斜坡代表深度贴图一个单独的纹理像素。你可以看到,多个片段从同一个深度值进行采样。
虽然很多时候没问题,但是当光源以一个角度朝向表面的时候就会出问题。
这种情况下深度贴图是从一个角度下进行渲染的,多个片段就会从同一个斜坡的深度纹理像素中采样,有些在地板上面,有些在地板下面;这样我们所得到的阴影就有了差异。
因为这个,有些片段被认为是在阴影之中,有些不在,由此产生了图片中的条纹样式。
我们可以用一个叫做阴影偏移(shadow bias)的技巧来解决这个问题,我们简单的对表面的深度(或深度贴图)应用一个偏移量,这样片段就不会被错误地认为在表面之下了。
使用了偏移量后,所有采样点都获得了比表面深度更小的深度值,这样整个表面就正确地被照亮,没有任何阴影。
我们可以这样在片段着色器中计算阴影时实现这个偏移:
float bias = 0.005;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
虽然一个0.005的偏移就能帮到很大的忙,但是有些表面坡度很大,仍然会产生阴影失真。
有一个更加可靠的办法能够根据表面朝向光线的角度更改偏移量:使用点乘:
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
这里我们有一个偏移量的最大值0.05,和一个最小值0.005,它们是基于表面法线和光照方向的。
这样像地板这样的表面几乎与光源垂直,得到的偏移就很小,而比如立方体的侧面这种表面得到的偏移就更大。
下图展示了同一个场景,但使用了阴影偏移,效果的确更好:
选用正确的偏移数值,在不同的场景中都需要一些像这样的轻微调校,但大多情况下,实际上就是增加偏移量直到所有失真都被移除的问题。
使用阴影偏移的一个缺点是我们对物体的实际深度应用了平移。
偏移有可能足够大,以至于可以看出阴影相对实际物体位置的偏移,可以从下图看到这个现象
这个阴影失真叫做悬浮(Peter Panning),因为物体看起来轻轻悬浮在表面之上(Peter Pan就是童话彼得潘,而panning有平移、悬浮之意,而且彼得潘是个会飞的男孩…)。
关于这个问题,感觉知乎的这个回答很好
偏移和剔除前表面是两种解决acne的方法,原理都是:不能让阴影图中记录的深度和最后渲染时再次转换到灯光空间下的深度值一样,这样因为分辨率问题就会出现误差。但如果同时使用 bias 和前表面剔除会产生一些问题
我们可以使用一个叫技巧解决大部分的Peter panning问题:当渲染深度贴图时候使用正面剔除(front face culling),我们曾经在面剔除教程中OpenGL默认是背面剔除。
我们要告诉OpenGL我们要剔除正面。
因为我们只需要深度贴图的深度值,对于实体物体无论我们用它们的正面还是背面都没问题。
使用背面深度不会有错误,因为阴影在物体内部有错误我们也看不见。
为了修复peter游移,我们要进行正面剔除,先必须开启GL_CULL_FACE:
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
RenderSceneToDepthMap();
glCullFace(GL_BACK); // 不要忘记设回原先的culling face
这十分有效地解决了peter panning的问题,但只对内部不会对外开口的实体物体有效。
我们的场景中,在立方体上工作的很好,但在地板上无效,因为正面剔除完全移除了地板。
地面是一个单独的平面,不会被完全剔除。
如果打算使用这个技巧解决peter panning必须考虑到只有剔除物体的正面才有意义。
另一个要考虑到的地方是接近阴影的物体仍然会出现不正确的效果。
必须考虑到何时使用正面剔除对物体才有意义。
不过使用普通的偏移值通常就能避免peter panning。
光的视锥不可见的区域一律被认为是处于阴影中,不管它真的处于阴影之中。
出现这个状况是因为超出光的视锥的投影坐标比1.0大,这样采样的深度纹理就会超出他默认的0到1的范围。
根据纹理环绕方式,我们将会得到不正确的深度结果,它不是基于真实的来自光源的深度值。
我们可以在图中看到,光照有一个区域,超出该区域就成为了阴影;这个区域实际上代表着深度贴图的大小,这个贴图投影到了地板上。
发生这种情况的原因是我们之前将深度贴图的环绕方式设置成了GL_REPEAT。
我们宁可让所有超出深度贴图的坐标的深度范围是1.0,这样超出的坐标将永远不在阴影之中。我们可以储存一个边框颜色,然后把深度贴图的纹理环绕选项设置为GL_CLAMP_TO_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);
GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
现在如果我们采样深度贴图0到1坐标范围以外的区域,纹理函数总会返回一个1.0的深度值,阴影值为0.0。结果看起来会更真实
同时仍有一部分是黑暗区域。
那里的坐标超出了光的正交视锥的远平面。可以看到这片黑色区域总是出现在光源视锥的极远处。
当一个点比光的远平面还要远时,它的投影坐标的z坐标大于1.0。这种情况下,GL_CLAMP_TO_BORDER环绕方式不起作用,因为我们把坐标的z元素和深度贴图的值进行了对比;它总是为大于1.0的z返回true。
解决这个问题也很简单,只要投影向量的z坐标大于1.0,我们就把shadow的值强制设为0.0:
在片段着色器里计算阴影的地方加上
if(projCoords.z > 1.0)
shadow = 0.0;
检查远平面,并将深度贴图限制为一个手工指定的边界颜色,就能解决深度贴图采样超出的问题,我们最终会得到下面我们所追求的效果:
这些结果意味着,只有在深度贴图范围以内的被投影的fragment坐标才有阴影,所以任何超出范围的都将会没有阴影。
由于在游戏中通常这只发生在远处,就会比我们之前的那个明显的黑色区域效果更真实。
阴影现在已经附着到场景中了,不过这仍不是我们想要的。如果放大看阴影,阴影映射对分辨率的依赖很快变得很明显。
因为深度贴图有一个固定的分辨率,多个片段对应于一个纹理像素。
结果就是多个片段会从深度贴图的同一个深度值进行采样,这几个片段便得到的是同一个阴影,这就会产生锯齿边。
可以通过增加深度贴图的分辨率的方式来降低锯齿块,也可以尝试尽可能的让光的视锥接近场景。
另一个(并不完整的)解决方案叫做PCF(percentage-closer filtering),这是一种多个不同过滤方式的组合,它产生柔和阴影,使它们出现更少的锯齿块和硬边。
核心思想是从深度贴图中多次采样,每一次采样的纹理坐标都稍有不同。
每个独立的样本可能在,也可能不再阴影中。
所有的次生结果接着结合在一起,进行平均化,我们就得到了柔和阴影。
一个简单的PCF的实现是简单的从纹理像素四周对深度贴图采样,然后把结果平均起来
下面的代码加入到片段着色器里
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 += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
shadow += currentDepth > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
这个textureSize返回一个给定采样器纹理的0级mipmap的vec2类型的宽和高。
用1除以它,返回一个单独纹理像素的大小,我们用以对纹理坐标进行偏移,确保每个新样本,来自不同的深度值。这里我们采样得到9个值,它们在投影坐标的x和y值的周围,为阴影阻挡进行测试,并最终通过样本的总数目将结果平均化。
使用更多的样本,更改texelSize变量,就可以增加阴影的柔和程度。下面可以看到应用了PCF的阴影:
从稍微远一点的距离看去,阴影效果好多了,也不那么生硬了。如果你放大,仍会看到阴影贴图分辨率的不真实感,但通常对于大多数应用来说效果已经很好了。
实际上PCF还有更多的内容,以及很多技术要点需要考虑以提升柔和阴影的效果,但处于本章内容长度考虑,我们将留在以后讨论。
在渲染深度贴图的时候,正交(Orthographic)和投影(Projection)矩阵之间有所不同。
正交投影矩阵并不会将场景用透视图进行变形,所有视线/光线都是平行的,这使它对于定向光来说是个很好的投影矩阵。
然而透视投影矩阵,会将所有顶点根据透视关系进行变形,结果因此而不同。
下图展示了两种投影方式所产生的不同阴影区域:
透视投影对于光源来说更合理,不像定向光,它是有自己的位置的。透视投影因此更经常用在点光源和聚光灯上,而正交投影经常用在定向光上。
另一个细微差别是,透视投影矩阵,将深度缓冲视觉化经常会得到一个几乎全白的结果。
发生这个是因为透视投影下,深度变成了非线性的深度值,它的大多数可辨范围都位于近平面附近。
为了可以像使用正交投影一样合适地观察深度值,我们必须先将非线性深度值转变为线性的,如我们在深度测试教程中已经讨论过的那样。
#version 330 core out vec4 color; in vec2 TexCoords; uniform sampler2D depthMap; uniform float near_plane; uniform float far_plane; float LinearizeDepth(float depth) { float z = depth * 2.0 - 1.0; // Back to NDC return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane)); } void main() { float depthValue = texture(depthMap, TexCoords).r; color = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective // color = vec4(vec3(depthValue), 1.0); // orthographic }
这个深度值与我们见到的用正交投影的很相似。需要注意的是,这个只适用于调试;正交或投影矩阵的深度检查仍然保持原样,因为相关的深度并没有改变。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。