帧缓冲区(FBO)是一个“离屏画布”,你可以往上面画东西,而不是直接画到屏幕上。
在OpenGL中,默认渲染画面到屏幕(后台缓冲区)。但在游戏引擎中,通常渲染画面到自己的帧缓冲区中,然后再用这个结果做后处理、选中检测、反射、模糊等高级特效。
然而,FBO 本身不存储任何像素,真正保存图像的是附件。
附件(Attachment)
附件就是一块可以附加到 FBO 上的纹理或缓冲区,用来保存渲染结果。
每个帧缓冲区可以有多个附件,就是它实际存储渲染结果的地方。
附件主要分为三类:
- 颜色附件:存储颜色像素值,用于最终画面输出、后处理、颜色选取。
- 深度附件:存储深度信息(z缓冲),用于深度测试(谁挡住谁)。
- 模板附件:存储模板值(通常是一个整数),用于控制渲染遮罩、描边、裁剪等。
- 自定义整数附件:可保存对象ID等任意整数,用于鼠标拾取等。
帧缓冲区与附件的组成关系如下:
Framebuffer(FBO)
├── Color Attachment 0 → 纹理A(RGBA8) ← 场景颜色渲染结果
├── Color Attachment 1 → 纹理B(R32I) ← 物体ID信息
├── Depth Attachment → 纹理C(Depth24)← 用于深度测试
创建FBO后,可以这样指定附件:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColor, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texID, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texDepth, 0);
具体应用
场景正常渲染:
- 使用
Color Attachment 0→ 渲染RGB画面。 - 使用
Depth Attachment→ 做遮挡剔除,保证远的物体不会挡住近的。
物体拾取(ID Picking):
- 使用
Color Attachment 1(R32I 格式) → 渲染时每个物体写入唯一ID。 - 鼠标点击时,读取这个附件上的像素 → 得到哪个物体被点击。
后处理(如 Bloom):
- 渲染一帧到
Color Attachment→ 再作为贴图送到后处理Shader做模糊、光晕。
代码
在类OpenGLFrameBuffer中,分别定义:模板Specification和资源Attachment
// FBO配置模板
FramebufferSpecification m_Specification;
std::vector<FramebufferTextureSpecification> m_ColorAttachmentSpecifications;
FramebufferTextureSpecification m_DepthAttachmentSpecification = FramebufferTextureFormat::None;
// FBO实际资源
std::vector<uint32_t> m_ColorAttachments; // 纹理附件ID列表:附加到当前帧的颜色附件
uint32_t m_DepthAttachment = 0; // 深度附件ID
当初始化一个FBO时,通常只会传入一个m_Specification,其中包含了颜色和深度的Specification,
由于一个FBO可以有多个颜色附件,所以用Vector保存。
在构造函数中,先对m_Specification进行处理,即分类为颜色和深度specification:
for (auto spec : m_Specification.Attachments.Attachments)
{
if (!Utils::IsDepthFormat(spec.TextureFormat))
{
m_ColorAttachmentSpecifications.emplace_back(spec);
}
else
{
m_DepthAttachmentSpecification = spec;
}
}
使用OpenGL语句创建FBO,实现流程:创建 -> 绑定 -> 创建附件 -> 绑定附件 -> 绘制 -> 解绑。
glCreateFramebuffers(1, &m_RendererID);
glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID);
/* 创建颜色附件和深度附件:从Specification到Attachment */
glDrawBuffers();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
创建颜色附件:
if (m_ColorAttachmentSpecifications.size())
{
m_ColorAttachments.resize(m_ColorAttachmentSpecifications.size());
Utils::CreateTextures(multisample, m_ColorAttachments.data(), m_ColorAttachments.size());
for (size_t i = 0; i < m_ColorAttachments.size(); i++)
{
Utils::BindTexture(multisample, m_ColorAttachments[i]);
switch (m_ColorAttachmentSpecifications[i].TextureFormat)
{
case FramebufferTextureFormat::RGBA8:
Utils::AttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_RGBA8, GL_RGBA, m_Specification.Width, m_Specification.Height, i);
break;
case FramebufferTextureFormat::RED_INTEGER:
Utils::AttachColorTexture(m_ColorAttachments[i], m_Specification.Samples, GL_R32I, GL_RED_INTEGER, m_Specification.Width, m_Specification.Height, i);
break;
}
}
}
创建深度附件:
if (m_DepthAttachmentSpecification.TextureFormat != FramebufferTextureFormat::None)
{
Utils::CreateTextures(multisample, &m_DepthAttachment, 1);
Utils::BindTexture(multisample, m_DepthAttachment);
switch (m_DepthAttachmentSpecification.TextureFormat)
{
case FramebufferTextureFormat::DEPTH24STENCIL8:
tils::AttachDepthTexture(m_DepthAttachment, m_Specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_Specification.Width, m_Specification.Height);
break;
}
}

Leave a comment