【问题标题】:How to reuse vertices across primitives in OpenGL如何在 OpenGL 中跨图元重用顶点
【发布时间】:2020-02-13 14:40:52
【问题描述】:

我在 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 个顶点,not 为每个整体顶点指定一次,总共 4*N 个顶点.

我该怎么办?

【问题讨论】:

  • 相对于以正常方式做事正在解决什么问题?
  • @NicolBolas 正常的方法是什么?我要解决的问题是每帧只更新 N 个顶点,而不是每帧 4*N 个顶点。
  • 是的,正常的方法是每帧更新 4*N 个顶点。我不是指你的“x,y,w,h”的东西;我的意思是每个顶点的标准“位置+颜色”。我的问题是为什么你觉得这是错误的?你想通过这种方式而不是预期的方式来解决什么问题?
  • 听起来你正在使用或想要使用实例化。在这种情况下,只有四个零或一顶点的第一个顶点缓冲区用于非实例化顶点属性,第二个顶点缓冲区用于实例化顶点属性(通过 glVertexAttribDivisor),按实例推进。为此,您需要一个顶点着色器,它执行gl_Position = pos * widthAndHeight + xAndY,其中pos 是由第一个顶点缓冲区提供的非实例化属性,xAndYwidthAndHeight 是由第二个顶点缓冲区提供的实例化属性.
  • @NicolBolas 如果我想做十二面体而不是矩形,它会更相关吗? ...如果我绘制的形状中有 100 多个顶点来代替四边形?那么这种技术有意义吗?只是因为四边形是如此简单,以至于您无法接受我这样做的动机吗?在我看来,每帧只上传 N 个顶点比每帧上传 N*M 个顶点更好。我只需要知道技术:如何告诉 OpenGL 缓冲区是如何设置的。

标签: c++ opengl egl nvidia-jetson nvidia-jetson-nano


【解决方案1】:

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

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

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


如果您已完成分析作业并确定 32-bytes-per-quad 太多了,那么顶点实例化仍然不是一个好主意。众所周知,在某些硬件上,extremely small instances can kill your VS performance。因此,应该避免。

在这种情况下,最好放弃所有顶点属性的使用(您的 VAO 应该禁用所有数组,并且您的 VS 应该没有定义 in 值)。相反,您应该获取实例数据directly from 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 值,并且不需要填充。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多