C++ 最好更新一个小顶点缓冲区,或者发送一个统一的?

C++ 最好更新一个小顶点缓冲区,或者发送一个统一的?,c++,opengl,user-interface,shader,C++,Opengl,User Interface,Shader,我正在为我的OpenGL(core profile)游戏引擎编写/规划一个GUI渲染器,我不完全确定应该如何表示四边形的顶点数据。到目前为止,我已经想到了两种可能的解决方案: 1) 简单的方法是,每个GuiElement都跟踪自己的顶点数组对象,其中包含2d屏幕坐标和纹理坐标,并在移动GuiElement或调整其大小时更新(glBufferSubData()) 2) 我全局存储单个顶点数组对象,其坐标为(0,0)(1,0)(0,1)(1,1),并将rect作为vec4均匀(x,y,w,h)上传到

我正在为我的OpenGL(core profile)游戏引擎编写/规划一个GUI渲染器,我不完全确定应该如何表示四边形的顶点数据。到目前为止,我已经想到了两种可能的解决方案:

1) 简单的方法是,每个
GuiElement
都跟踪自己的顶点数组对象,其中包含2d屏幕坐标和纹理坐标,并在移动
GuiElement
或调整其大小时更新(
glBufferSubData()

2) 我全局存储单个顶点数组对象,其坐标为
(0,0)(1,0)(0,1)(1,1)
,并将
rect
作为
vec4
均匀(x,y,w,h)上传到每帧,并在顶点着色器中变换顶点位置(
vertex.xy*=guiRect.zw;vertex.xy+=guiRect.xy;


我知道第二种方法有效,但我想知道哪一种更好

对于GUI元素,您可以使用动态顶点缓冲区(环形缓冲区),只需在每一帧上载几何体,因为这是相当少量的几何体数据。然后,您可以批处理GUI元素呈现,这两种方法都不同


如果呈现大量GUI元素(如文本),批处理非常重要。你可以很容易地用它构建一个通用的GUI渲染系统,它缓存GUI元素draw调用,并在状态发生变化时将绘图刷新到GPU。

我建议像DXUT那样做,它从每个元素中提取矩形,并用一个通用方法渲染它们,该方法将元素作为参数,其中包含一个rect。每个控件可以有许多元素。它在
STREAM\u DRAW
模式中以特定顺序将rect的四个点添加到缓冲区,并添加一个常量索引缓冲区。这确实会单独绘制每个矩形,但性能并不完全重要,因为几何体很简单,并且当您处于对话框中时,通常可以将3d场景的渲染放在次要位置。编辑:即使使用它来处理HUD项目,它的性能损失也可以忽略不计

这是一种简单而有组织的方法,可以很好地处理纹理,并且只有两个着色器,一个用于绘制纹理组件,另一个用于非纹理组件。然后有一种特殊的方式来做文本

如果你想看看我是怎么做到的,你可以看看这个:


它在GLUFGui.h/.cpp中,我很喜欢选项二的想法,但是,它效率很低,因为它需要对每个元素进行一次draw调用。正如其他回复中提到的,最大的性能提升在于批处理几何体和减少绘制调用的数量。(换句话说,减少应用程序与GL驱动程序通信的时间)

因此,我认为使用OpenGL绘制2D对象的最快方法是使用一种类似于选项1的技术,但在其中添加批处理

在屏幕上绘制四边形所需的最小顶点格式是简单的
vec2
,每个四边形有4个
vec2
s。纹理坐标可以在非常轻量级的顶点着色器中生成,例如:

// xy = vertex position in normalized device coordinates ([-1,+1] range).
attribute vec2 vertexPositionNDC;

varying vec2 vTexCoords;

const vec2 scale = vec2(0.5, 0.5);

void main()
{
    vTexCoords  = vertexPositionNDC * scale + scale; // scale vertex attribute to [0,1] range
    gl_Position = vec4(vertexPositionNDC, 0.0, 1.0);
}
在应用程序端,您可以通过使用两个顶点缓冲区设置双缓冲区来优化吞吐量,这样您可以在给定帧上写入其中一个,然后翻转缓冲区并将其发送到GL,同时立即开始写入下一个缓冲区:

// Update:
GLuint vbo = vbos[currentVBO];
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferSubData(GL_ARRAY_BUFFER, dataOffset, dataSize, data);

// Draw:
glDrawElements(...);

// Flip the buffers:
currentVBO = (currentVBO + 1) % NUM_BUFFERS;
或者另一个更简单的选择是使用单个缓冲区,但在每次提交时分配新存储,以避免阻塞,如下所示:

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, dataSize, data, GL_STREAM_DRAW);
这是一种众所周知的用于简单异步数据传输的技术。更多


使用索引几何体也是一个好主意。使用顶点缓冲区保留一个无符号短索引缓冲区。每个元素2字节的IB将大大减少数据流量,索引范围应该足够大,可以容纳您可能希望绘制的任意数量的2D/UI元素。

如果使用方法2,您可以对所有元素重复使用相同的几何体。这正是我最初提出方法2的原因。哦,我明白了,对不起。我误认为“单顶点数组对象”是指每个元素的单个实例。Oops:-)听起来像是一个非常复杂的解决方案,基本上就是我用选项2描述的那样……谢谢你的建议,我真的很喜欢这个想法。它可能不适用于所有与gui相关的工作(字体和图标需要特别考虑),但对于大多数gui工作来说,这是一种极好的方法。我可能必须以更智能的方式调整纹理坐标,特别是对于边框,但这应该可以很好地工作。