I've been looking at the new OpenGL framework for iOS, aptly named GLKit, and have been playing around with porting some existing OpenGL 1.0 code to OpenGL ES 2.0 just to dip my toe in the water and get to grips with things.
After reading the API and a whole ream of other best practices provided by Apple and the OpenGL documentation, i've had it pretty much ingrained into me that I should be using Vertex Buffer Objects and using "elements" or rather, vertex indices. There seems to be a lot of mention of optimising memory storage by using padding where necessary too but that's a conversation for another day perhaps ;)
I read on SO a while ago about the benefits of using NSMutableData over classic malloc/free and wanted to try and take this approach when writing my VBO. So far i've managed to bungle together a snippet that looks like i'm heading down the right track but i'm not entirely sure about how much data a VBO should contain. Here's what i've got so far:
//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;
}
}
Currently, the interleaving vertex data contains enough to store the vertices, normals, colors and texture coordinates for each vertex. I was under the impression that there would be an equal number of vertices and indices but in practice this obviously isn't the case so for this reason, the indices are part of the VBO rather than the InterleavingVertexData.
Question Updated:
I've updated the code above after managing to wrangle it into a working state. Hopefully it will come in useful to someone in the future.
Now that i've managed to set everything up, i'm having trouble getting the expected results from rendering the content bound to the VBO. Here's the code i've got so far for loading my data into 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);
And the code for rendering everything:
//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);
Whilst my iPhone hasn't yet exploded with awesome graphics of unicorns shooting rainbows from their eyes, I haven't been able to render a simple shape in it's entirety without tearing my hair out.
From the rendering it looks as though only 1/3rd of each shape is being drawn, perhaps 1/2 depending on the viewing angle. It seems the culprit it the count parameter passed to glDrawElements as fiddling with this has differing results but I've read the documentation and checked the value over and over again and it does indeed expect the total number of indices (which is what i'm passing currently).
As I mentioned in my original question, i'm quite confused by VBO's currently or rather, confused by the implementation rather than the concept at least. If anyone would be so kind as to cast an eye over my implementation, that would be super awesome as i'm sure i've made a rookie error somewhere along the way but you know how it is when you stare at something for hours on end with no progress.
Thanks for reading!
A Vertex Array Object (VAO) is an OpenGL Object that stores all of the state needed to supply vertex data (with one minor exception noted below). It stores the format of the vertex data as well as the Buffer Objects (see below) providing the vertex data arrays.
A VBO is a buffer of memory which the gpu can access. That's all it is. A VAO is an object that stores vertex bindings. This means that when you call glVertexAttribPointer and friends to describe your vertex format that format information gets stored into the currently bound VAO.
A vertex consists of one or more attributes, such as the position, the color, the normal, or texture coordinates. An OpenGL ES 2.0 or 3.0 app is free to define its own attributes; each attribute in the vertex data corresponds to an attribute variable that acts as an input to the vertex shader.
glEnableVertexAttribArray enables the generic vertex attribute array specified by index . glDisableVertexAttribArray disables the generic vertex attribute array specified by index . By default, all client-side capabilities are disabled, including all generic vertex attribute arrays.
I think I see your problem.
You've got a struct, VertexIndices
which contains three indices, or the indices for one triangle. When you bind your IBO (Index Buffer Object, the buffer object containing your indices), you do this:
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (sizeof(VertexIndices) * vertexBuffer.totalIndices), self.vertexIndices, GL_STATIC_DRAW);
Which is fine. The size
parameter in glBufferData
is in bytes so you're multiplying sizeof(3 floats) by the number of groups of 3 floats that you have. Great.
But then when you actually call glDrawElements, you do this:
glDrawElements(GL_TRIANGLES, vertexBuffer.totalIndices, GL_UNSIGNED_INT, (void *)0);
However, the vertexBuffer.totalIndices
is equal to the number of VertexIndices
structs you've got, which is equal to the total number of indices / 3 (or total number of triangles). So you need to do one of the following:
glDrawElements(..., vertexBuffer.totalIndices * 3, ...);
vertexBuffer.totalIndices
should contain the actual total number of indices that you've got, not the total number of triangles you're rendering. You need to do one of these because right now totalIndices contains the total number VertexIndices
you've got, and each one has 3 indices. The right thing to do here is either rename totalIndices to totalTriangles, or keep track of the actual total number of indices somewhere.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With