LearnOpenGL——延迟渲染学习笔记
延迟渲染学习笔记
- 一、基本概念
- 二、G-Buffer
- MRT
- 三、Lighting Pass
- 四、结合延迟渲染和前向渲染
- 五、更多光源
我们之前使用的一直是 前向渲染(正向渲染 Forward Rendering),指的是在场景中根据所有光源照亮一个物体,之后再渲染下一个物体。对程序性能影响很大,因为对于每一个需要渲染的物体,程序都要对每一个光源每一个需要渲染的片段进行迭代。 而且还有一部分被渲染的片段会被覆盖遮挡,造成浪费
一、基本概念
延迟渲染(Deferred Rendering) ,包含两个Pass:
- 第一个是几何处理Pass:先渲染场景一次,之后获取对象的各种几何信息(比如顶点位置、颜色、法线、高光信息等),并存储在G-Buffer中

- 然后第二个Pass是用于计算光照信息——Lighting Pass:我们将会渲染一个满屏的方片并根据G-Buffer中的几何数据信息来为每个片元进行光照计算(在G-Buffer中每个像素进行迭代)。

与前向渲染不同,延迟渲染并不是将每个对象进行顶点着色器到片元着色器的计算,而是将片元着色器移动到后期处理。这样保证了对于在光照处理阶段中处理的每一个像素都只处理一次,所以我们能够省下很多无用的渲染调用。
缺陷:
- 显存消耗会较大:G-Buffer需要我们存储较大的几何数据
- 不能使用MSAA:因为我们通过只有几何数据的G-Buffer来进行着色
二、G-Buffer
G-Buffer是一个用来存储光照计算所需数据的纹理的总称。
- 3D位置向量,来计算片段位置变量 lightDir,viewDir
- 3D法向量,normal
- RGB漫反射颜色向量,Albedo
- 镜面强度(高光反射强度)
- 光源的位置向量、颜色向量
- 观察者的位置向量
在前向渲染中,我们每个物体的光照计算都是根据特定实时数据的,所以如何给Lighting Pass传递正确的光照数据是很重要的。延迟渲染G-Buffer已经把几何数据渲染到一张2D纹理中,纹理允许我们存储各种各样的数据类型,所以纹理上每个片元都有正确的几何数据供光照计算
while(...) // render loop
{// 1. geometry pass: render all geometric/color data to g-buffer glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);glClearColor(0.0, 0.0, 0.0, 1.0); // keep it black so it doesn't leak into g-bufferglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);gBufferShader.use();for(Object obj : Objects){ConfigureShaderTransformsAndUniforms();obj.Draw();} // 2. lighting pass: use g-buffer to calculate the scene's lightingglBindFramebuffer(GL_FRAMEBUFFER, 0);lightingPassShader.use();BindAllGBufferTextures();SetLightingUniforms();RenderQuad();
}
MRT
在几何处理阶段,我们需要渲染场景中所有物体,并且存储这些几何数据在G-Buffer中。我们可以使用 MRT 来在一个Pass中渲染多个颜色缓冲。
我们需要初始化一个帧缓冲gBuffer(这个gBuffer会有多个颜色缓冲附件,以及一个深度渲染缓冲对象)。对于位置和法向量的纹理,我们希望使用高精度的纹理(每分量16或32位的浮点数),而对于反照率和镜面值,使用默认的纹理(每分量8位浮点数)就够了。
GLuint gBuffer;
glGenFramebuffer(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
GLuint gPosition, gNormal, gColorSpec;
//位置颜色缓冲
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCRHEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);//法线颜色缓冲
glGenTextures(1, &gNormal);
glBindTexture(GL_TEXTURE_2D, gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);//颜色+镜面颜色缓冲
glGenTextures(1, &gAlbedoSpec);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);//告诉OpenGL我们将用哪个颜色附件来渲染
GLuint attatchments[3] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
glDrawBuffers(3, attachments);// 之后同样添加渲染缓冲对象(Render Buffer Object)为深度缓冲(Depth Buffer),并检查完整性
[...]
接下来就是将数据渲染到G-Buffer中,我们将使用一下片元着色器
#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;uniform sampler2D texture_diffuse1;
uniform sampler2D texture_specular1;void main()
{ // 存储第一个G缓冲纹理中的片段位置向量gPosition = FragPos;// 同样存储对每个逐片段法线到G缓冲中gNormal = normalize(Normal);// 和漫反射对每个逐片段颜色gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb;// 存储镜面强度到gAlbedoSpec的alpha分量gAlbedoSpec.a = texture(texture_specular1, TexCoords).r;
}
因为有光照计算,所以要将所有坐标转换到一个坐标系下,此处我们是将所有坐标转换到世界空间下
三、Lighting Pass
我们通过对G-Buffer进行逐像素的遍历,将其数据作为光照计算的输入,来计算场景最终的光照颜色。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderLightingPass();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);//发送光照相关的uniform
SendAllLightUniformsToShader(shaderLightingPass);
glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "viewPos"), 1, &camera.Position[0]);
RenderQuad();
在片元着色器中,我们将会在G-Buffer中直接采样
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;struct Light {vec3 Position;vec3 Color;
};
const int NR_LIGHTS = 32;
uniform Light lights[NR_LIGHTS];
uniform vec3 viewPos;void main()
{ // 从G缓冲中获取数据vec3 FragPos = texture(gPosition, TexCoords).rgb;vec3 Normal = texture(gNormal, TexCoords).rgb;vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;float Specular = texture(gAlbedoSpec, TexCoords).a;// 然后和往常一样地计算光照vec3 lighting = Albedo * 0.1; // 硬编码环境光照分量vec3 viewDir = normalize(viewPos - FragPos);for(int i = 0; i < NR_LIGHTS; ++i){// 漫反射vec3 lightDir = normalize(lights[i].Position - FragPos);vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color;lighting += diffuse;}FragColor = vec4(lighting, 1.0);
}
四、结合延迟渲染和前向渲染
在延迟渲染中,光源通常被视为无形的点或方向,而不是具有材质和颜色的物体。如果我们想将光源渲染为一个带有光照颜色的立方体,就需要额外的几何处理,而这超出了延迟渲染的范畴,就需要结合前向渲染(透明物体、镜面反射、光源模型等)。
前向渲染的部分会在延迟渲染操作之后进行。
// 延迟渲染光照渲染阶段
[...]
RenderQuad();// 现在像正常情况一样正向渲染所有光立方体
shaderLightBox.Use();
glUniformMatrix4fv(locProjection, 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(locView, 1, GL_FALSE, glm::value_ptr(view));
for (GLuint i = 0; i < lightPositions.size(); i++)
{model = glm::mat4();model = glm::translate(model, lightPositions[i]);model = glm::scale(model, glm::vec3(0.25f));glUniformMatrix4fv(locModel, 1, GL_FALSE, glm::value_ptr(model));glUniform3fv(locLightcolor, 1, &lightColors[i][0]);RenderCube();
}

不过现在的深度结果并不正确,因为除了光源立方体的深度信息都在延迟渲染过程中,所以我们需要将延迟渲染中的深度信息提取出来,然后再渲染光立方体。
我们可以使用glBlitFramebuffer复制一个帧缓冲的内容到另一个帧缓冲中。我们需要指定一个帧缓冲为读帧缓冲(Read Framebuffer),并且类似地指定一个帧缓冲为写帧缓冲(Write Framebuffer)
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 写入到默认帧缓冲
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 现在像之前一样渲染光立方体
[...]
在这里我们复制整个读帧缓冲的深度缓冲信息到默认帧缓冲的深度缓冲,对于颜色缓冲和模板缓冲我们也可以这样处理。

五、更多光源
延迟渲染本身并不能支持非常大量的光源,但是我们可以为其引入一个优化:光体积(Light Volumes)。因为对于场景中的物体,有的光是影响非常非常小的,所以我们就可以计算光的影响半径。
我们可以通过光源的衰减值,来计算光的影响范围,我们只需要对在光影响范围内的片段进行光照计算就可以了。
相关文章:
LearnOpenGL——延迟渲染学习笔记
延迟渲染学习笔记 一、基本概念二、G-BufferMRT 三、Lighting Pass四、结合延迟渲染和前向渲染五、更多光源 我们之前使用的一直是 前向渲染(正向渲染 Forward Rendering),指的是在场景中根据所有光源照亮一个物体,之后再渲染下一…...
惠海H4312 dcdc同步整流降压恒压IC 30V 40V转3.3V/5V/12V小体积大电流单片机供电
1.产品描述 H4312是一种内置30V耐压MOS,并且能够实现精确恒压以及恒流的同步降压型 DC-DC 转换器: 支持 3.1A 持续输出电流输出电压可调,最大可支持 100%占空比;通过调节FB 端口的分压电阻,可以输出2.5V到 24V的稳定电压。 H4312 采用高端…...
[Linux]如何在虚拟机安装Ubuntu?(小白向)
一、我们为什么要在虚拟机中安装Ubuntu? 在虚拟机中安装系统主要是为了让一个系统与我们原本的系统隔离,不管是想运行一些不安全的软件,或者是想运行一些独特的操作系统,我们都可以选择使用虚拟机来安装和隔离这些操作系统。如果你是一位Lin…...
keepalived详解
概念 keepalived 是一款基于 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)协议来实现高可用(High Availability, HA)的轻量级软件。它主要用于防止单点故障,特别是在 Linux 环境下ÿ…...
工业设备中弧形导轨的检测标准是什么?
弧形导轨在工业自动化中扮演着重要的角色,尤其是在需要曲线运动或圆弧插补的场合。这种运动形式在工业自动化中虽然不如直线运动普遍,但在某些特定应用中却是不可或缺的。弧形导轨的质量直接影响加工效率与加工质量,因此,弧形…...
Redis 技术详解
一、Redis 基础 (一)为什么使用 Redis 速度快,因为数据存在内存中,类似于 HashMap,查找和操作的时间复杂度都是 O(1)。支持丰富数据类型,支持 string、list、set、Zset、hash 等。支持事务,操…...
Kubernetes Pod入门
在 Kubernetes 中,一个重要的概念就是 Pod(豆英),Kubernetes 并不是直接管理容器的,他的最小管理单元叫做 Pod。 一、什么是 Pod。 Pod 是一个或多个容器的组合。这些容器共享存储、网络和命名空间,以及运行规范。在 Pod中&…...
opencv批量修改图片大小
文章已删除,访问可以 在点击这里查找. 在点击这里查找. 在点击这里查找. 在点击这里查找. 在点击这里查找. 在点击这里查找. 在点击这里查找. ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~...
【RTT-Studio】详细使用教程十二:UART的分析和使用
文章目录 一、简介1.串口发送模式2.串口接收模式 二、串口配置三、串口发送四、串口接收 一、简介 本文主要阐述STM32串口的几种工作中使用的工作模式和编程思路。串口通常情况下使用的是:1个起始位,8个数据位,无奇偶校验,1位停止…...
【AI绘画】Midjourney前置指令/settings设置详解
文章目录 💯Midjourney前置指令/settings设置详解💯Use the default model(AI绘画所使用的大模型)Midjourney Model(Midjourney 模型)Niji Model(Niji模型) 💯Midjourney…...
【NI国产替代】PXIe‑4330国产替代24位,8通道PXI应变/桥输入模块
25 kS/s,24位,8通道PXI应变/桥输入模块 PXIe‑4330是一款同步输入模块,为基于桥接的传感器提供集成数据采集和信号调理。 PXIe‑4330具有更高的准确性、高数据吞吐量和同步特性,使其成为高密度测量系统的理想选择。\n\n为了消除噪…...
哪里可以免费上传招生简章
随着招生季的临近,各高校和培训机构纷纷摩拳擦掌,准备迎接新一代学子们的到来。在这个信息化的时代,如何让招生简章发挥最大的效用,成为吸引优质生源的关键。 那么如何制作招生简章? 1. 注册账号:访问FLBO…...
Midjourney中文版教程:参数详解
1.长宽比 可以设置图片的纵横比。按照需求可以选择不同的尺寸,也可以自定义。 注意:--ar必须使用整数。使用139:100代替1.39:1。 长宽比会影响生成图像的形状和构图。 在放大时,某些长宽比可能会稍微改变。 较旧的…...
误闯机器学习(第一关-概念和流程)
以下内容,皆为原创,实属不易,请各位帅锅,镁铝点点赞赞和关注吧! 好戏开场了。 一.什么是机器学习 机器学习就是从数据中自动分析获取模型(总结出的数据),并训练模型,去预…...
Tensorflow 2.16.0+在PyCharm中找不到keras的报错解决
在PyCharm(2024.2版本)中,直接使用from tensorflow import keras会提示“Cannot find reference ‘keras’ in ‘init.py’ ”,找不到keras,如下图所示。 查阅相关资料,可以发现在tf2.16之后,默认的keras后端升级为了…...
【Python】高效的Web自动化测试利器—Python+Playwright快速上手自动化实战指南(限时开放)
文章目录 前言一.playwright是什么二.python引入playwright1.安装2.playwright命令行参数3.playwright codegen自动生成代码4.Chrome和Chromium有什么关系?三.基本概念1. 无头浏览器(Headless Browser)2.同步和异步模式操作playwright2.1.同步(Sync)模式同步方式代码模板2…...
CentOS上安装和配置Docker与Docker Compose的详细指南
引言 大家好,我是小阳,在这篇文章中,我将带大家一步步完成在CentOS系统上安装和配置Docker与Docker Compose的过程。通过这篇详细的指南,你将能够轻松配置Docker环境,并在日常开发和部署中享受其带来的便利。 原文阅…...
Vim多文件操作
Vim多文件编辑的实际意义在于它极大地提高了开发者在处理多个相关文件时的效率和便利性。在软件开发、文本编辑、代码审查、配置管理等场景中,经常需要同时打开和操作多个文件。Vim的多文件编辑功能使得这些任务变得更加直观和高效。 提高编码效率:在开发…...
【ARM+Codesys 客户案例 】 基于RK3568/A40i/STM32+CODESYS在智能制造中的应用案例:全自动切片机器人
蔬菜是人们日常生活必不可缺的食品,并且食用方法多种多样。自步入小康社会以来,人们的生活节奏越来越快,很多传统服务已不能满足人们的物质需求和生活节奏。日常生活中通过手工快速切菜严重地威胁着人身安全,切菜时间过长或切菜不…...
NSI程序打包脚本文件编写教程
引言 NSIS (Nullsoft Scriptable Install System) 是一个专业开源的制作 windows 安装程序的工具。我们通过HM NSIEDIT编写好脚本、编译即可生成exe安装包。安装过程中可以配置其安装包图标、名称、出版人、网站等。此外,还可以设置程序开机自启动、管理员权限运行…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...
旋量理论:刚体运动的几何描述与机器人应用
旋量理论为描述刚体在三维空间中的运动提供了强大而优雅的数学框架。与传统的欧拉角或方向余弦矩阵相比,旋量理论通过螺旋运动的概念统一了旋转和平移,在机器人学、计算机图形学和多体动力学领域具有显著优势。这种描述不仅几何直观,而且计算…...
RMQ 算法详解(区间最值问题)
RMQ 算法详解(区间最值问题) 问题介绍解决方法暴力法ST表法基本思想算法步骤C实现 问题介绍 RMQ问题是OI中经常遇到的问题,主要是一下形式: 给你一堆数,不断的对里面的数进行操作,例如:让某个…...
typeof运算符 +unll和undefined的区别
typeof运算符 JavaScript 有三种方法,可以确定一个值到底是什么类型。而我们 现在需要接触到的就是typeof 数值返回number 1 typeof 123 // "number" 字符串返回string 1 typeof 123 // "string" 布尔值返回boolean 1 typeof fal…...
Postgresql字符串操作函数
目录 一、基础字符串操作 二、大小写转换 三、空白处理 四、子串提取 五、搜索与定位 六、字符串修改 七、填充与格式化 八、编码转换 九、正则表达式(高级匹配) 十、其他实用函数 使用技巧: 以下是 PostgreSQL 中最全面的常用字符…...
Linux Docker的简介
参考资料 30分钟Docker入门教程 ◀ 本篇博客所有图片皆来自于该视频截图阮一峰 - Docker 入门教程 目录 一. 环境配置时可能会遇到的问题二. 什么是Docker三. 虚拟机 与 Docker 的区别3.1 虚拟机3.2 Docker 四. Docker的基本架构五. Dockerfile 一. 环境配置时可能会遇到的问题…...
