[OpenGL] 点光源阴影(万向阴影贴图)

admin2024-05-15  1

本章节源码 点击此处 文档持续更新

一 为什么采用点透视投影

透视投影:

  • 由于点光源是一个点向四周发散的光线,所以这将导致点光源会以不同的角度到达场景中的不同表面,造成近大远小的效果,所以要采用透视投影矩阵来处理点光源的阴影,透视投影能够正确反映这种随着距离增加而大小和亮度逐渐变化的现象。
  • 使用透视投影来渲染点光源的阴影,可以准确地模拟出光源的真实效果,即光源距离物体越远,投射在地面或物体上的阴影边缘越模糊,产生自然的衰减和透视缩放效果。

二 阴影计算思路

  • 对于点光源的阴影计算,我们计算方式还是和平行光产生的阴影计算方式是相同的,
  • 从光的透视图生成一个深度贴图,基于当前fragment位置来对深度贴图采样,然后用储存的深度值和每个fragment进行对比,看看它是否在阴影中。
  • 但是对于点光源要值考虑的一点是,它是从任何方向都会发散的,那么就需要对整个场景中点光源处的六个方向都进行深度贴图的采样。

[OpenGL] 点光源阴影(万向阴影贴图),第1张

三 深度贴图生成

对于点光源的深度贴图,我们需要在上下左右前后六个方向都生成深度贴图

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);
    // 绑定深度缓冲的shader
    depthProgramObject.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();

    // 绑定shader
    shaderProgramObject.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);
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!