【OpenGL 渲染器开发笔记】7 帧缓冲

我们渲染器中的最后一个主要组件是帧缓冲区。帧缓冲区是用于“渲染到纹理”技术的纹理容器。其中一些技术相当简单,例如,场景可渲染到一个附加到帧缓冲区的高分辨率纹理上,之后该纹理会被保存到磁盘,以此实现以远高于显示器分辨率的精度截取屏幕画面。其他使用帧缓冲区的技术则更为复杂,例如延迟着色:在第一个渲染阶段向多个纹理写入数据,输出深度、法向量和材质属性等信息;在第二个阶段,渲染一个全屏四边形,通过读取这些纹理来为场景着色。许多使用帧缓冲区的技术都会在第一个阶段向纹理写入数据,然后在后续阶段从中读取数据。

在我们的渲染器中,帧缓冲区由Framebuffer类表示,如图7-1所示。
在这里插入图片描述

struct ColorAttachments {
	virtual ~ColorAttachments() = default;

	virtual Texture2D& operator[](size_t index) = 0;
	virtual const Texture2D& operator[](size_t index) const = 0;
	virtual size_t count() const = 0;

	virtual Texture2D::iterator begin() = 0;
	virtual Texture2D::iterator end() = 0;
};


class Framebuffer {
public:
	virtual ~Framebuffer() = default;

	virtual ColorAttachments& getColorAttachments() = 0;
	virtual Texture2D& getDepthAttachment() const = 0;
	virtual void setDepthAttachment(Texture2D& texture) = 0;

	virtual Texture2D& getDepthStencilAttachment() const = 0;
	virtual void setDepthStencilAttachment(Texture2D& texture) = 0;
};

客户端可通过Context.createFramebuffer创建帧缓冲区。与顶点数组类似,帧缓冲区是轻量级容器对象,且不能在多个上下文之间共享,这也是createFramebufferContext的方法而非Device的原因。帧缓冲区创建后,我们可以将多个纹理附加为颜色附件,并将另一个纹理附加为深度附件或深度/模板附件。

纹理格式必须与帧缓冲区附件类型兼容,这正是Texture2DDescription包含ColorRenderableDepthRenderableDepthStencilRenderable属性的原因。

我们通过将帧缓冲区赋值给Context.framebuffer来渲染到该帧缓冲区。如果启用了深度测试渲染状态,帧缓冲区必须有一个深度附件或深度/模板附件。忘记添加深度附件是OpenGL初学者的常见错误。在这种情况下,我们的渲染器会在Context.draw中抛出异常。

Framebuffer framebuffer = context.createFramebuffer();

const uint32_t width = 640;
const uint32_t height = 480;
const bool mipmaps = false;

// 创建并附加颜色纹理
Texture2DDescription colorDesc(width, height, TextureFormat::RedGreenBlue8, mipmaps);
framebuffer.colorAttachments[0] = Device::createTexture2D(colorDesc);

// 创建并附加深度纹理
Texture2DDescription depthDesc(width, height, TextureFormat::Depth32f, mipmaps);
framebuffer.depthAttachment = Device::createTexture2D(depthDesc);

当使用多个颜色附件时,必须将片段着色器的输出变量与帧缓冲区的颜色附件索引对应起来,这与顶点着色器的属性输入和顶点数组属性的匹配方式类似。例如,一个片段着色器可能包含两个输出变量:

out vec4 dayColor;
out vec4 nightColor;

通过ShaderProgram.fragmentOutputs可以查询片段着色器输出变量对应的颜色附件索引。该索引可用于将纹理分配到相应的颜色附件,从而建立着色器输出变量与纹理的关联:

ShaderProgram sp = //...
framebuffer.colorAttachments[sp.fragmentOutputs("dayColor")] = dayTexture;
framebuffer.colorAttachments[sp.fragmentOutputs("nightColor")] = nightTexture;

附加到帧缓冲区的纹理被写入后,可被分配到纹理单元并在后续渲染阶段中读取,但无法在单次绘制调用中同时对同一纹理进行读写操作

我们本可以将Framebuffer属性从Context移至DrawState,以减少上下文级别的全局状态。但将帧缓冲区保留为上下文的一部分更为便捷,因为这允许对象在渲染时无需知晓自己正渲染到哪个帧缓冲区。尽管这种设计依赖于上下文级别的状态,但帧缓冲区的管理要比整个渲染器状态简单得多。

GL 渲染器实现

帧缓冲区的OpenGL实现在FramebufferGL3xColorAttachmentsGL3x中完成。OpenGL帧缓冲区对象的名称通过glGenFramebuffers创建,通过glDeleteFramebuffers销毁。正如本章中多次提到的,这里采用了延迟处理技术:在客户端代码调用Context.drawContext.clear之前,不会执行其他OpenGL调用。许多对象(如着色器和纹理)不会影响清除操作,因此需要注意,清除操作的效果取决于当前绑定的帧缓冲区。

与常规做法一致,当客户端代码将纹理分配给帧缓冲区的附件时,该附件会被标记为“脏”。在绘制或清除操作执行前,帧缓冲区会通过glBindFramebuffer绑定,然后调用glFramebufferTexture清理所有脏附件。如果某个脏附件没有绑定纹理(例如,被赋值为null),则调用glFramebufferTexture时传入的纹理参数为0。同样,如果Context.framebuffer被赋值为null,glBindFramebuffer会传入帧缓冲区参数0,这意味着使用默认帧缓冲区。


参考:

  • Cozi, Patrick; Ring, Kevin. 3D Engine Design for Virtual Globes. CRC Press, 2011.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值