【openGLES】帧缓冲区对象frameBufferObject(FBO)
文章目录
- 帧缓冲区基础
- 为什么要使用帧缓冲区对象
- 帧缓冲区、渲染缓冲区和纹理的区别与联系
- 📊 FBO 附着点绑定对象对比总结
- 帧缓冲区对象
- 1. **创建纹理作为颜色附件**
- 2. **创建 FBO 并附加纹理**
- 3. **渲染到纹理**
- 4. **着色器配置**
- **OpenGL ES 3.0+(推荐)**
- 5. **使用渲染后的纹理**
- 渲染缓冲区对象
- 🛠️ **RBO 的创建与使用步骤**
- 1. 创建并分配存储空间
- 2. 附加到 FBO
- 3. 渲染到 RBO
- 帧缓冲区完整性
- 帧缓冲区位块传送
- **📌 帧缓冲区位块传送(Framebuffer Blitting)详解**
- **📌 核心 API:`glBlitFramebuffer`**
- **参数说明**
- **📌 使用步骤**
- **1. 绑定源和目标帧缓冲区**
- **2. 执行位块传送**
- **3. 解绑帧缓冲区**
- **📌 典型应用场景**
- **1. 解析多重采样(MSAA)到普通纹理**
- **📌 注意事项**
- 帧缓冲区失效
- 📌 1. 什么是帧缓冲区失效?
- ⚙️ 2. 为什么需要帧缓冲区失效?
- 🛠️ 3. 如何使用帧缓冲区失效?
- 🎯 4. 适用场景与最佳实践
- 🚀 失效操作的性能提升场景
- 注意事项
OpenGL 中的 FBO(Frame Buffer Object,帧缓冲对象) 与 OpenCV 中的 cv::Mat(单纯的内存图像存储)有本质区别。FBO 是 GPU 端的逻辑容器,用于管理渲染目标(如颜色、深度、模板缓冲),而非直接存储像素数据。以下是其核心原理和使用的详细解析:
帧缓冲区基础
为什么要使用帧缓冲区对象
使用帧缓冲区(Framebuffer Object,简称 FBO)是 OpenGL ES 中实现高级渲染技术的核心手段。它本质上是一个不直接显示到屏幕的离屏渲染目标,允许你将场景渲染到纹理或其他缓冲区中,而非默认的窗口缓冲区。
为了让你快速理解其核心价值,我先用一个表格总结使用 FBO 的主要原因:
主要原因 | 说明 |
---|---|
离屏渲染 | 将场景渲染到纹理中,而不是直接渲染到屏幕。 |
后期处理 | 对渲染好的图像进行全屏特效处理(如模糊、锐化、色调调整)。 |
环境映射 | 动态生成环境贴图,用于反射和折射效果。 |
阴影技术 | 实现阴影映射(Shadow Mapping),这是生成真实阴影的主流方法。 |
减少重绘 | 将静态场景或元素预先渲染到纹理,避免每帧重复渲染。 |
抗锯齿 | 实现多重采样抗锯齿(MSAA),提升图像质量。 |
帧缓冲区、渲染缓冲区和纹理的区别与联系
一个 FBO 本身不存储图像数据,它只是一个附件集合的容器。例如可以为其附加两种类型的存储对象:
- 纹理附件(Texture Attachment):将纹理对象附加到 FBO 的颜色附着点(如
GL_COLOR_ATTACHMENT0
)。渲染后,图像数据会存储在纹理中,之后可以像普通纹理一样被采样使用。 - 渲染缓冲区附件(Renderbuffer Attachment):将渲染缓冲区对象(RBO)附加到 FBO 的深度和模板附着点(
GL_DEPTH_ATTACHMENT
,GL_STENCIL_ATTACHMENT
)。RBO 是经过优化的离屏缓冲区,不支持采样,但作为深度和模板测试的存储时可能更高效。
创建和使用 FBO 的基本流程如下所示:
FBO的颜色纹理
好的,这是一个总结了 OpenGL ES 中帧缓冲区(FBO)各种附着点可绑定的对象类型、适用场景、以及各自优缺点的对比表格。
📊 FBO 附着点绑定对象对比总结
附着点类型 | 可绑定对象 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
颜色附着点 ( GL_COLOR_ATTACHMENTi ) | 纹理 (Texture) | 绝大多数情况 • 后期处理(需要采样渲染结果) • 环境映射/反射 • 渲染到纹理以供后续使用 | 灵活:渲染结果可被着色器采样,用途广泛。 | 可能比 RBO 有轻微的性能开销(取决于硬件驱动优化)。 |
渲染缓冲区 (RBO) | 临时性、中间渲染步骤 • 不需要对颜色结果进行采样的离屏渲染 • 某些硬件上可能作为快速颜色附件 | 可能更高效:驱动可能为其优化,减少内存带宽占用。 | 不灵活:无法被着色器采样,渲染后数据“只进不出”。 | |
深度附着点 ( GL_DEPTH_ATTACHMENT ) | 深度纹理 (Depth Texture) | 需要访问深度值的算法 • 阴影映射(Shadow Mapping) • 屏幕空间环境光遮蔽(SSAO) • 其他屏幕空间效果 | 可采样:深度数据可传入着色器用于复杂计算。 | 需要 OpenGL ES 3.0+ 或 GL_OES_depth_texture 扩展(ES 2.0)。 |
渲染缓冲区 (RBO) | 仅需深度测试,无需访问深度数据 • 普通的离屏渲染,只需要深度测试功能 | 可能更高效:在某些硬件上,作为纯深度测试存储可能性能更优。 | 不可采样:深度数据无法被着色器使用。 | |
模板附着点 ( GL_STENCIL_ATTACHMENT ) | 渲染缓冲区 (RBO) | 最常用和兼容的方式 • 任何需要使用模板测试的离屏渲染 | 广泛支持:所有支持 FBO 的 OpenGL ES 版本都可用。 高效:专为模板测试设计。 | 不可采样:模板值无法被着色器直接读取。 |
深度+模板纹理 ( packed depth-stencil texture ) | 需要访问模板值的算法 • 高级模板操作(较少见) | 可采样:理论上模板值可以被读取(需注意硬件支持)。 | 支持度有限:并非所有硬件/驱动都支持将打包的深度模板纹理用于模板附着点。应优先使用 RBO。 | |
深度+模板联合附着点 ( GL_DEPTH_STENCIL_ATTACHMENT ) | 深度+模板纹理 (e.g., GL_DEPTH24_STENCIL8 ) | 需要同时使用深度和模板测试,且希望附件是纹理 • 渲染到深度模板纹理,可能用于后续采样或读取 | 一体化:单个纹理同时包含深度和模板信息,管理方便。 可采样:数据可用于着色器。 | 需要 OpenGL ES 3.0+ 或相关扩展支持。 |
渲染缓冲区 (RBO) (e.g., GL_DEPTH24_STENCIL8 ) | 需要同时使用深度和模板测试的标准方式 • 普通的离屏渲染 | 高效且兼容性好:这是最通用和稳定的方式。 | 不可采样:数据无法被着色器使用。 |
帧缓冲区对象
1. 创建纹理作为颜色附件
首先需要创建一个纹理,它将作为 FBO 的颜色附件接收渲染输出:
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);// 分配纹理内存(不填充数据)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, // 内部格式:RGBA1024, 1024, // 纹理尺寸0, GL_RGBA, GL_UNSIGNED_BYTE, NULL // 初始数据为空
);// 设置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2. 创建 FBO 并附加纹理
将纹理绑定为 FBO 的颜色附件:
GLuint fboID;
glGenFramebuffers(1, &fboID);
glBindFramebuffer(GL_FRAMEBUFFER, fboID);// 将纹理附加到 FBO 的颜色附着点
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, // 颜色附件索引GL_TEXTURE_2D, textureID, 0 // Mipmap 级别
);// 可选:附加深度和模板附件(如需要)
GLuint rboID;
glGenRenderbuffers(1, &rboID);
glBindRenderbuffer(GL_RENDERBUFFER, rboID);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1024, 1024);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rboID);// 检查 FBO 完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {// 错误处理...
}
3. 渲染到纹理
绑定 FBO 后,所有渲染操作将输出到纹理:
// 绑定自定义 FBO
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
glViewport(0, 0, 1024, 1024); // 视口匹配纹理尺寸// 清除缓冲区
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// 绘制场景(片段着色器输出到纹理)
drawScene();// 切换回默认帧缓冲区(屏幕)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
4. 着色器配置
片段着色器需明确输出到 FBO 的颜色附件:
OpenGL ES 3.0+(推荐)
#version 300 es
precision mediump float;// 输出到 FBO 的颜色附件0
layout(location = 0) out vec4 fragColor;void main() {fragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
5. 使用渲染后的纹理
渲染完成后,纹理 textureID
中已存储了渲染结果,可像普通纹理一样采样:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);// 在着色器中采样
uniform sampler2D u_RenderedTexture;
in vec2 v_TexCoord;
out vec4 fragColor;void main() {fragColor = texture(u_RenderedTexture, v_TexCoord);
}
渲染缓冲区对象
渲染缓冲区(Renderbuffer)是 OpenGL ES 中一种专为离屏渲染设计的高效存储对象,通常附加到帧缓冲区对象(FBO)上,用于临时存储颜色、深度或模板数据。与纹理不同,RBO 不能被着色器直接采样,但在特定场景下(如深度/模板测试)可能比纹理更高效。
特性 | 渲染缓冲区 (RBO) | 纹理 (Texture) |
---|---|---|
用途 | 临时存储颜色/深度/模板数据,用于离屏渲染 | 存储图像数据,可被着色器采样 |
是否可采样 | ❌ 不可采样 | ✅ 可采样 |
内存优化 | 可能更高效(驱动优化) | 灵活性高,但可能有额外开销 |
绑定目标 | GL_RENDERBUFFER | GL_TEXTURE_2D / GL_TEXTURE_3D |
典型附着点 | GL_COLOR_ATTACHMENT (颜色)GL_DEPTH_ATTACHMENT (深度)GL_STENCIL_ATTACHMENT (模板) | 同左,但需支持纹理附件格式 |
修改数据 | 只能通过渲染操作写入(如 glClear , glDraw ) | 可通过 glTexSubImage 直接更新数据 |
🛠️ RBO 的创建与使用步骤
1. 创建并分配存储空间
GLuint rbo;
glGenRenderbuffers(1, &rbo); // 生成 RBO
glBindRenderbuffer(GL_RENDERBUFFER, rbo); // 绑定到目标// 分配存储空间(例如:RGBA8 颜色缓冲区,尺寸 1024x768)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1024, 768);// 对于深度模板联合缓冲区(常用!)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1024, 768);
2. 附加到 FBO
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);// 将 RBO 附加为颜色附件
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);// 将 RBO 附加为深度模板附件(推荐用法!)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);// 检查完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {// 错误处理...
}
3. 渲染到 RBO
glBindFramebuffer(GL_FRAMEBUFFER, fbo); // 绑定到自定义 FBO
glViewport(0, 0, 1024, 768);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// 绘制场景(输出到 RBO)
drawScene();glBindFramebuffer(GL_FRAMEBUFFER, 0); // 切换回默认帧缓冲区
帧缓冲区完整性
帧缓冲区完整性是指 OpenGL ES 检查当前绑定的帧缓冲区对象(FBO)的配置是否满足一系列硬性条件的过程。只有通过所有这些检查,FBO 的状态才是 “完整的”(Complete),才能被用作渲染目标。
你可以通过调用 glCheckFramebufferStatus(GL_FRAMEBUFFER)
来查询完整性状态。它返回以下结果之一:
返回值 (GLenum) | 含义 | 说明 |
---|---|---|
GL_FRAMEBUFFER_COMPLETE | 完整 | 恭喜!FBO 配置正确,可以用于渲染。 |
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT | 附件不完整 | 至少有一个附件的配置无效(如纹理格式/尺寸不匹配)。 |
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT | 缺少附件 | FBO 没有绑定任何颜色附件。 |
GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS | 尺寸不一致 | 所有附件的宽度和高度不统一。 |
GL_FRAMEBUFFER_UNSUPPORTED | 格式不支持 | 附件的组合格式不被系统支持(常见于深度/模板格式冲突)。 |
(ES 3.0+) GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE | 多重采样不一致 | 所有附件的多重采样设置(样本数)不匹配。 |
帧缓冲区位块传送
📌 帧缓冲区位块传送(Framebuffer Blitting)详解
帧缓冲区位块传送(Framebuffer Blitting)是 OpenGL ES 中的一种高效图像数据复制机制,允许在不同帧缓冲区(FBO)之间或 FBO 与默认帧缓冲区(屏幕)之间快速复制像素块。它通常用于以下场景:
- 解析多重采样(MSAA)渲染结果(如将 MSAA FBO 解析为非 MSAA 纹理)。
- 屏幕空间后处理(如将渲染结果复制到另一个 FBO 进行模糊处理)。
- 高效截图(从帧缓冲区读取像素数据到 CPU)。
📌 核心 API:glBlitFramebuffer
在 OpenGL ES 3.0+ 中,glBlitFramebuffer
是实现位块传送的核心函数:
void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, // 源区域(左下、右上)GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, // 目标区域(左下、右上)GLbitfield mask, // 复制内容(颜色/深度/模板)GLenum filter // 缩放过滤方式(GL_NEAREST/GL_LINEAR)
);
参数说明
参数 | 含义 |
---|---|
srcX0, srcY0, srcX1, srcY1 | 源帧缓冲区中的矩形区域(左下角 + 右上角坐标)。 |
dstX0, dstY0, dstX1, dstY1 | 目标帧缓冲区中的矩形区域(左下角 + 右上角坐标)。 |
mask | 指定复制哪些缓冲区:GL_COLOR_BUFFER_BIT (颜色)GL_DEPTH_BUFFER_BIT (深度)GL_STENCIL_BUFFER_BIT (模板) |
filter | 缩放过滤方式:GL_NEAREST (最近邻,适合整数倍缩放)GL_LINEAR (线性插值,适合非整数倍缩放) |
📌 使用步骤
1. 绑定源和目标帧缓冲区
// 绑定源 FBO(如 MSAA FBO)
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);// 绑定目标 FBO(如普通纹理 FBO 或默认帧缓冲区)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
2. 执行位块传送
// 从 MSAA FBO 解析颜色到普通纹理 FBO
glBlitFramebuffer(0, 0, width, height, // 源区域(整个 FBO)0, 0, width, height, // 目标区域(相同尺寸)GL_COLOR_BUFFER_BIT, // 仅复制颜色GL_LINEAR // 线性过滤(适合 MSAA 解析)
);
3. 解绑帧缓冲区
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
📌 典型应用场景
1. 解析多重采样(MSAA)到普通纹理
// 1. 创建 MSAA FBO 和普通纹理 FBO
GLuint msaaFBO, resolveFBO;
glGenFramebuffers(1, &msaaFBO);
glGenFramebuffers(1, &resolveFBO);// 2. 绑定 MSAA FBO(颜色附件为 MSAA RBO)
glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaRBO);// 3. 绑定普通纹理 FBO(颜色附件为普通纹理)
glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolvedTexture, 0);// 4. 从 MSAA FBO 解析到普通纹理
glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
📌 注意事项
-
尺寸匹配:
- 如果源和目标区域尺寸不同,
glBlitFramebuffer
会自动缩放(根据filter
参数)。 - 但建议尽量保持相同尺寸以避免性能损失。
- 如果源和目标区域尺寸不同,
-
格式兼容性:
- 源和目标颜色缓冲区的内部格式必须兼容(如
GL_RGBA8
→GL_RGBA8
)。 - 深度/模板缓冲区需格式相同(如
GL_DEPTH24_STENCIL8
→GL_DEPTH24_STENCIL8
)。
- 源和目标颜色缓冲区的内部格式必须兼容(如
-
性能优化:
- 避免每帧调用:位块传送是 GPU 操作,但仍有一定开销。尽量在必要时使用。
- 优先使用
GL_NEAREST
:如果不需要插值(如整数倍缩放),GL_NEAREST
更快。
-
OpenGL ES 版本:
glBlitFramebuffer
需要 OpenGL ES 3.0+。- 在 ES 2.0 中,需手动实现类似功能(如通过着色器渲染到另一个 FBO)。
帧缓冲区失效
帧缓冲区失效(Framebuffer Invalidation)是 OpenGL ES 3.0 引入的一种性能优化机制,它允许应用程序显式通知图形驱动程序不再需要帧缓冲区的特定附件内容,从而减少不必要的内存操作和带宽消耗,提升渲染效率,尤其在移动设备和Tile-Based渲染架构中效果显著。
📌 1. 什么是帧缓冲区失效?
帧缓冲区失效通过 glInvalidateFramebuffer
API 实现,其核心作用是告知GPU:帧缓冲区的某个或多个附件(如颜色、深度或模板缓冲区)的内容已经无用,可以安全丢弃。这并非删除或清除数据,而是让驱动程序跳过对这些数据的保留、回写或加载操作,从而优化内存带宽使用。 例如,在完成多重采样渲染后,可以失效不再需要的深度附件,避免GPU将其内容写回内存。
⚙️ 2. 为什么需要帧缓冲区失效?
在基于块状渲染(TBR)的GPU架构(常见于移动设备)中,GPU需要将片上缓存的数据写回系统内存。如果应用程序不显式失效不再需要的内容,GPU可能会默认执行这些冗余操作,导致:
- 带宽浪费:不必要的内存传输会增加带宽压力。
- 功耗增加:额外的数据移动会消耗更多电量。
- 性能下降:冗余操作可能降低帧率。
失效机制通过避免这些操作,直接带来性能提升和功耗降低,对于频繁使用离屏渲染(如后处理、阴影渲染)的应用至关重要。
🛠️ 3. 如何使用帧缓冲区失效?
使用 glInvalidateFramebuffer
函数,需指定目标帧缓冲区和要失效的附件列表。其函数原型为:
void glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum *attachments);
- target:可以是
GL_READ_FRAMEBUFFER
、GL_DRAW_FRAMEBUFFER
或GL_FRAMEBUFFER
(被视为GL_DRAW_FRAMEBUFFER
)。 - numAttachments:要失效的附件数量。
- attachments:附件标识符数组,如
GL_COLOR_ATTACHMENT0
、GL_DEPTH_ATTACHMENT
等。
典型示例:在多重采样渲染后失效深度附件和颜色附件。
// 完成渲染到多重采样FBO后
glBindFramebuffer(GL_READ_FRAMEBUFFER, mMSAAFramebuffer);
// 失效深度附件
GLenum attachments[] = {GL_DEPTH_ATTACHMENT};
glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, attachments);
// 执行glBlitFramebuffer后,再失效颜色附件
GLenum colorAttachments[] = {GL_COLOR_ATTACHMENT0};
glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, colorAttachments);
🎯 4. 适用场景与最佳实践
- 常见应用场景:
- 多重采样抗锯齿(MSAA):解析多重采样缓冲区后,失效其深度和颜色附件。
- 多渲染目标(MRT):当某个渲染目标不再需要时,失效其附件。
- 离屏渲染链:在中间渲染步骤后失效临时附件。
🚀 失效操作的性能提升场景
在以下情况下,在绘制前失效FBO能有效提升性能:
- 内容将被完全覆盖时:如果你接下来的渲染通道会绘制每一个像素(例如全屏渲染、后处理特效),旧数据无保留价值。失效可以避免GPU在渲染开始前将片上缓存中的旧数据回写到内存,节省了带宽
- Tile-Based GPU架构:移动设备普遍采用TBDR架构。在该架构下,若未在渲染通道(Render Pass)前进行清除或失效操作,GPU可能会自动从系统内存读取附件初始内容到片上缓存,造成不必要的带宽开销。失效操作能避免该回读
- 中间渲染目标:对于仅在后续渲染步骤中用作输入,而最终结果不需保留的临时FBO,失效其附件是很好的习惯。
注意事项
- 不要再在 窗口屏幕和自定义FBO之间反复切换
- 不要逐帧创建和删除FBO和RBO
- 避免使用glTexImage2D等直接修改附着于FBO的纹理
- 尽可能共享深度和模板附着
- 当RBO附着于FBO时,如果删除RBO,FBO的附着会被重置为0
- 如果整个纹理都要被新画面覆盖:
- 创建时用 glTexImage2D(…, NULL)(省去传初始数据的开销)。
- 渲染前用 glInvalidateFramebuffer告诉 GPU:“旧内容可丢弃了,不用备份”。