优化OpenGL中VBO的性能

4

我目前正在尝试以较快的速度渲染复杂模型,但遇到了一些问题。渲染单个模型会导致我的帧率过低,而程序中没有任何额外的工作。我的模型(场景中只有一个)似乎太大了。上传到缓冲区的顶点数组中有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();

这段代码让我的FPS保持在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个三角形的渲染。我宁愿现在这样做,而不是将来必须重写和重构更大量的代码。

我已启用背面剔除;我不确定视锥体剔除是否有所帮助,因为有时整个或大部分模型都在屏幕上(我目前舍弃对象,但不舍弃单个对象内的三角形)。在视口中裁剪不朝向摄像机的面可能会有所帮助,但我不确定如何做到这一点。除此之外,我不知道如何优化渲染。我还没有实现一个顶点缓冲区,但我已经读到说那可能只会增加速度约10%。

人们如何在屏幕上同时以可接受的帧率显示数万个三角形并进行其他操作?我该怎么样提高我的VBO渲染性能?更新:根据下面的评论,我将数组画了一半,如下所示:glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/2); 然后是数组的四分之一:glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/4); 每次减少绘制的数组量实际上使速度加倍(从12毫秒到6和3毫秒),但模型完好无损 - 没有任何丢失的部分。这似乎表明我在其他地方做错了什么,但我不知道是什么;我相当自信,在组成模型时不会添加相同的三角形4次或更多次,那么它可能是什么?也许我在多次上传缓冲区?

2
你使用的是什么硬件?你正在使用哪些着色器?你是否在使用着色器?你的模型具体是什么? - Nicol Bolas
2
每秒帧数是非常非线性的。请切换到每帧毫秒数。 - genpfault
1
@GarrickW 能否提供一个最小完整的示例来演示问题?我认为问题可能出现在代码的其他地方,因为我没有看到你发布的内容中有什么明显的错误。三角形数量这么低,却能达到50fps,听起来像是发生了一些意外的事情,比如GL错误没有被捕获或缓冲区溢出。如果只绘制数组的一半会发生什么(glDrawArrays(GL_TRIANGLES, 0, m_ArraySize/2);)? - Devin Lane
@Devin 哇,我刚刚用一个更小的模型测试了你的建议;绘制一半的数组实际上将速度提高了近一倍(在较小的模型上,绘制时间从3-4毫秒缩短到1-2毫秒,在较大的模型上从12毫秒缩短到6毫秒)。然而整个模型仍然被显示,并且像往常一样着色。这是什么意思? - GarrickW
@GarrickW 我在下面提供了一个答案,解释了这里发生了什么 - 你要求OpenGL绘制了6倍于实际数据的内容! - Devin Lane
显示剩余7条评论
3个回答

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

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


哇,我从来不知道那个。你真是救星!在没有任何额外优化的情况下,我的85k个三角形的绘制时间为1-2毫秒/500 FPS。这是我所希望的性能表现!再次感谢! - GarrickW

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 帧每秒?在 GTX660M 上,我认为你做得不错。我不指望在你运行的硬件上能够获得更高的性能。

至于固定功能管线,现在所有流行的技术都使用全可编程管线。但是 FF 不会降低性能 —— 在内部,驱动程序将 FF 状态编译成一组着色器,因此它仍然作为一个着色器在 GPU 上执行。

正如 @JamesSteele 提到的那样,如果你能保持顶点数据的良好引用局部性,则使用索引三角形是一个不错的选择。但这可能需要重新编译或调整输入数据。


2
我期望GTX660M的性能比这要好得多。我可以在类似的显卡上以200+FPS的速度渲染50万个三角形。 - Devin Lane
@Devin,令人非常愉悦的是,GTX 660M实际上还有很多其他优点。我目前正在调查这张卡的能力,并且到目前为止我能够以22ms - 23ms每帧的速度渲染约2M个三角形。尽管还在优化中,但想了解一下其他人的操作,看看我能否挤出更多性能 :) - Piotr Justyna

3
我认为问题在于你的模型中每个三角形都有自己的三个顶点。你没有使用索引三角形(GL_ELEMENT_ARRAY_BUFFER,glDrawElements),因此顶点数据可以在多个三角形之间共享。
据我所知,你当前的方法存在两个问题:
1.需要处理大量的数据(尽管索引三角形也可能存在此问题)。
2.当使用glDrawArrays()而不是glDrawElements时,GPU无法利用后变换缓存,该缓存用于减少顶点处理量。
如果可能的话,请重新安排你的数据以使用索引三角形。
我只想补充一点,如果你使用索引三角形,则必须尽可能地确保在三角形之间共享顶点数据,以获得最佳性能。这实际上取决于你如何组织数据。

当然,使用索引缓冲区以及共享顶点进行渲染应该会提高速度。我对Direct3D比OpenGL更有经验,但在D3D中建议尽可能使用索引缓冲区。由于底层硬件相同,这也适用于OpenGL。完全没注意到这一点! - Oguz Meteer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接