2

我在 C++ 中使用 OpenGL(技术上是 EGL,在 Jetson Nano 上。)

假设我想画 N 个四边形。想象一下只是一个彩色矩形列表。框架中可能有几千个这样的矩形。

我想使用两个顶点缓冲区:

  1. 一个定义每个四边形的几何图形。
  2. 一个定义每个四边形共有的属性。

第一个顶点缓冲区应该定义每个四边形的几何形状。它应该只有 4 个顶点,它的数据只是四边形的角。就像是:

0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right

然后第二个顶点缓冲区应该只有所有矩形的 x,y,width,height。

x1, y1, width1, height1, color1,
x2, y2, width2, height2, color2,
x3, y3, width3, height3, color3,
x4, y4, width4, height4, color4,
x5, y5, width5, height5, color5,
x6, y6, width6, height6, color6,
... etc.

问题是我的矩形缓冲区中的每个项目都应该应用于顶点缓冲区中的 4 个顶点。

有没有办法设置它,以便它不断地为每个矩形重复使用相同的 4 个四边形顶点,并一次将相同的矩形属性应用于 4 个顶点?

我想我可以做一些事情,所以我说第一个顶点缓冲区应该每个顶点使用一个元素并环绕,但第二个顶点缓冲区每四个顶点使用一个元素或类似的东西。

我该如何设置?

我现在应该做什么:

现在我需要一个顶点缓冲区,它的四顶点重复次数与我的实例一样多。

0, 0, // (1) top left
1, 0, // 
0, 1, // 
1, 1  // 
0, 0, // (2) top left
1, 0, // 
0, 1, // 
1, 1, // 
0, 0, // (3) top left
1, 0, // 
0, 1, // 
1, 1, // 
... etc

我的第二个缓冲区为每个顶点复制其数据:

x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
... etc.

这似乎真的很低效,我只想指定前 4 个顶点一次并让它继续以某种方式重用它们,而不是复制这 4 个顶点 N 次以在我的第一个缓冲区中总共有 4*N 个顶点。而且我只想为每个四边形指定一次 x,y,width,height,color 属性,总共 N 个顶点,而不是为每个整体顶点指定一次,总共 4*N 个顶点。

我该怎么办?

4

1 回答 1

2

一般来说,渲染一系列四边形最有效的方法是……渲染一系列四边形。您不发送宽度/高度或其他实例信息;您计算 CPU 上 4 个顶点的实际位置,然后使用适当的缓冲区对象流技术将它们写入 GPU 内存。具体来说,避免尝试只更改几个四边形;如果您的数据不是静态的,最好将所有数据重新上传(到不同/无效的缓冲区),而不是仅在原位修改几个字节。

您假设的替代方案只会在两种情况下表现更好:如果将数据写入 GPU 的带宽是您当前的瓶颈(无论是由于四边形还是您正在执行的其他传输),或者读取数据以进行渲染的带宽是否是当前的瓶颈。

您可以通过减小顶点数据的大小来缓解此问题。由于我们正在谈论 2D 四边形,因此您可以很好地使用短裤来表示每个顶点的 XY 位置。或 16 位浮点数。无论哪种方式,这意味着每个顶点(位置 + 颜色)只占用 8 个字节,这意味着一个四边形只有 32 个字节的数据。显然 12 字节小于 32(如果您使用类似的压缩,则 12 是每个实例的成本),但它仍然比完整float位置使用的 48 字节减少了 33%。


如果您已经完成了分析作业并确定 32-bytes-per-quad 太多了,那么顶点实例化仍然不是一个好主意。众所周知,在某些硬件上,极小的实例会影响您的 VS 性能。因此,应该避免。

在这种情况下,最好放弃所有顶点属性的使用(您的 VAO 应该禁用所有数组,并且您的 VS 应该没有in定义任何值)。相反,您应该直接从 SSBOs获取实例数据。

gl_VertexID输入值告诉您正在渲染的顶点索引。鉴于您正在渲染四边形,当前实例将为gl_VertexID / 4. 并且四边形内的当前顶点是gl_VertexID % 4。所以你的 VS 看起来像这样:

struct instance
{
  vec2 position;
  vec2 size;
  uint color; //Packed as 4 bytes; unpack with unpackUnorm4x8
  uint padding; //Padding needed due to alignment/stride of 8 bytes.
};

layout(binding = 0, std430) buffer instance_data
{
  instance instances[];
};

vec2[4] vertex_table =
{
  vec2{0, 0},
  vec2{1, 0},
  vec2{0, 1},
  vec2{1, 1},
};

void main()
{
    instance curr_instance = instances[gl_VertexID / 4];
    vec2 vertex = vertex_table[gl_VertexID % 4];

    vertex = curr_instance.position + (curr_instance.size * vertex);
    gl_Position = vec4(vertex.xy, 0.0, 1.0);
}

这种事情的速度将完全取决于您的 GPU 处理这些全局内存读取的能力。请注意,至少假设可以将每个实例数据的大小减少回 12。您可以分别使用unpackUnorm2x16unpackHalf2x16解包这些值,将位置和大小打包到两个 16 位短路或半浮点数中。如果你这样做,那么你的instance结构只有 3 个uint值,并且不需要填充。

于 2019-10-16T21:22:50.250 回答