【问题标题】:Improving VBO performance in OpenGL提高 OpenGL 中的 VBO 性能
【发布时间】:2013-02-03 20:14:29
【问题描述】:

所以我目前正在尝试以不错的速度渲染复杂的模型,但遇到了一些麻烦;渲染单个模型会导致我的帧速率变得紧张,而在程序中没有任何额外的工作。我的模型(场景中只有一个)似乎太大了。我上传到缓冲区的顶点数组中有 444384 个浮点数(因此模型中有 24688 个三角形)。

//Create vertex buffers
glGenBuffers(1, &m_Buffer);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);    
int SizeInBytes = m_ArraySize * 6 * sizeof(float);
glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_DYNAMIC_DRAW);

//Upload buffer data
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * VertArray.size(), &VertArray[0]);

我知道 VBO 的大小会产生影响,因为 A) 减小大小可以提高性能,并且 B) 注释掉渲染代码:

glPushMatrix();

//Translate
glTranslatef(m_Position.x, m_Position.y, m_Position.z);

glMultMatrixf(m_RotationMatrix);

//Bind buffers for vertex and index arrays
glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), 0);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 6 * sizeof(float), (void*)12);

//Draw
glDrawArrays(GL_TRIANGLES, 0, m_ArraySize);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

//Unbind the buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);

glPopMatrix();

给我留下大约 2000-2500 FPS,而取消注释此代码会使我下降到大约 130FPS,或 8 毫秒/帧(仅此一项就足够了,但我还需要能够在程序中执行其他操作,其中一些可能是 CPU 密集型的)。具有 85k 三角形的更复杂的模型将其降低到 50 FPS 以下,或大约 20 毫秒/帧,此时程序明显卡顿。

此时我使用的一对着色器非常少,我怀疑这就是问题所在。他们在这里,以防万一;首先是顶点着色器:

void main()
{
    vec3 normal, lightDir;
    vec4 diffuse;
    float NdotL;
    /* first transform the normal into eye space and normalize the result */

    normal = normalize(gl_NormalMatrix * gl_Normal);
    /* now normalize the light's direction. Note that according to the

    OpenGL specification, the light is stored in eye space. Also since
    we're talking about a directional light, the position field is actually
    direction */
    lightDir = normalize(vec3(gl_LightSource[0].position));
    /* compute the cos of the angle between the normal and lights direction.

    The light is directional so the direction is constant for every vertex.
    Since these two are normalized the cosine is the dot product. We also
    need to clamp the result to the [0,1] range. */
    NdotL = max(dot(normal, lightDir), 0.0);
    /* Compute the diffuse term */

    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    gl_FrontColor =  NdotL * diffuse;

    gl_Position = ftransform();
} 

片段着色器:

void main()
{
    gl_FragColor = gl_Color;
}

我正在使用GTX 660M 作为我的显卡运行程序。

现在据我所知,VBO 是在 OpenGL 中渲染大量多边形最快的方法,而且互联网似乎建议许多机器可以同时计算和显示数百万个多边形,所以我确信一定有是一种优化我相对微不足道的 27k 三角形的渲染的方法。我宁愿现在就这样做,也不愿将来重写和重组大量代码。

我启用了背面剔除;我不确定 fustrum 剔除会有所帮助,因为有时,所有或大部分模型都在屏幕上(我目前剔除对象,但不是单个对象中的三角形)。剔除视口中不面向相机的面可能会有所帮助,但我不知道该怎么做。除此之外,我不确定如何优化渲染。我还没有实现顶点缓冲区,但我读到这可能只会将速度提高 10% 左右。

人们如何以可接受的帧速率同时在屏幕上制作数万或数十万个三角形,同时进行其他操作?我可以做些什么来提高 VBO 渲染的性能?

更新:根据下面的cmets,我只画了一半的数组如下:

glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/2);

然后是四分之一的数组:

glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/4);

减少每次绘制的数组数量实际上使速度翻了一番(分别从 12 毫秒到 6 毫秒和 3 毫秒),但模型完全完好无损 - 没有任何遗漏。这似乎表明我在其他地方做错了什么,但我不知道是什么;我相当有信心在构建模型时不会添加 4 次以上相同的三角形,那还能是什么呢?我可能会以某种方式多次上传缓冲区吗?

【问题讨论】:

  • 您在什么硬件上运行?你用的是什么着色器?你甚至使用着色器吗?你的模型到底是什么?
  • 每秒帧数为terribly non-linear。切换到每帧毫秒数。
  • 具有 27k 个三角形的网格对于几乎任何相对现代的 GPU 来说都应该很容易。这当然取决于您在着色器中所做的事情。顺便说一句,我看到您正在使用 GL_DYNAMIC_DRAW 标志。您需要动态缓冲区吗?如果你不这样做,那么你可以将其更改为 GL_STATIC_DRAW,这样应该会更快。
  • @Nicol:我使用一对准系统的顶点和片段着色器,我在 GTX 660M 上运行。我已经更新了上面的帖子。
  • @GarrickW 是否可以制作一个最小的完整示例来演示该问题?我认为问题可能出在代码中的其他地方,因为我没有看到您发布的内容有任何严重错误。 50fps 与这么低的三角形计数听起来像是发生了一些意想不到的事情,比如没有被捕获的 GL 错误或缓冲区溢出。如果绘制了一半的数组会发生什么 (glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/2);)?

标签: c++ performance opengl 3d vbo


【解决方案1】:

glDrawArrays() 将要绘制的索引的数量作为其第三个参数。您正在传入交错顶点和法线数组中的浮点数,这是索引数的 6 倍。 GPU 滞后是因为您告诉它访问缓冲区边界之外的数据——现代 GPU 在发生这种情况时会触发故障,旧的 GPU 只会让您的系统崩溃:)

考虑以下交错数组:

vx0 vy0 vz0 nx0 ny0 nz0 vx1 vy1 vz1 nx1 ny1 nz1 vx2 vy2 vz2 nx2 ny2 nz2

这个数组包含三个顶点和三个法线(一个三角形)。绘制一个三角形需要三个顶点,因此您需要三个索引来选择它们。要绘制上述三角形,您可以使用:

glDrawArrays(GL_TRIANGLES, 0, 3);

属性的工作方式(顶点、法线、颜色、纹理等),单个索引从每个属性中选择一个值。如果您在上面的三角形中添加颜色属性,您仍将只使用 3 个索引。

【讨论】:

  • 哇,我从来没有新的。你是救生员!对于 85k 三角形,我得到 1-2 毫秒的绘制时间/500 FPS,没有任何额外的优化。这是我所希望的那种表现!再次感谢!
【解决方案2】:

我认为问题在于模型中的每个三角形都有自己的三个顶点。您没有使用索引三角形(GL_ELEMENT_ARRAY_BUFFER、glDrawElements),因此可以在三角形之间共享顶点数据。

据我所知,您当前的方法存在两个问题。

  1. 需要处理的大量数据(尽管 这也可能是索引三角形的问题)。

  2. 当使用 glDrawArrays() 而不是 glDrawElements 时,GPU 无法使用转换后缓存,该缓存用于减少 顶点处理量。

如果可能,请重新排列数据以使用索引三角形。

我要补充一点,如果您使用索引三角形,则必须确保尽可能多地在三角形之间共享顶点数据以获得最佳性能。这实际上与您组织数据的能力有关。

【讨论】:

  • 啊,当然,使用索引缓冲区和共享顶点进行渲染应该会提高速度。我在 Direct3D 方面比 OpenGL 更有经验,但在 D3D 中,建议尽可能使用索引缓冲区。由于底层硬件相同,因此它也适用于 OpenGL。完全错过了那个!
【解决方案3】:

edit:阅读一些cmets;回复如下。


一些随机的尝试。

glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_DYNAMIC_DRAW);

试试GL_STATIC_DRAW。在稳定状态下它可能无济于事(因为驱动程序应该注意到不需要重新上传,因为没有修改顶点缓冲区数据),但值得一试。

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

//Unbind the buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);

如果没有必要,不要在每次绘制后更改顶点缓冲区状态。它只是一个缓冲区,保持绑定。

   normal = normalize(gl_NormalMatrix * gl_Normal);
    /* now normalize the light's direction. Note that according to the

    OpenGL specification, the light is stored in eye space. Also since
    we're talking about a directional light, the position field is actually
    direction */
    lightDir = normalize(vec3(gl_LightSource[0].position));
    /* compute the cos of the angle between the normal and lights direction.

    The light is directional so the direction is constant for every vertex.
    Since these two are normalized the cosine is the dot product. We also
    need to clamp the result to the [0,1] range. */
    NdotL = max(dot(normal, lightDir), 0.0);

您实际上可以稍微优化一下并保存normalize()(因此是半昂贵的invsqrt)。请注意,对于向量 v1v2,以及标量 s1s2

dot(v1 * s1, v2 * s2) == s1 * s2 * dot(v1, v2);

因此,如果 v1v2 未归一化,您可以将它们的平方大小分解,将它们相乘,然后乘以组合的 invsqrt 一次以缩小它们的点积。


85k 三角形,大约 50 FPS?使用 GTX660M 我会说你做得很好。我不希望在您正在运行的硬件上获得更高的数字。

至于固定功能流水线——现在所有酷孩子都在使用完全可编程的流水线。但是 FF 不会失去你的性能——在内部,驱动程序会将 FF 状态编译成一组着色器,因此无论如何它都会作为着色器在 GPU 上执行。

正如@JamesSteele 所提到的,如果您可以在顶点数据中保持良好的参考位置,索引三角形是一个好主意。不过,这可能需要重新编译或以其他方式重新调整您的输入数据。

【讨论】:

  • 我希望 GTX660M 的性能比 GTX660M 高得多。我可以在类似的卡上以 200+FPS 的速度渲染 50 万个三角形。
  • @Devin 令人高兴的是,GTX 660M 实际上还有很多可供选择。我目前正在研究这张特定卡的功能,到目前为止,我能够以每帧 22 毫秒到 23 毫秒的速度渲染约 2M 的三角形。不过还在优化。想看看其他人是怎么做的,我能从中挤出更多的东西吗:)
猜你喜欢
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-22
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多