我正在使用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 sprite batchers的工作原理是它们只使用纹理四边形/三角形而不是任意形状。或者,我可以像SFML渲染器一样为每个可绘制对象发出一个绘制调用,但这会破坏批渲染的目的。
我觉得我的渲染器正试图“做所有事情”,这是我想要避免的事情,因为在我的经历中它通常会变得过于复杂。
我要问的是:我怎样才能重新设计我的渲染器?我可以为不同的提交保留单独的批次列表吗?我可以以某种方式模块化我的渲染器吗?我应该只允许在XNA / MonoGame中使用纹理对象吗?
答案 0 :(得分:6)
好的,我们需要最小化状态更改次数并绘制调用。我假设您正在使用现代OpenGL,包括Vertex Buffer Objects和着色器。
一种方法是确保所有顶点数据具有相同的格式。例如,每个顶点都有一个位置,颜色和纹理坐标(xyz,rgba,uv)。如果我们在VBO中交错我们的顶点数据,我们只需要在渲染之前调用glVertexAttribPointer
和glEnableVertexAttribArray
。
这意味着无纹理对象的一些冗余数据,但我们可以把所有内容都塞进一个批处理中,这很不错。
要处理无纹理对象,您可以绑定空白白色纹理并将其视为纹理对象。或者,您可以在片段着色器中使用统一变量(0到1之间的浮点数),并使用mix
函数在纹理颜色和顶点颜色之间进行混合。
要批量精灵和形状,我们应首先处理CPU上的转换,以便我们始终将“世界”坐标上传到GPU。这使我们不必为每个精灵设置变换均匀,每个精灵都需要单独的绘制调用。
此外,我们需要尽可能按纹理排序,因为纹理绑定是您可以执行的更昂贵的操作之一。
我们的方法基本归结为以下几点:
可以通过不同方式将数据从CPU获取到GPU内存。例如,首先在GPU上分配足够大的空内存缓冲区,并使用glBufferSubData
上传顶点/索引数据的子集,无论何时进行一次渲染调用。
在进行此类工作时进行性能分析非常重要。例如,比较批处理和单个绘制调用之间的性能,或glDrawArrays与glDrawElements之间的性能。我建议使用gDebugger,这是一个免费且非常好的OpenGL分析器。
另请注意,VBO太大hurt your performance。所以保持一个合理的大小,并在它填满时用一个绘制调用冲洗它。