阴影映射
阴影看了好多遍了,着实是个难以下手的东西,这次好好整理下。
Shadow Mapping 官方资料笔记
视频游戏中较多使用的一种技术是阴影贴图(shadow mapping),效果不错,而且相对容易实现。阴影贴图并不难以理解,性能也不会太低,而且非常容易扩展成更高级的算法(比如 Omnidirectional Shadow Maps和 Cascaded Shadow Maps)。
首先看看效果图
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了。假设有一个地板,在光源和它之间有一个大盒子。由于光源处向光线方向看去,可以看到这个盒子,但看不到地板的一部分,这部分就应该在阴影中了
来看看第一步处理
准备好一个帧缓冲,里面放我们的深度缓冲,用来后面储存我们的深度缓冲
// configure depth map FBO
// -----------------------
const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
unsigned int depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);
// create depth texture
unsigned int depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
//GL_DEPTH_COMPONENT -- 读到的是“深度”值,相当于RGBA里的A, (GL_ALPHA).结果可能就是1。
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);
// attach depth texture as FBO's depth buffer
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
//颜色方面都设置为NONE,不写入任何颜色,但是还是要把颜色模板传入
//我们需要的只是在从光的透视图下渲染场景的时候深度信息,所以颜色缓冲没有用。
//然而,不包含颜色缓冲的帧缓冲对象是不完整的。
//所以我们需要显式告诉OpenGL我们不适用任何颜色数据进行渲染。
//我们通过将调用glDrawBuffer和glReadBuffer把读和绘制缓冲设置为GL_NONE来做这件事。
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
还有自然就是要准备好投影矩阵
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;
// render scene from light's point of view
//变换矩阵
simpleDepthShader.use();
simpleDepthShader.setMat4("lightSpaceMatrix", 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);
// reset viewport
//重置视窗
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
此处的渲染场景不是真正的显示场景,我们看看shader就知道了
首先是顶点着色器
#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);
}
只是进行了顶点的转换。从我们的视角(摄像机)转移到了光线空间。
再看看fs
#version 330 core
void main()
{
// gl_FragDepth = gl_FragCoord.z;
}
不会有任何片段的输出
gl_FragDepth输入变量gl_FragCoord能让我们读取当前片段的窗口空间坐标,并获取它的深度值,但是它是一个只读(Read-only)变量。我们不能修改片段的窗口空间坐标,但实际上修改片段的深度值还是可能的。GLSL提供给我们一个叫做gl_FragDepth的输出变量,我们可以使用它来在着色器内设置片段的深度值。
如果着色器没有写入值到gl_FragDepth,它会自动取用gl_FragCoord.z的值。
注意这一句话
“第一步我们需要生成一张深度贴图(Depth Map)。深度贴图是从光的透视图里渲染的深度纹理,用它计算阴影。因为我们需要将场景的渲染结果储存到一个纹理中,我们将再次需要帧缓冲。”
这就涉及到帧缓冲的概念了
说到底,我们需要理解帧缓冲是什么,才能知道他为什么能这么做
帧缓冲可以理解为着色器渲染之后将要显示在窗口上的所有颜色信息,深度信息和模版信息的数据集合,这些数据都保存在内存中,最后经由显示器显示在窗口中。窗口都有一个默认的帧缓冲,来存放最终要显示的所有信息。
Shadow Mapping个人总结
那么知道概念我们自己来总结下:
-
自己创建一个帧缓冲,我们需要帧缓冲来储存我们深度纹理,但同时,帧缓冲的创建三大必要条件缺一不可,所以我们使用none来填充颜色。帧缓冲里唯一可用的就是我们的深度缓冲。来源于此函数:g