Dominic Szablewski, @phoboslab
— Tuesday, July 29th 2008

Yuckfu Dev Diary #3 – Loading and Displaying 3D Models

After some hours of reading to figure out how exactly the memory management with alloc, dealloc, retain and release works with NSObjects in Objective-C (here’s a good article) and some more hours to get used to the funny syntax, I was finally able to do something with it.

To get my 3D model onto the iPhone, I had to find a data format that is easy to load and can be used directly in my Application without much modifications. I decided to export the model from Wings3D as Wavefront .OBJ file, which is a pretty straight forward ASCII format. I, however, didn’t want to go through the hassle of parsing ASCII data in C, so I wrote a small PHP script to transform this .obj file into a binary format that was ready to be used with OpenGL ES.

This is where the fun starts.

My model file format simply consists of an unsigned short specifying the total number of vertices in the file, followed by the actual vertex data. Every face of a model consists of exactly 3 vertices. Vertices are not shared between faces – this increases the file size a bit, compared to an indexed vertex format, but makes them easier to load. My vertex data is stored in the following format.

typedef struct TagVector3f {
    float x, y, z;
} Vector3f;

typedef struct TagVertex {
    Vector3f position;
    Vector3f normal;
    unsigned long color;
} Vertex;

This allowed me to load the complete file into memory in just two simple steps. I was also able to directly bind the loaded vertex data into a Vertex Buffer Object in the graphic memory and release my malloc’ed memory again:

FILE * fh = fopen([resourcePath UTF8String], "rb");

// Determine the number of vertices in this file
unsigned short numVertices;
fread(&numVertices, sizeof(short), 1, fh);

// Load the vertices into memory
Vertex * vertexData = malloc(sizeof(Vertex)*numVertices);
fread(vertexData, sizeof(Vertex), numVertices, fh);

fclose(fh);


// Bind data into VBO
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*numVertices, vertexData, GL_STATIC_DRAW);

free(vertexData);

I have no idea if binding the vertex data into a VBO, compared to just handing over an array of vertices in every draw call, makes any difference in drawing performance. The iPhone has no dedicated graphics memory – It just uses 24MB of it’s RAM for the graphics chip. So, binding the data into a VBO might not be necessary at all, but it worked just fine for me.

With the vertex data in a VBO and provided that GL_VERTEX_ARRAY, GL_NORMAL_ARRAY and GL_COLOR_ARRAY are enabled, I can now draw the complete model with just one call to glDrawArrays:

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexPointer(3, GL_FLOAT, sizeof(Vertex), 0);
glNormalPointer(GL_FLOAT, sizeof(Vertex), (void*)sizeof(Vector3f));
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), (void*)(sizeof(Vector3f)*2)); 

glDrawArrays(GL_TRIANGLES, 0, numVertices);

This is of course much faster than drawing each vertex separately – and OpenGL ES actually has no glVertex3f() function to do something like this. However, there’s still room for improvement: I didn’t care about organizing my vertices to be drawn in triangle strips, instead of single triangles. This could speed up things even more.

iPhone OpenGL Performance Test

A quick performance test on the iPhone with one light source and 30 of my spaceship models – about 12,000 polygons (420 per model. Yes, I’m bad at low-poly modeling) – ran with ~30 FPS. Remember, these are untextured polygons. The flat shading actually makes no difference performance wise, compared to Gouraud shading. Still, these results are not too bad at all. And certainly good enough for my purposes.

Time to program the game itself! I will probably build some more dummy models for the background and crates, build the game logic and then spend some days on “play testing”!

© 2024 Dominic Szablewski – Imprint – powered by Pagenode (3ms) – made with <3