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

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

一 为什么采用点透视投影

透视投影:

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

二 阴影计算思路

  • 对于点光源的阴影计算,我们计算方式还是和平行光产生的阴影计算方式是相同的,
  • 从光的透视图生成一个深度贴图,基于当前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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/707417.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

visual studio snippet常用注释片段

Visual Studio 2022 添加自定义代码片段_vs2022 代码片段-CSDN博客 dclass.snippet: <?xml version"1.0" encoding"utf-8"?> <CodeSnippets xmlns"http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> …

刚学完PMP好找工作吗?

如果你有项目管理相关工作经验&#xff0c;持有PMP证书会增加找工作的机会。许多企业在招聘项目经理和项目管理相关岗位时&#xff0c;通常要求具备PMP证书&#xff0c;或者PMP持有者优先考虑。 PMP证书是项目管理领域的专业认证&#xff0c;在多个行业都有广泛应用。如果你已…

24上软考高项最后一周冲刺攻略+答题技巧,赶紧收藏!!

距离软考考试的时间越来越近了&#xff0c;趁着这一周赶紧准备起来。 今天给大家整理了——信息系统项目管理师冲刺资料 &#xff0c;都是核心重点&#xff0c;有PDF&#xff0c;打印出来直接背。 一、考试科目&#xff1a; 科目一&#xff1a;综合知识 【总分&#xff1a;75分…

不能输的战争:谷歌的Veo能否成功阻击OpenAI的Sora?|TodayAI

在2024年谷歌I/O大会上&#xff0c;谷歌宣布推出Veo&#xff0c;这是一款能够根据文本、图像或视频提示生成高清视频的AI视频合成模型。Veo具有生成1080p分辨率、时长超过一分钟的视频的能力&#xff0c;并能根据书面指令编辑视频&#xff0c;但目前尚未向公众广泛发布。 据介…

QT项目实战:贪吃蛇小游戏

目录 内容介绍 一.添加头文件 二.初始化蛇与奖品 三.设置背景图&#xff0c;蛇与奖品 1.设置背景图 2.画蛇 3.画奖品 四.小蛇移动 1.控制方向 2.向上移动 3.向下移动 4.向右移动 5.向左移动 五.随机生成奖品位置 六.是否吃到奖品 七.删除操作 八.游戏结束 九…

Android:基于Gradle 7.0+(8.2、8.6)发布aar到maven仓库,使用maven-publish插件

maven插件与maven-publish插件的区别 maven插件适用于gradle1.0-6.2版本&#xff0c;6.2版本后该插件就被废弃了&#xff0c;推荐使用maven-publish插件。 maven-publis插件是在gradle 1.3 版本后开始支持的&#xff0c;使配置更加简洁。 maven-publis插件的使用 砍柴不误磨…

数据结构:二叉树的序列化和反序列化(序列和二叉树结构一一对应)

文章目录 一、基础知识1.1 序列化和反序列基础知识1.2 string与int转化STL函数 二、详解说明2.1 基于前序遍历的序列化和反序列化2.2 基于后序遍历的序列化和反序列化2.3 基于层序遍历的序列化和反序列化 三、例题——652. 寻找重复的子树 三个相同题目&#xff1a; 297. 二叉树…

Pencils Protocol Season 2 收官在即,Season 3 携系列重磅权益来袭

此前Scroll生态LaunchPad &聚合收益平台Pencils Protocol&#xff08;原Penpad&#xff09;&#xff0c;推出了首个资产即其生态代币PDD的Launch&#xff0c;Season 2活动主要是用户通过质押ETH代币、组件战队等方式&#xff0c;来获得Point奖励&#xff0c;并以该Point为依…

C++那些事之Mixin惯用法

C那些事之Mixin惯用法 大家好&#xff0c;我是光城&#xff0c;今天给大家分享C那些事里面的一个惯用法&#xff1a;mixin 混合&#xff08;Mixins&#xff09;是Lisp中的一个概念。混合是类的一部分&#xff0c;意味着它旨在与其他类或混合组合在一起。常规独立类&#xff08;…

C++学习~~string类

1.STL简单介绍 &#xff08;1&#xff09;标准模版库&#xff0c;是C里面的标准库的一部分&#xff0c;C标准库里面还有其他的东西&#xff0c;但是我们不经常使用&#xff0c;我们经常使用的还是STL这个标准库部分。 &#xff08;2&#xff09;六大件&#xff1a;仿函数&…

内网安全工具之ADExplorer的使用

ADExplorer是域内一款信息查询工具&#xff0c;它是独立的可执行文件&#xff0c;无需安装。它能够列出域组织架构、用户账号、计算机账号登&#xff0c;可以帮助寻找特权用户和数据库服务器等敏感目标。 下载地址&#xff1a;http://live.sysinternals.com/ 连接 下载了ADE…

SSM宠物管理系统-计算机毕业设计源码56932

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对宠物管理系统等问题&#xff0c;对宠物管理…