LearnOpenGL学习笔记—高级光照 03(1):阴影映射

【项目地址:点击这里这里这里

0 前言

在用几何着色器做机器人爆破中出现了一个BUG,此时才发现并修复
(不过代码已经在发现后及早做了修改,以几何着色器那节的代码为准,并且之后的章节都没有再去弄爆破效果,所以除了那节以外没有影响)
详情可点击 几何着色器 的4.1节

1 知识:阴影映射

本节对应官网学习内容:阴影映射
关于知识部分,也可以结合着看当时学games101的课所做的笔记,为了引入光线追踪而对ShadowMapping进行了介绍
GAMES101课程学习笔记—Lec 13~14(1):Ray Tracing(1) Whitted风格光线追踪

阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了。

假设有一个地板,在光源和它之间有一个大盒子。由于光源处向光线方向看去,可以看到这个盒子,但看不到地板的一部分,这部分就应该在阴影中了。

在这里插入图片描述

  • 所有蓝线代表光源可以看到的fragment。
  • 黑线代表被遮挡的fragment:它们应该渲染为带阴影的。
  • 如果我们绘制一条从光源出发到达最右边盒子一个片段上的线段或射线,那么射线将先击中悬浮的盒子,随后才会到达最右侧的盒子。
  • 结果就是悬浮的盒子被照亮,而最右侧的盒子将处于阴影之中。

我们会想要得到射线第一次击中的点,也就是最近点,然后这个最近点就可以被拿来和射线上其他点进行对比,射线上除了最近点以外打到的点都应该处于阴影中。

虽然很形象,但是对从光源发出的射线上的成千上万个点进行遍历是极其消耗性能的,我们会进行相似的操作,也就是比较深度缓冲。

深度测试的章节中,我们知道,深度缓冲指的是,在摄像机视角下,对应一个片段的0到1的深度值。

如果我们从光源来渲染场景,然后把深度值的结果存到纹理中,通过这种方式,我们就能得到光线打到的“最近点”。我们管储存在纹理中的所有这些深度值,叫做深度贴图(depth map)或阴影贴图。

在这里插入图片描述

  • 左边的图展示了场景中的一个立方体,在平行光的照射下的阴影情况。
    通过储存到深度贴图中的深度值,我们就能借此来表示最近点,用以决定摄像机看到的片段是否在阴影中。
    我们使用一个来自光源的视图和投影矩阵来渲染场景,这样可以创建一个深度贴图。
    这个投影和视图矩阵结合在一起成为一个变换(图中为 T T T变换),它可以将任何三维位置转变到光源的观察坐标下。
    同时定向光并没有位置,因为它被规定为无穷远。所以为了实现阴影贴图,我们得在光的方向的某一点上渲染场景。
  • 右图我们可以看到平行光和观察者的位置,我们渲染 P P P处的片段,判断它在不在阴影中。
    我们先用 T T T变换,把 P P P变换到光源的坐标空间下,在这里它的Z坐标就对应了它的深度,图中为0.9。
    我们同时还有 P P P的坐标,可以索引之前渲染的深度贴图,发现最近的可见深度是 C C C,深度为0.4。
    所以比较之后我们就知道了 P P P被挡住了,应该在阴影中

阴影映射由两个步骤组成:
首先,我们渲染深度贴图
然后我们像往常一样渲染场景,使用生成的深度贴图来计算片段是否在阴影之中

1.2 深度贴图

我们新建一个cpp文件用来做练习深度贴图的绘制的例子
思路很简单,创建一个2D深度纹理作为帧缓冲的深度缓冲
完成之后进行两个步骤的完整的渲染阶段
渲染思路如下(main函数的思路):

  • main函数:窗口初始化(新增FramebufferSize的回调函数)
    →两组shader设置(从场景到深度缓冲,从深度缓冲到显示)
    →地面的绘制(VAO,VBO,材质)
    →深度贴图的创建,绑定到framebuffer,设置shader中深度贴图的位置
    →假定光源位置
    →渲染循环:
  • 渲染循环:检测相关输入输出
    →clear & clear buffer bit
    →准备光源的VP矩阵(平行光采用正射投影)设置到shader中准备进行绘制时,把坐标变换到光源位置看到的场景
    →在深度缓冲中绘制场景(即只做坐标变换与深度缓冲记录)
    →打开正常的窗口,绑定深度缓冲,根据深度值进行绘制

我们会用到两组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屏幕四边形上(和我们在帧缓冲一节做的后处理过程类似),就能在屏幕上显示出来,我们会获得这样的东西:
在这里插入图片描述

1.2.1 main.cpp

以下是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;
}

1.2 渲染阴影

正确地生成深度贴图以后我们就可以开始生成阴影了。
在之前的渲染思路上拓展
渲染思路如下(main函数的思路):

  • main函数:窗口初始化(新增FramebufferSize的回调函数)
    →三组shader设置(从场景到深度缓冲,从深度缓冲到显示,以及加上阴影的场景绘制)
    (第二组shader不一定使用,深度贴图的显示用来看有没有bug)
    →地面的绘制(VAO,VBO,材质)
    →深度贴图的创建,绑定到framebuffer,设置深度贴图shader中深度贴图的位置,设置正常的阴影绘制中的shadowMap与漫反射材质(全程都只用了一个材质)
    →假定光源位置
    →渲染循环:
  • 渲染循环:检测相关输入输出
    →clear & clear buffer bit
    →准备光源的VP矩阵(平行光采用正射投影)设置到shader中,准备进行绘制时,把坐标变换到光源位置看到的场景
    →在深度缓冲中绘制场景(即只做坐标变换与深度缓冲记录)
    →打开正常的窗口,使用带上阴影的场景绘制
    →预备用:绑定深度缓冲,根据深度值进行绘制

我们对于带上阴影的场景绘制新增的是这组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传到片段着色器里,我们必须自己做透视除法

  • 当使用正交投影矩阵,顶点w元素仍保持不变,所以这一步实际上毫无意义。可是,当使用透视投影的时候就是必须的了,所以为了保证在两种投影矩阵下都有效就得留着这行。
  • 因为来自深度贴图的深度在0到1的范围,我们也打算使用projCoords从深度贴图中去采样,所以我们将NDC坐标变换为0到1的范围。

我们会得到这样的结果
在这里插入图片描述

1.2.1 main.cpp

我们把渲染思路实现的话,就是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;
}

1.3 改进阴影贴图

我们试图让阴影映射工作,但是也看到了,阴影映射还是有点不真实,我们修复它才能获得更好的效果,这是下面的部分所关注的焦点。

1.3.1 阴影失真

我们看上面的场景,放大看会发现明显的线条样式:
在这里插入图片描述
这种阴影贴图的不真实感叫做阴影失真(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,它们是基于表面法线和光照方向的。
这样像地板这样的表面几乎与光源垂直,得到的偏移就很小,而比如立方体的侧面这种表面得到的偏移就更大。
下图展示了同一个场景,但使用了阴影偏移,效果的确更好:
在这里插入图片描述
选用正确的偏移数值,在不同的场景中都需要一些像这样的轻微调校,但大多情况下,实际上就是增加偏移量直到所有失真都被移除的问题。

1.3.2 悬浮

使用阴影偏移的一个缺点是我们对物体的实际深度应用了平移。

偏移有可能足够大,以至于可以看出阴影相对实际物体位置的偏移,可以从下图看到这个现象
在这里插入图片描述
这个阴影失真叫做悬浮(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.3.3 采样过多

光的视锥不可见的区域一律被认为是处于阴影中,不管它真的处于阴影之中。
出现这个状况是因为超出光的视锥的投影坐标比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坐标才有阴影,所以任何超出范围的都将会没有阴影。
由于在游戏中通常这只发生在远处,就会比我们之前的那个明显的黑色区域效果更真实。

1.3.4 PCF

阴影现在已经附着到场景中了,不过这仍不是我们想要的。如果放大看阴影,阴影映射对分辨率的依赖很快变得很明显。
在这里插入图片描述
因为深度贴图有一个固定的分辨率,多个片段对应于一个纹理像素。
结果就是多个片段会从深度贴图的同一个深度值进行采样,这几个片段便得到的是同一个阴影,这就会产生锯齿边。

可以通过增加深度贴图的分辨率的方式来降低锯齿块,也可以尝试尽可能的让光的视锥接近场景。

另一个(并不完整的)解决方案叫做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还有更多的内容,以及很多技术要点需要考虑以提升柔和阴影的效果,但处于本章内容长度考虑,我们将留在以后讨论。

1.3.5 正交与透视

在渲染深度贴图的时候,正交(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
}

这个深度值与我们见到的用正交投影的很相似。需要注意的是,这个只适用于调试;正交或投影矩阵的深度检查仍然保持原样,因为相关的深度并没有改变。

猜你喜欢

转载自blog.csdn.net/weixin_43803133/article/details/108626041