Framebuffers 帧缓冲

LearnOpenGL 顺序:Advanced OpenGL → Framebuffers · 原教程

在管线哪里

... → Fragment Shader → Depth / Stencil / Blending → 当前绑定的 Framebuffer attachments

这里要修正一个常见心智模型:Framebuffer 更像“这一遍渲染要写到哪里”,而不是一个像 Fragment Shader 那样的计算阶段。 默认情况下我们写进窗口系统提供的默认 framebuffer;绑定自己的 FBO 后,同样的绘制命令会改写你挂在它上面的 color / depth / stencil attachments。

FBO 本身更像“附件集合与状态对象”,真正保存像素的是附件:

Framebuffer Object
├─ GL_COLOR_ATTACHMENT0        → Texture
└─ GL_DEPTH_STENCIL_ATTACHMENT → Renderbuffer

核心代码

unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
 
// color attachment:之后要采样,所以用 texture
glFramebufferTexture2D(
    GL_FRAMEBUFFER,
    GL_COLOR_ATTACHMENT0,
    GL_TEXTURE_2D,
    textureColorbuffer,
    0
);
 
// depth/stencil:这里只做测试,不采样,所以用 renderbuffer
glFramebufferRenderbuffer(
    GL_FRAMEBUFFER,
    GL_DEPTH_STENCIL_ATTACHMENT,
    GL_RENDERBUFFER,
    rbo
);
 
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    // debug attachments
 
// 0 = 默认 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);

最实用的选择规则:之后要在 shader 里采样 → texture attachment;只需要拿来做 depth/stencil test → renderbuffer 通常更合适。

两遍渲染是这一节的核心

// Pass 1:场景 → 自定义 FBO 的 color texture
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glEnable(GL_DEPTH_TEST);
DrawScene();
 
// Pass 2:color texture → 默认 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
DrawFullscreenQuad();

第一遍结束之后,“整张已经渲染好的场景”变成了一张纹理。第二遍 fragment shader 因此可以对最终图像逐像素做反相、灰度、锐化、模糊、边缘检测等后期处理。

Interactive Lab

推荐先切换 None → Inversion,确认“场景已经变成一张可采样纹理”;再关闭 Depth/Stencil Attachment,看 off-screen pass 的深度关系如何出错;最后把 color attachment 分辨率改成 0.25×,观察为什么附件尺寸变化后 glViewport 也必须跟着改。

这节最容易踩的坑

  • 绑定了 custom FBO 后忘了 glBindFramebuffer(GL_FRAMEBUFFER, 0),结果窗口里什么都没有。
  • 自定义 FBO 没有 depth attachment,却以为 GL_DEPTH_TEST 会自动借用默认 framebuffer 的深度缓冲;每个 framebuffer 有自己的一套 attachments。
  • FBO texture 尺寸和 viewport 不一致,导致只渲染到附件的一部分或比例不对。
  • 窗口 resize 后只改了 glViewport,却忘了重建/resize framebuffer attachments。
  • 忘记 glCheckFramebufferStatus,导致附件配置错误时只能对着黑屏猜。

往外长的知识

Framebuffer 是后面很多技术的共同基础:Shadow Map、Mirror / Portal、Picking、HDR、Bloom、Ping-Pong Blur、Deferred Rendering / G-Buffer、SSAO、MSAA resolve、各种屏幕空间效果。

从这一节开始,可以把渲染理解成:不是“从顶点一路画到屏幕就结束”,而是可以把一遍渲染结果保存下来,作为下一遍渲染的输入。