computer science and mathematics

A preliminary Wavefront OBJ loader in C++

This post will examine the Wavefront OBJ file format and present a preliminary loader in C++. We will overlook the format's support for materials and focus purely on the geometry. Once our model has been loaded, an OpenGL Display List will be used to render the model. Below is a rendering of a dragon model available at The Stanford 3D Scanning Repository. In the OBJ file used for this render the vertex normals were not present. At run time, normals were evaluated at each face for lighting calculations yielding a flat shading.

This is a rendering of the dragon without normals supplied in the OBJ file. Normals were evaluate for each face at run time.

In the render below we had imported the OBJ file into Blender, applied a smooth rendering, and exported the smoothed model. We then had a normal for each vertex allowing us to interpolate the normals across the face yielding a smooth shading. One way this could be done in our loader would be to assign a normal to each vertex, then for each face that uses a vertex, add that face's normal to the vertex normal, and, finally, ensure the vertex normal has unit length.

This is a rendering of the dragon model after being passed through Blender. Normals were evaluated at the vertices for interpolation across the face at run time.

Below is a video capture of the smoothed dragon model at run time.

Your browser doesn't support the HTML5 video tag.

We will be parsing the OBJ file for vertices, normals, and faces. The texture coordinates and parameter vertices will also be parsed, but these will go unused for the time being.

A vertex definition will have the following forms where "v" is literal.

v x y z
v x y z w

Normals will have the following form where "vn" is literal.

vn i j k

Finally, faces will have one of the following forms where "f" is literal.

In the first case only vertices are supplied, in the second we have vertices and texture coordinates, in the third we have vertices and normals, and, lastly, we have vertices, texture coordinates, and normals.

Below we define a couple of structures. The first is for a vertex which we will use for the vertices and normals (in addition to the texture coordinates and the parameter vertices). We have added a normalize method to ensure our normal vectors have unit length. The minus operator has been overloaded and a cross method added for evaluating face normals when normals have not been specified in the OBJ file. The second structure is to store the vertices, normals, and texture coordinates for a face.

The constructor will attempt to open the OBJ file for reading and populate the vertices, normals, and faces. Afterward, it calls the compileList method to generate the OpenGL Display List and then releases the memory allocated for the vertices, normals, and faces.

The compileList method builds the OpenGL Display List. This method expects the faces to be either triangles or quadrilaterals. If normals were specified in the OBJ file, they are used here; otherwise, a normal is evaluated at each face.

Instantiating the object should occur after an OpenGL context has been created.

cObj obj("media/dragon_smooth.obj");

Rendering the object.

obj.render();

We have not implemented much in the way of error checking or materials. Additionally, the OBJ file format has support for curves and surfaces. Hopefully, we can get around to implementing some of these other features, but for now download the project and modify it.