如何在OpenGL中使用顶点缓冲对象和顶点数组?

3

我正在使用顶点数组(Vertex Array)快速地绘制许多三角形:

void initializeGL() { 
   ...
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_COLOR_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);
   glColorPointer(3,  GL_FLOAT, 0,   colors);
}

void paintGL() {
   ...  
   glDrawElements(GL_TRIANGLES, 3*numTriangles, GL_UNSIGNED_INT, indices);
 }

然而,我希望通过使用顶点缓冲对象(VBO)来进一步加快渲染速度。

我的理解是否正确:glVertexPointer()告诉GPU从CPU哪里获取顶点数据,然后GPU在每个paintGL()中从CPU的这个位置复制它?

使用VBO会改善这种情况,因为它只会将顶点数据写入GPU一次,是这样吗?

由于我正在使用Qt,所以尝试使用QGLBuffer类:

void GLWidget::initializeGL() {
   ... 
   vertexBuffer = new QGLBuffer(QGLBuffer::VertexBuffer);
   vertexBuffer->create();
   vertexBuffer->bind();
   vertexBuffer->setUsagePattern(QGLBuffer::StaticDraw);
   vertexBuffer->allocate(vertices, 3*numVertices*sizeof(float)); // copies vertices to GPU? 
   vertexBuffer->release();

   #define BUFFER_OFFSET(bytes) ((GLubyte*) NULL + (bytes)) 
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_COLOR_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
   glColorPointer(3, GL_FLOAT, 0,  colors);
}  

当我运行这个程序时,它会在崩溃前长时间卡住:-(。如果我注释掉vertexBuffer->release();这一行,则根本不显示任何内容,但不会崩溃。

我做错了什么呢?

另外:我怎样才能将我的颜色一次性发送到GPU上?QGLBuffer::ColorBuffer类型不存在!?

编辑: 我在项目中包含了GLee.[h/c]文件,并用以下代码替换了QGLBuffer调用:

unsigned int vbufferid;
glGenBuffers(1, &vbufferid);  
glBindBuffer(GL_ARRAY_BUFFER, vbufferid); 
int size = 3*numVertices*sizeof(float);
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);

但是代码仍然没有绘制任何内容!?

glGenBuffers调用将vbufferid分配为值1,因此我认为问题不在于我的显卡。

编辑2:我发现如果注释掉“glEnableClientState(GL_COLOR_ARRAY);”行,三角形将使用VBO显示(但是没有颜色)!

那么在使用VBO进行顶点绘制时,如何将颜色传输到GPU?

没有GL_COLOR_BUFFER类型!?


如果你认为在处理VBO时需要使用glBufferData函数,那么glXYZPointer方法就不会被使用。但是我可能在这里弄错了。但我非常确定你需要glBufferData。你的片段着色器和顶点着色器是什么样子的?你确定问题不在那里吗? - dowhilefor
@dowhilefor:您仍然必须告诉OpenGL在缓冲区中查找顶点数据的位置,以及每个元素的大小。因此,是的,您仍然需要使用gl...Pointer函数。 - datenwolf
@dowhilefor:感谢你的帮助,但我认为你错了。如果我注释掉glVertexPointer调用,程序会崩溃。而且我的代码与《OpenGL编程指南》第104页的示例非常相似(该示例也使用glVertexPointer)。 - Andy
哦,好的,那就不要理我 :) 我已经很久没有使用OpenGL了。 - dowhilefor
看我的答案。颜色只是另一个顶点属性。它们也放入GL_ARRAY_BUFFER中。 - datenwolf
顺便说一句:GL_VERTEX_BUFFER类型也不存在。它被称为GL_ARRAY_BUFFER是有原因的。 - datenwolf
1个回答

1
我理解得对吗,glVertexPointer() 告诉 GPU 在 CPU 中可以获取顶点数据的位置,然后 GPU 会在每个 paintGL() 中从这个位置复制数据?
使用 VBO 可以通过仅将顶点数据写入 GPU 一次来改善这种情况,对吗?
大致如此,但实际执行(您不必关心)有些棘手。
您犯了一个常见的误解:OpenGL 没有“初始化”。当然,您可以在初始阶段上传某些资源到 OpenGL,但作为状态机,OpenGL 需要在您需要它之前处于所需状态。
然后是您真正的问题:您的顶点位置在缓冲对象中。但是您的颜色不在其中。但是您正在给 OpenGL 一个指针,它将其误解为顶点缓冲区中的偏移量。
因此,我建议您将以下行移到 glDrawElements 调用之前:
#define BUFFER_OFFSET(bytes) ((GLubyte*) NULL + (bytes)) 
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, vbufferid);
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));

glBindBuffer(GL_ARRAY_BUFFER, 0); // or put the colors into a VBO as well
glColorPointer(3, GL_FLOAT, 0,  colors);

glDrawElements(…

请注意,在调用glVertexPointer和颜色指针之前,必须显式绑定缓冲区,并在之后解除绑定。
但是,将一些属性放入VBO中,而另一些不放入,则达不到预期的效果。因此,我建议您将所有顶点属性移动到VBO中,最好是单个VBO,可以一个接一个地排列(步幅为0,它们之间的偏移量为偏移量),或者交错排列(步幅=整个属性向量的大小,偏移量为第一个属性向量的偏移量)。

更新代码示例:

上传到VBO

unsigned int vbufferid;
glGenBuffers(1, &vbufferid);  
glBindBuffer(GL_ARRAY_BUFFER, vbufferid); 

int vertexbufsize = (3)*numVertices*sizeof(float);
int colorbufsize = (3)*numVertices*sizeof(float);
glBufferData(GL_ARRAY_BUFFER, vertexbufsize + colorbufsize, NULL, GL_STATIC_DRAW); // data=NULL : initialize, but don't copy

glBufferSubData(GL_ARRAY_BUFFER, 0, vertexbufsize, vertices);
glBufferSubData(GL_ARRAY_BUFFER, vertexbufsize, colorbufsize, vertices);

使用组合VBO绘图

#define BUFFER_OFFSET(bytes) ((GLubyte*) NULL + (bytes)) 
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, vbufferid);
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));
glColorPointer(3, GL_FLOAT, 0,  BUFFER_OFFSET(vertexbufsize));

glDrawElements(…

谢谢您的帮助,datenwolf!那个方法很有效 :-). 我不需要将所有的enable/bind/xyzPointer代码移到我的paintGL()函数中。对我来说,即使只在initializeGL()函数中运行一次,它也能工作。但是每次重绘都运行这些代码的代价可能不会很大吧?它们只是告诉OpenGL数据在哪里,而不是从CPU复制任何数据到GPU。 - Andy
我的800万个三角形现在以良好的FPS旋转 :-). 你认为下一个优化应该是什么?也许使用VOB来进行索引?或者在顶点着色器中计算颜色? - Andy
@Andy:是的,索引数组会是下一步。缓冲类型为GL_INDEX_BUFFER。然后我建议交错顶点位置和颜色属性。现在的布局是PPP…PCCC…C。通过交错它们,即PCPCPC…PC,可以获得更好的内存局部性。 - datenwolf

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