本章节源码 点击此处 文档持续更新
一 为什么采用点透视投影
透视投影:
- 由于点光源是一个点向四周发散的光线,所以这将导致点光源会以不同的角度到达场景中的不同表面,造成近大远小的效果,所以要采用透视投影矩阵来处理点光源的阴影,透视投影能够正确反映这种随着距离增加而大小和亮度逐渐变化的现象。
- 使用透视投影来渲染点光源的阴影,可以准确地模拟出光源的真实效果,即光源距离物体越远,投射在地面或物体上的阴影边缘越模糊,产生自然的衰减和透视缩放效果。
二 阴影计算思路
- 对于点光源的阴影计算,我们计算方式还是和平行光产生的阴影计算方式是相同的,
- 从光的透视图生成一个深度贴图,基于当前fragment位置来对深度贴图采样,然后用储存的深度值和每个fragment进行对比,看看它是否在阴影中。
- 但是对于点光源要值考虑的一点是,它是从任何方向都会发散的,那么就需要对整个场景中点光源处的六个方向都进行深度贴图的采样。
三 深度贴图生成
对于点光源的深度贴图,我们需要在上下左右前后六个方向都生成深度贴图
3.1 渲染场景生成
- 我们可以在CPU端,渲染深度贴图时,进行6个不同方向的渲染,具体思路就是提供分别由视点方向看下前后左右上下6个方向的观察矩阵,并进行6次深度贴图的渲染,这样就会在深度缓冲中生成了不同方向的深度贴图。
- 但是这种会导致在CPU端进行了多次的渲染调用,这会很消耗CPU的性能。所以
for(int i = 0; i < 6; i++)
{GLuint face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, face, depthCubemap, 0);/* 伪代码: 也就是视角矩阵 */BindViewMatrix(lightViewMatrices[i]);RenderScene();
}
3.2 几何着色器生成
我们可以利用几何着色器的功能,在一次渲染过程中就完成多个方向上的深度立方体贴图。我们生成一个深度缓冲,后续利用这个深度缓冲来进行深度值的对比。
- 首先我们需要准备立方体深度深度缓冲
- 正常情况下,我们把立方体贴图纹理的一个面附加到帧缓冲对象上,渲染场景6次,每次将帧缓冲的深度缓冲目标改成不同立方体贴图面。由于我们将使用一个几何着色器,它允许我们把所有面在一个过程渲染,我们可以使用glFramebufferTexture直接把立方体贴图附加成帧缓冲的深度附件
// 创建一个帧缓冲对象glGenFramebuffers(1,&depthCubeMapFBO);// 创建一个立方体贴图glGenTextures(1,&depthCubeMap);// 绑定纹理 并设置每个方向上纹理格式glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubeMap);for (unsigned int i = 0; i < 6; ++i)glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);// 设置纹理的过滤方式。glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);// 将创建的立方体贴图提供给帧缓冲作为深度附件glBindFramebuffer(GL_FRAMEBUFFER,depthCubeMapFBO);// 将纹理附件depthCubeMap作为深度缓冲(GL_DEPTH_ATTACHMENT)绑定到帧缓冲对象上glFramebufferTexture(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,depthCubeMap,0);// 别禁用了当前帧缓冲对象的颜色绘制和读取功能,使得后续的渲染和像素// 读取操作不涉及任何颜色数据。这在进行深度测试、模板测试、只关注非颜色附件的渲染任务等场景中是合理的// 显示告诉OpenGL不适用颜色进行渲染glDrawBuffer(GL_NONE);glReadBuffer(GL_NONE);if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;glBindFramebuffer(GL_FRAMEBUFFER,defaultFramebufferObject());
- 其次我们在PaintGL中生成立方体深度缓冲
- perspective的视野参数:设置为90度。90度我们才能保证视野足够大到可以合适地填满立方体贴图的一个面,立方体贴图的所有面都能与其他面在边缘对齐。
QMatrix4x4 shadowProj;QMatrix4x4 shadowView;float near_plane = 1.0f, far_plane = 25.0f;// 定义一个透视投影 这个透视投影矩阵是深度缓冲中的裁剪空间 并且这个并不会在每个方向上改变,改变的只是观察矩阵shadowProj.perspective(90.0f, (float)SHADOW_WIDTH / (float)SHADOW_HEIGHT, near_plane, far_plane);// 准备6个不同方向的观察矩阵std::vector<QMatrix4x4> shadowTransforms;shadowView.lookAt(lightPos, lightPos + QVector3D( 1.0, 0.0, 0.0), QVector3D(0.0,-1.0, 0.0));shadowTransforms.push_back(shadowProj * shadowView); shadowView.setToIdentity();shadowView.lookAt(lightPos, lightPos + QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0,-1.0, 0.0));shadowTransforms.push_back(shadowProj * shadowView); shadowView.setToIdentity();shadowView.lookAt(lightPos, lightPos + QVector3D( 0.0, 1.0, 0.0), QVector3D(0.0, 0.0, 1.0));shadowTransforms.push_back(shadowProj * shadowView); shadowView.setToIdentity();shadowView.lookAt(lightPos, lightPos + QVector3D( 0.0,-1.0, 0.0), QVector3D(0.0, 0.0,-1.0));shadowTransforms.push_back(shadowProj * shadowView); shadowView.setToIdentity();shadowView.lookAt(lightPos, lightPos + QVector3D( 0.0, 0.0, 1.0), QVector3D(0.0,-1.0, 0.0));shadowTransforms.push_back(shadowProj * shadowView); shadowView.setToIdentity();shadowView.lookAt(lightPos, lightPos + QVector3D( 0.0, 0.0,-1.0), QVector3D(0.0,-1.0, 0.0));shadowTransforms.push_back(shadowProj * shadowView);// 设置绘制的视窗大小,因为这个是绘制在缓冲中的,并不是真正的绘制在屏幕上面的,所以我们最好保持它和深度纹理贴图采样的分辨率一样是最好的glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);// 将当前帧缓冲区绑定到depthCubeMapFBO上glBindFramebuffer(GL_FRAMEBUFFER, depthCubeMapFBO);// 清除当前缓冲区的信息glClear(GL_DEPTH_BUFFER_BIT);// 绑定深度缓冲的shaderdepthProgramObject.bind();// 将6个观察矩阵传递给GPU端for (unsigned int i = 0; i < 6; ++i){std::string str="shadowMatrices[" + std::to_string(i) + "]";depthProgramObject.setUniformValue(str.c_str(), shadowTransforms[i]);}// 将远平面传递给GPU端depthProgramObject.setUniformValue("far_plane", far_plane);// 将点光源的位置传递给GPU端depthProgramObject.setUniformValue("lightPos", lightPos);// 渲染场景renderScene(&depthProgramObject);// 将帧缓冲区绑定到默认的缓冲区对象上,这样才能让后续的绘制绘制到屏幕上面glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());depthProgramObject.release();// 绑定shadershaderProgramObject.bind();// 生成透视投影,这个透视投影矩阵是真正的视角的裁剪空间projection.perspective(m_camera.Zoom,(float)width()/(float)height(),_near,_far);// 获得摄像机的观察视角矩阵view = m_camera.GetViewMatrix();// 设置视窗大小,这里是真实的绘制到屏幕上面,所以要保持和openGL的绘制窗口相同。glViewport(0, 0, width(), height());// 传递数据到GPU端shaderProgramObject.setUniformValue("far_plane", far_plane);shaderProgramObject.setUniformValue("lightPos", lightPos);shaderProgramObject.setUniformValue("shadows", true);shaderProgramObject.setUniformValue("projection", projection);shaderProgramObject.setUniformValue("view", view);shaderProgramObject.setUniformValue("viewPos",m_camera.Position);shaderProgramObject.setUniformValue("depthCubeMap",1);// 绑定纹理单元glActiveTexture(GL_TEXTURE1);// 将纹理绑定到纹理单元上glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubeMap);renderScene(&shaderProgramObject);shaderProgramObject.release();
- (深度缓冲的)顶点着色器:我们只需要把世界坐标传递过去即可。
#version 330 core
layout (location = 0) in vec3 aPos;uniform mat4 model;void main()
{gl_Position = model * vec4(aPos, 1.0);
}