4

我正在受 XNA/MonoGame 界面的启发在 OpenGL 中制作 2D 批处理渲染器,但我遇到了一个小设计问题,我正在寻找一些输入。目前,您可以通过四种通用方式提交顶点数据:

void Render(const Sprite& sprite);
void Render(const Shape& shape);
void Render(const Vertex* vertices, unsigned int length);
void Render(const Vertex* vertices, unsigned int length, const Texture* texture);

一个精灵包含四个顶点、颜色和纹理坐标,而其他三个可以包含任意数字(精灵和形状具有唯一的变换)。一切都可以有纹理或无纹理。我想批量处理所有内容以减少状态更改和 OpenGL 绘制调用的数量。我认为假设大多数提交将具有共享顶点是合理的,这样我就可以使用 glDrawElements 而不是 glDrawArrays,但是鉴于我上面描述的内容,我很难弄清楚如何正确地批处理事情。

XNA/MonoGame 精灵批处理器之所以有效,是因为它们仅适用于带纹理的四边形/三角形,而不是任意形状。或者,我可以像 SFML 渲染器一样为每个可绘制对象发出绘制调用,但这违背了批量渲染的目的。

我觉得我的渲染器正在尝试“做所有事情”,这是我想要避免的,因为在我的经验中它通常很快变得太复杂。

我要问的是:我怎样才能重新设计我的渲染器?我可以为不同的提交保留单独的批次列表吗?我可以以某种方式模块化我的渲染器吗?我应该只允许 XNA/MonoGame 中的纹理对象吗?

4

1 回答 1

6

好的,所以我们需要尽量减少状态更改和绘制调用的次数。我假设您使用的是现代 OpenGL,包括顶点缓冲区对象和着色器。

一种方法是确保所有顶点数据具有相同的格式。例如,每个顶点都有一个位置、颜色和纹理坐标(xyz、rgba、uv)。如果我们在 VBO 中交错顶点数据,我们只需要在渲染之前调用一次glVertexAttribPointerand glEnableVertexAttribArray

这意味着未纹理对象的一些冗余数据,但我们可以将所有内容都塞进一个批次中,这很好。

要处理无纹理的对象,您可以绑定一个空白的白色纹理并将其视为带纹理的对象。或者,您可以在片段着色器中有一个统一变量(介于 0 和 1 之间的浮点数),并使用该mix函数在纹理颜色和顶点颜色之间进行混合。

要批量处理精灵和形状,我们应该首先在 CPU 上处理变换,以便我们始终将“世界”坐标上传到 GPU。这使我们不必为每个精灵设置一个转换统一,每个精灵都需要单独的绘制调用。

此外,我们需要尽可能按纹理排序,因为纹理绑定是您可以执行的更昂贵的操作之一。

我们的方法基本上归结为以下几点:

  • 维护单个顶点和索引缓冲区对象来存储数据
  • 将所有顶点数据保存在单一格式中,并在 VBO 中交错数据
  • 按纹理排序
  • 每当我们更改纹理(或设置纹理混合均匀,如果我们使用该选项)时刷新缓冲区中的数据(绘制元素/数组)

可以通过不同的方式将数据从 CPU 获取到 GPU 内存。例如,通过首先在 GPU 上分配一个足够大的空内存缓冲区,并glBufferSubData在您执行其中一个 Render 调用时用于上传顶点/索引数据的子集。

记得要简介!

在进行此类工作时进行分析非常重要。例如,比较批处理和单个绘制调用之间的性能,或者 glDrawArrays 与 glDrawElements。我推荐使用gDebugger,它是一个免费且非常好的 OpenGL 分析器。

另请注意,过大的 VBO 会损害您的表现。因此,请将其保持在合理的大小,并在其填满时使用 draw call 刷新它。

于 2013-08-08T17:08:31.480 回答