使用glDrawElements我不明白

时间:2014-07-23 12:48:47

标签: opengl

我正在研究一个开源项目的源代码,他们使用了我不理解的glDrawElements函数。作为程序员,我对GL API很陌生,所以如果有人能告诉我它是如何工作的,我将不胜感激。

让我们从绘图部分开始。代码如下所示:

for (int i = 0; i < numObjs; i++) {

    glDrawElements(GL_TRIANGLES, vboIndexSize(i), GL_UNSIGNED_INT, (void*)(UPTR)vboIndexOffset(i));
}

vboIndiexSize(i)返回当前对象的索引数, vboIndexOffset 返回平面内存数组中的偏移量(以字节为单位),其中顶点数据和存储对象的索引。

我不明白的部分是(void *)(UPTR)vboIndexOffset(i))。我多次查看代码,函数vboIndexOffset返回一个int32,UPTR也将返回的值转换为int32。那么如何将int32转换为void *并期望它能够工作?但是,让我们假设我在那里犯了一个错误,它实际上返回了一个指向这个变量的指针。 glDrawElements调用的第四个参数是内存块中的字节偏移量。以下是数据实际存储在GPU上的方式:

int ofs = m_vertices.getSize();
for (int i = 0; i < numObj; i++)
{
    obj[i].ofsInVBO = ofs;
    obj[i].sizeInVBO = obj[i].indices->getSize() * 3;
    ofs += obj[i].indices->getNumBytes();
}

vbo.resizeDiscard(ofs);
memcpy(vbo.getMutablePtr(), vertices.getPtr(), vertices.getSize());
for (int i = 0; i < numObj; i++)
{
    memcpy(
        m_vbo.getMutablePtr(obj[i].ofsInVBO),
        obj[i].indices->getPtr(),
        obj[i].indices->getNumBytes());
}

所以他们所做的就是计算存储顶点数据所需的字节数,然后将存储我们想要绘制的所有对象的索引所需的字节数加到这个数字上。然后他们分配该大小的内存,并复制该内存中的数据:首先是顶点数据,然后是索引。其中一个是完成的,他们使用以下方法将其推送到GPU:

glGenBuffers(1, &glBuffer);
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
checkSize(size, sizeof(GLsizeiptr) * 8 - 1, "glBufferData");
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, data, GL_STATIC_DRAW);

有趣的是,它们将所有内容存储在GL_ARRAY_BUFFER中。它们从不将顶点数据存储在GL_ARRAY_BUFFER中,然后使用GL_ELEMENT_ARRAY_BUFFER存储索引。

但是要回到绘图完成的代码,他们首先要做通常的东西来声明顶点属性。对于每个属性:

glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, size, type, GL_FALSE, stride, pointer);

这是有道理的,只是标准的。然后是我已经提到的代码:

for (int i = 0; i < numObjs; i++) {

    glDrawElements(GL_TRIANGLES, vboIndexSize(i), GL_UNSIGNED_INT, (void*)(UPTR)vboIndexOffset(i));
}

所以问题是:即使(UPTR)实际上返回指向变量的指针(代码没有表明这一点,但我可能会弄错,这是一个大项目),我不知道可以使用GL_ARRAY_BUFFER使用相同的内存块存储所有顶点和索引数据,然后使用glDrawElements并使第4个参数成为此内存块中当前对象的此索引列表的第一个元素的偏移量。我认为你需要使用GL_ARRAY_BUFFER和GL_ELEMENT_BUFFER分别声明顶点数据和索引。我并不认为您可以使用GL_ARRAY_BUFFER一次性声明所有数据,并且无论如何都无法让它在我身边工作。

以前有人看过这个吗?我还没有机会让它工作,并想知道是否有人可能会告诉我是否有特定的东西我需要注意才能让它发挥作用。我测试了一个带有位置,法线和纹理坐标数据的简单三角形,因此我有8 * 3个浮点数用于顶点数据,我有一个3个整数的数组用于索引,0,1,2。然后我将所有内容复制到一个内存块,用这个初始化glBufferData,然后尝试用:

绘制三角形
int n = 96; // offset in bytes into the memory block, fist int in the index list
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void*)(&n));

它并没有崩溃,但我看不到三角形。

编辑:

添加似乎对我不起作用的代码(崩溃)。

float vertices[] = {
    0,  1, 0, // Vertex 1 (X, Y)
    2, -1, 0, // Vertex 2 (X, Y)
   -1, -1, 0, // Vertex 3 (X, Y)
    3,  1, 0,
};

U8 *ptr = (U8*)malloc(4 * 3 * sizeof(float) + 6 * sizeof(unsigned int));
memcpy(ptr, vertices, 4 * 3 * sizeof(float));
unsigned int indices[6] = { 0, 1, 2, 0, 3, 1 };
memcpy(ptr + 4 * 3 * sizeof(float), indices, 6 * sizeof(unsigned int));

glGenBuffers(1, &vbo);

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(float) + 6 * sizeof(unsigned int), ptr, GL_STATIC_DRAW);

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);

free(ptr);

然后说到:

glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);

// see stackoverflow.com/questions/8283714/what-is-the-result-of-null-int/
typedef void (*TFPTR_DrawElements)(GLenum, GLsizei, GLenum, uintptr_t);
TFPTR_DrawElements myGlDrawElements = (TFPTR_DrawElements)glDrawElements;

myGlDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, uintptr_t(4 * 3 * sizeof(float)));

这会导致应用崩溃。

请参阅下面的答案以获取解决方案

2 个答案:

答案 0 :(得分:1)

这是由于OpenGL重新使用固定功能管道调用。绑定GL_ARRAY_BUFFER VBO时,对glVertexAttribPointer的后续调用需要偏移进入VBO(以字节为单位),然后将其转换为(void *)GL_ARRAY_BUFFER绑定保持有效,直到另一个缓冲区被绑定,就像GL_ELEMENT_ARRAY_BUFFER绑定保持有效一样,直到另一个&#39;}&#39;缓冲区已绑定。

您可以使用Vertex Array Object封装缓冲区绑定和属性指针(偏移)状态。 您示例中的地址无效。使用以下内容投射偏移量(void *) n

答案 1 :(得分:0)

感谢您的回答。我想虽然(并在网上做了一些研究之后),

  • 首先你应该使用glGenVertexArray。现在看来这是OpenGL4.x的标准,所以不是在绘制几何图形之前调用glVertexAttribPointer,而是将数据推送到GPU缓冲区时创建VAO似乎是最佳做法。

  • 我(实际上)能够组合顶点数据和SAME缓冲区内的索引(GL_ARRAY_BUFFER),然后使用glDrawElements绘制基元(见下文)。无论如何,标准方法是将顶点数据分别推送到GL_ARRAY_BUFFER并将索引推送到GL_ELEMENT_ARRAY_BUFFER。因此,如果这是标准的做法,那么最好不要试图过于聪明并只使用这些功能。

示例:

glGenBuffers(1, &vbo);
// push the data using GL_ARRAY_BUFFER
glGenBuffers(1, &vio);
// push the indices using GL_ELEMENT_ARRAY_BUFFER
...
glGenVertexArrays(1, &vao);
// do calls to glVertexAttribPointer
...

如果我错了,请纠正我,但这似乎是正确的(也是唯一的)方式。

编辑:

但是,只要在调用glDrawElements之前完成对glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,vbo)的调用,就可以将顶点数据和索引一起“打包”到ARRAY_BUFFER中。

工作代码(与原始帖子中的代码相比):

float vertices[] = {
    0,  1, 0, // Vertex 1 (X, Y)
    2, -1, 0, // Vertex 2 (X, Y)
   -1, -1, 0, // Vertex 3 (X, Y)
    3,  1, 0,
};

U8 *ptr = (U8*)malloc(4 * 3 * sizeof(float) + 6 * sizeof(unsigned int));
memcpy(ptr, vertices, 4 * 3 * sizeof(float));
unsigned int indices[6] = { 0, 1, 2, 0, 3, 1 };
memcpy(ptr + 4 * 3 * sizeof(float), indices, 6 * sizeof(unsigned int));

glGenBuffers(1, &vbo);

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(float) + 6 * sizeof(unsigned int), ptr, GL_STATIC_DRAW);

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);

free(ptr);

然后说到:

glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo); // << THIS IS ACTUALLY NOT NECESSARY

// VVVV THIS WILL MAKE IT WORK VVVV

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo);

// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// see stackoverflow.com/questions/8283714/what-is-the-result-of-null-int/
typedef void (*TFPTR_DrawElements)(GLenum, GLsizei, GLenum, uintptr_t);
TFPTR_DrawElements myGlDrawElements = (TFPTR_DrawElements)glDrawElements;

myGlDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, uintptr_t(4 * 3 * sizeof(float)));