在OpenGL批处理渲染器中批处理任意顶点数据

3

我正在使用OpenGL制作一个受XNA/MonoGame界面启发的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);

一个sprite包含四个顶点、颜色和纹理坐标,而其他三个可以包含任意数量的内容(sprite和shape具有独特的变换)。所有的东西都可以被纹理化或未被纹理化。我想要将所有东西批量处理以减少状态改变和OpenGL绘制调用的数量。我认为可以合理地假设大多数提交都有共享的顶点,这样我就可以使用glDrawElements而不是glDrawArrays,但是我在考虑如何正确地批处理时遇到了困难。
XNA/MonoGame sprite批处理器之所以工作是因为它们仅与纹理化的四边形/三角形一起使用,而不是任意形状。或者,我可以像SFML渲染器一样为每个可绘制对象发布一个绘制调用,但这违背了批处理渲染的目的。
我觉得我的渲染器正在尝试“做所有事情”,这是我想要避免的,因为在我的经验中,它通常很快变得过于复杂。
我想问的是:我应该如何重新设计我的渲染器?我能否保留不同提交的单独批处理列表?我能否以某种方式将我的渲染器模块化?我是否应该只允许纹理化的对象,就像XNA/MonoGame一样?
1个回答

5
好的,所以我们需要最小化状态更改和绘制调用次数。我假设您正在使用现代OpenGL,包括顶点缓冲对象和着色器。
一种方法是确保所有顶点数据具有相同的格式。例如,每个顶点都有一个位置、颜色和纹理坐标(xyz、rgba、uv)。如果我们在VBO中交错我们的顶点数据,我们只需要在渲染之前调用一次glVertexAttribPointerglEnableVertexAttribArray
这意味着对于未纹理化的对象来说会有一些冗余数据,但我们可以将所有内容压缩到单个批次中,这很好。
要处理未纹理化的对象,您可以绑定一个空白白色纹理并将其视为纹理化对象。或者,您可以在片段着色器中有一个统一变量(0到1之间的浮点数),并使用mix函数在纹理颜色和顶点颜色之间混合。
要批处理精灵和形状,我们应该首先在CPU上处理变换,以便我们始终将“世界”坐标上传到GPU。这样可以避免为每个精灵设置变换统一变量,这将需要单独的绘制调用。
此外,我们需要尽可能按纹理排序,因为纹理绑定是您可以执行的更昂贵的操作之一。
我们的方法基本上归结为以下内容:
- 维护单个顶点和索引缓冲对象以存储数据 - 保持所有顶点数据具有相同的格式,并在VBO中交错数据 - 按纹理排序 - 在更改纹理(或设置纹理混合统一变量,如果我们选择该选项)时刷新缓冲区中的数据(绘制元素/数组)
将数据从CPU传输到GPU内存可以通过不同的方式完成。例如,首先在GPU上分配足够大的空的内存缓冲区,并在每次Render调用时使用glBufferSubData上传一部分顶点/索引数据子集。
记得进行性能分析!
进行这种工作时进行性能分析非常重要。例如,比较批处理和单独的绘制调用之间的性能,或者glDrawArrays与glDrawElements之间的性能。我建议使用gDebugger,它是一个免费且非常好的OpenGL分析器。
还要注意,过大的VBO可能会影响性能。因此,请将其保持在合理的大小,并在填满时使用绘制调用刷新它。

这是非常实用的信息,非常感谢。我总共要创建三个渲染器(支持不同版本以供教育目的),但是据我所见,您的见解可以很好地转化。我的顶点确实具有相同的格式。在使用mix函数时,我只会使用值0和1进行插值,即完整的纹理颜色或完整的顶点颜色,对吗?变换正如您所建议的那样处理。我也考虑在批量渲染之前将未贴图的对象复制到批次中。我不知道gDebugger,感谢提供链接! - NordCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接