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安装包。安装过程中可以配置其安装包图标、名称、出版人、网站等。此外,还可以设置程序开机自启动、管理员权限运行…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
