Loading models from file
One of the biggest challenges while learning OpenGL, especially the new core profile, is the challenge of rendering interesting scenes when you don’t actually have anything interesting to render. The SuperBible is a good teaching text, but huge amounts of each example are typically taken up with simply creating the geometry to be rendered. And of course in the real world, that is something that programmers just don’t do – an artist creates the models, the coder writes routines to load them from file and render them.
So, I want a simple model loader that students can use to load in their data, and that will allow them to work more on more realistic projects. Sadly, most examples out there on the internet are rooted deeply in either platform specific code and/or, more commonly, heavy use of deprecated functions and features.
There are lots of 3D model formats out there, and maybe, just maybe, there is a really simple one that is ideal for teaching. But I don’t know about it. Collada has established itself as the defacto standard for 3D model interchange, and as part of the Khronos group of standards, is linked to OpenGL. But easy for beginners it isn’t – individual model files can consist of multiple meshes, as well as physics information.
If you wish to avoid having to write loaders altogether, then the Assimp (Open Asset Importer) library may be just what you are looking for – but it introduces a small number of additional dependencies.
For my teaching needs I’ve hacked together a custom mesh model format – currently calling it “uwsm” but that may change. The model format is currently as follows:
- ‘Magic Number’ string – not used in the code listed below, but updates to the model format start the file with a text string to identify the file type. Files should start with the string “uwsm” and a version number indicator. e.g. “uwsm0_2″.
- int i – the first value is an integer, this is the number of floating point values to follow. Should be divisible by three, as each set of three values is going to be one vertex
- floats v – i many floats, three for each vertex. These get loaded into an array of vertex data.
- int j – the next value is another integer, this is the number of floating point values to follow. Should be divisible by three, as each set of three values is going to be one normal (note: this will potentially allow the number of normals to differ from the number of vertices, as in Collada)
- floats n – j many floats, three for each normal. These also get loaded into an array – this time of normal data.
- int k – theĀ next value is another integer, this is the number of floating point values to follow. Should be divisible by two, as each set of two values is going to be one texture UV coordinate normal (again, this will potentially allow the number of texture coordinates to differ from the number of vertices, as in Collada. This allows a vertex to be shared between two faces each of which has a different texture.)
- floats t – k many floats, two for each texture coordinate. Guess what? These get loaded into an array of texture data!
- int m – another integer, this represents the number of triangles in the final model, each triangle will use have a position, a normal and (optionally) a texture coordinate.
- This bit subject to change! What follows currently is a list of 3 x m integers. This is an index array – if the first three values are 0, 1 and 3, then it means that the first triangle will be composed of vertex[0], vertex[1] and vertex[3]. Collada files may provide separate values for vertex, normal and texture indices – but simple ones use the same indices for all three. Currently, my format does this also. So the normals for the first triangle in this example will be normal[0], normal[1] and normal[3]
Phew! That was a fair bit of work. Anyway, the sample code to load and view a simple model is available – it isn’t really too complicated. Check it out here: http://gist.github.com/629763 (a model data file is available there also). This version uses GLTools – a version that only needs freeglut, GLEW and math3d should follow soon.
The other info you might want is how to create new models!
I created my model using Google Sketchup. I then exported it as a 3D model in the Collada format. Next up, I opened the file in a text editor (yes really!), and manually stripped out all the XML leaving just the integer values which tell the loader how many floats/triangles/whatever to be read in and the data itself.
What still needs to be done
- Store separate indices for normals and textures – possibly as an option
- Header data. A file format magic number, version number, and any values required to set options (e.g. whether or not the file uses different indices for normal and texture data!)
- Error checking. The current code assumes that the file is OK. It doesn’t check whether the amount of vertex data is divisible by three, the number of texture co-ordinates divisible by two (luckily, as the sample data file actually has 3 texture coordinates, not 2,… oops!), or even if the end of file is reached unexpectedly. This is a BIG failing for any software for real world use. Unexpected crashes can result.
- Textures. The texture information is currently there as a place-holder only. I haven’t tested this out at all yet.
Head over to http://gist.github.com/629763 to get the full code, but you can check out the guts of the model loading code itself here:
// entire file has been loaded into memblock
// going to throw this into a stringstream for reading
stringstream strstream;
strstream << memblock;
string magicnumber; // Basic check: is this a uwsm file?
strstream >> magicnumber;
if (magicnumber.compare(0,4,"uwsm")!=0)
{ // could also use the version number, but not doing that just now
cerr << "File " << fname << " is not a uwsm file." << endl;
exit(1);
}
int count; //count is number of floats, ie verts x 3
strstream >> count;
M3DVector3f *vertices = new M3DVector3f[count/3];
int i;
for (i=0;i<count/3;i++)
strstream >> vertices[i][0] >> vertices[i][1] >> vertices[i][2];
strstream >> count; // how many normals? need not be same as vertices
M3DVector3f *normals = new M3DVector3f[count/3];
for (i=0;i<count/3;i++)
strstream >> normals[i][0] >> normals[i][1] >> normals[i][2];
strstream >> count; // how many texture coords? need not be same as vertices
GLfloat nul;
for (i=0;i<count;i++)
strstream >> nul; // not using these just now!
int triangles;
strstream >> triangles; // how many *triangles* in mesh?
M3DVector3f *meshVerts = new M3DVector3f[triangles*3];
M3DVector3f *meshNormals = new M3DVector3f[triangles*3];
int n;
for (i=0;i<triangles*3;i++)
{
// commented out lines show how we *could* vary no. of normals & tex co-ords
// from number of vertices. Useful for e.g. when a vertex is shared
// by two faces with different textures on them
strstream >> n; // vertex index
m3dCopyVector3(meshVerts[i],vertices[n]);
//strstream >> n; // normal index
m3dCopyVector3(meshNormals[i],normals[n]);
//strstream >> n; // texture u-v index - ignore for now
}
object.Begin(GL_TRIANGLES, triangles*3, 0);
object.CopyVertexData3f(meshVerts);
object.CopyNormalDataf(meshNormals);
object.End();
delete [] memblock; // I sure did allocate a lot of arrays!
delete [] vertices; // could do more for less, I'm sure
delete [] normals;
delete [] meshVerts;
delete [] meshNormals;


Trackbacks and Pingbacks