提高OpenGL中的VBO性能

时间:2013-02-03 20:14:30

标签: c++ performance opengl 3d vbo

所以我现在正试图以合适的速度渲染复杂的模型,并遇到一些麻烦;渲染单个模型会导致我的帧速率变得紧张,而在程序中没有任何额外的工作。我的模型(场景中只有一个)看起来太大了。我上传到缓冲区的顶点数组中有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,或8ms /帧(仅此一点就足够了,但我需要能够在程序中做其他事情) ,其中一些可能是CPU密集型的)。具有85k三角形的更复杂的模型将其降低到50 FPS以下,或大约20ms /帧,此时程序明显地断断续续。

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

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渲染的性能?

更新:根据下面的评论,我只绘制了数组的一半,如下所示:

glDrawArrays(GL_TRIANGLES,0,m_ArraySize / 2);

然后是阵列的四分之一:

glDrawArrays(GL_TRIANGLES,0,m_ArraySize / 4);

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

3 个答案:

答案 0 :(得分:3)

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

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

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

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

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

    我只想补充一点,如果你使用索引三角形,你必须确保尽可能多地在三角形之间共享顶点数据以获得最佳性能。这真的是关于你如何组织数据。

答案 1 :(得分:3)

编辑:阅读部分评论;回复如下。


要尝试一些随机的事情。

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所提到的,索引三角形是个好主意,如果你能在顶点数据中保持良好的引用局部性。这可能需要重新编译或重新调整输入数据。

答案 2 :(得分:3)

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);

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