OpenGL ES交错顶点缓冲对象

11

我一直在研究iOS的新OpenGL框架,名为GLKit,并尝试将一些现有的OpenGL 1.0代码移植到OpenGL ES 2.0中,以便了解并掌握相关知识。

在阅读了苹果和OpenGL文档提供的API和最佳实践后,我深刻认识到应该使用顶点缓冲对象,并使用“元素”或者说顶点索引。此外,还有很多关于使用必要填充优化内存存储的提及,但这可能是另一个话题 ;)。

前段时间在SO上看到使用NSMutableData而不是经典的malloc/free的好处,并希望在编写VBO时采用这种方法。到目前为止,我已经成功地组合了一小段代码,看起来我正在正确的方向上努力,但我并不完全确定VBO应该包含多少数据。以下是我目前的代码:

//import headers
#import <GLKit/GLKit.h>

#pragma mark -
#pragma mark InterleavingVertexData

//vertex buffer object struct
struct InterleavingVertexData
{
    //vertices
    GLKVector3 vertices;

    //normals
    GLKVector3 normal;

    //color
    GLKVector4 color;

    //texture coordinates
    GLKVector2 texture;
};
typedef struct InterleavingVertexData InterleavingVertexData;

#pragma mark -
#pragma mark VertexIndices

//vertex indices struct
struct VertexIndices
{
    //vertex indices
    GLuint a;
    GLuint b;
    GLuint c;
};
typedef struct VertexIndices VertexIndices;

//create and return a vertex index with specified indices
static inline VertexIndices VertexIndicesMake(GLuint a, GLuint b, GLuint c)
{
    //declare vertex indices
    VertexIndices vertexIndices;

    //set indices
    vertexIndices.a = a;
    vertexIndices.b = b;
    vertexIndices.c = c;

    //return vertex indices
    return vertexIndices;
}

#pragma mark -
#pragma mark VertexBuffer

//vertex buffer struct
struct VertexBuffer
{
    //vertex data
    NSMutableData *vertexData;

    //vertex indices
    NSMutableData *indices;

    //total number of vertices
    NSUInteger totalVertices;

    //total number of indices
    NSUInteger totalIndices;
};
typedef struct VertexBuffer VertexBuffer;

//create and return a vertex buffer with allocated data
static inline VertexBuffer VertexBufferMake(NSUInteger totalVertices, NSUInteger totalIndices)
{
    //declare vertex buffer
    VertexBuffer vertexBuffer;

    //set vertices and indices count
    vertexBuffer.totalVertices = totalVertices;
    vertexBuffer.totalIndices = totalIndices;

    //set vertex data and indices
    vertexBuffer.vertexData = nil;
    vertexBuffer.indices = nil;

    //check vertices count
    if(totalVertices > 0)
    {
        //allocate data
        vertexBuffer.vertexData = [[NSMutableData alloc] initWithLength:(sizeof(InterleavingVertexData) * totalVertices)];
    }

    //check indices count
    if(totalIndices > 0)
    {
        //allocate data
        vertexBuffer.indices = [[NSMutableData alloc] initWithLength:(sizeof(VertexIndices) * totalIndices)];
    }

    //return vertex buffer
    return vertexBuffer;
}

//grow or shrink a vertex buffer
static inline void VertexBufferResize(VertexBuffer *vertexBuffer, NSUInteger totalVertices, NSUInteger totalIndices)
{
    //check adjusted vertices count
    if(vertexBuffer->totalVertices != totalVertices && totalVertices > 0)
    {
        //set vertices count
        vertexBuffer->totalVertices = totalVertices;

        //check data is valid
        if(vertexBuffer->vertexData)
        {
            //allocate data
            [vertexBuffer->vertexData setLength:(sizeof(InterleavingVertexData) * totalVertices)];
        }
        else
        {
            //allocate data
            vertexBuffer->vertexData = [[NSMutableData alloc] initWithLength:(sizeof(InterleavingVertexData) * totalVertices)];
        }
    }

    //check adjusted indices count
    if(vertexBuffer->totalIndices != totalIndices && totalIndices > 0)
    {
        //set indices count
        vertexBuffer->totalIndices = totalIndices;

        //check data is valid
        if(vertexBuffer->indices)
        {
            //allocate data
            [vertexBuffer->indices setLength:(sizeof(VertexIndices) * totalIndices)];
        }
        else
        {
            //allocate data
            vertexBuffer->indices = [[NSMutableData alloc] initWithLength:(sizeof(VertexIndices) * totalIndices)];
        }
    }
}

//release vertex buffer data
static inline void VertexBufferRelease(VertexBuffer *vertexBuffer)
{
    //set vertices and indices count
    vertexBuffer->totalVertices = 0;
    vertexBuffer->totalIndices = 0;

    //check vertices are valid
    if(vertexBuffer->vertexData)
    {
        //clean up
        [vertexBuffer->vertexData release];
        vertexBuffer->vertexData = nil;
    }

    //check indices are valid
    if(vertexBuffer->indices)
    {
        //clean up
        [vertexBuffer->indices release];
        vertexBuffer->indices = nil;
    }
}

目前,交错的顶点数据包含足够的信息来存储每个顶点的顶点、法线、颜色和纹理坐标。我原本以为顶点数和索引数相等,但实际上这并不是这样,所以索引是VBO的一部分,而不是交错的顶点数据。
问题更新:
在成功地将代码整理成工作状态后,我已经更新了上面的代码。希望它对将来的某些人有用。
现在,我设定好了所有东西,但在渲染绑定到VBO的内容时,我无法获得预期的结果。这是我目前为止加载我的数据到OpenGL的代码:
//generate buffers
glGenBuffers(2, buffers);

//bind vertices buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBufferData(GL_ARRAY_BUFFER, (sizeof(InterleavingVertexData) * vertexBuffer.totalVertices), self.vertexData, GL_STATIC_DRAW);

//bind indices buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (sizeof(VertexIndices) * vertexBuffer.totalIndices), self.vertexIndices, GL_STATIC_DRAW);

//reset buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

渲染所有内容的代码如下:

//enable required attributes
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribNormal);
glEnableVertexAttribArray(GLKVertexAttribColor);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);

//bind buffers
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);

//set shape attributes
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, vertices));
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, normal));
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, color));
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, texture));

//draw shape
glDrawElements(GL_TRIANGLES, vertexBuffer.totalIndices, GL_UNSIGNED_INT, (void *)0);

//reset buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

//disable atttributes
glDisableVertexAttribArray(GLKVertexAttribTexCoord0);
glDisableVertexAttribArray(GLKVertexAttribColor);
glDisableVertexAttribArray(GLKVertexAttribNormal);
glDisableVertexAttribArray(GLKVertexAttribPosition);

虽然我的iPhone还没有爆炸出独角兽从眼睛里射出彩虹的神奇图形,但我却无法完整地渲染一个简单的形状,这让我抓狂。从渲染结果来看,每个形状只有三分之一被绘制了,或者根据视角而定可能是一半。看起来罪魁祸首是传递给glDrawElements的计数参数,因为调整它会产生不同的结果,但我已经反复阅读了文档并检查了值,它确实期望总索引数(这就是我目前传递的值)。正如我在原始问题中提到的那样,我对VBO目前感到相当困惑,或者至少是对实现感到困惑,而不是概念。如果有人能够耐心地审查我的实现,那将非常棒,因为我确定我在某个地方犯了新手错误,但你知道当你盯着某些东西几个小时而没有任何进展时的感觉。谢谢阅读!

我猜没有人想发表意见。无论如何,我已经更新了代码,以防将来有人路过。 - Zack Brown
很抱歉,没有人回答你的问题(主要是因为那对我也非常有帮助...)。 - Kai Huppmann
我理解你对“实现而非概念”的困惑。昨天我也在同一主题上提出了一个问题(http://stackoverflow.com/questions/8430108/opengl-sending-vertex-pointers-or-generating-buffers),但我的问题比你的基础得多,所以我不认为那个答案能帮到你...你可以看一下。 - Kai Huppmann
下次你应该加上OpenGL标签,并且完全省略移动部分,只说“交错VBO初始化/渲染代码无法正常工作”,然后发布相关代码。这样会引起更多关注。人们因为认为自己无法理解而被Objective-C和iOS标签吓到了。 - Andrew Rasmussen
1个回答

4

我想我发现了您的问题。

您拥有一个结构体 VertexIndices,其中包含三个索引,即一个三角形的索引。当您绑定 IBO(Index Buffer Object,包含索引的缓冲对象)时,您需要执行以下操作:

glBufferData(GL_ELEMENT_ARRAY_BUFFER, (sizeof(VertexIndices) * vertexBuffer.totalIndices), self.vertexIndices, GL_STATIC_DRAW);

没问题。在glBufferData中,size参数以字节为单位,因此您将sizeof(3 floats)与您拥有的3个float组的数量相乘。太好了。

但是,当您实际调用glDrawElements时,您会这样做:

glDrawElements(GL_TRIANGLES, vertexBuffer.totalIndices, GL_UNSIGNED_INT, (void *)0);

然而,vertexBuffer.totalIndices等于您拥有的VertexIndices结构的数量,这等于索引总数/ 3(或三角形总数)。因此,您需要执行以下操作之一:
  1. 简单但愚蠢的修复方法:glDrawElements(..., vertexBuffer.totalIndices * 3,...);
  2. 适当但更费力的方法:vertexBuffer.totalIndices应包含您实际拥有的索引总数,而不是要渲染的三角形总数。
您需要执行其中之一,因为现在totalIndices包含您拥有的VertexIndices的总数,每个VertexIndices都有3个索引。在这里正确的做法要么将totalIndices重命名为totalTriangles,要么在某个地方跟踪实际的索引总数。

1
感谢您的回复!我刚尝试了快速且简单的修复方法,可以确认这有效 :) 我将修改代码以包括您提到的索引总数,因为那样会更有意义(而且看起来也更清爽)。再次感谢! - Zack Brown
@arasmussn,请问您能解释一下为什么在glDrawElements的最后一个参数中传递(void *)0吗? - kelin
@kelin:在这个例子中,我们将索引存储在缓冲对象(buffers[1])中。glDrawElements的最后一个参数被解释为偏移量,指向当前绑定到GL_ELEMENT_ARRAY_BUFFER的缓冲区。这类似于glVertexAttribPointer的工作方式。 - Andrew Rasmussen
@arasmussen,但是文档说应该有对索引数组的引用。我有误解吗? - kelin
你的链接无法使用。请查看此页面:https://www.opengl.org/wiki/GLAPI/glDrawElements。 - Andrew Rasmussen

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