WAYNETS.ORG

Game and Program

帧缓冲区

作者:

发表于

Context Polling System Post-Processing Renderer

帧缓冲区(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