Home » Uncategorized » Lesson 9: Loading Wavefront OBJ 3D models

Lesson 9: Loading Wavefront OBJ 3D models

In this tutorial we will load Wavefront OBJ 3D models. These models are static models (e.g. the model itself does not have any movement definition). You can get a set of example models here : http://people.sc.fsu.edu/~jburkardt/data/obj/obj.html  Remember to triangulate them in Blender or another 3d editing program.

Every OBJ model consists of a point cloud. For example, a simple pyramid consists of 5 points. The more complicated your model is, the more points you will have. For example, an airplane point cloud could look like this:

Wavefront OBJ

Every point is defined in the vector space using their (x,y,z) coordinate.  The Wavefront OBJ format stores everything in plain text, and thus if you open an OBJ model in a text editor you will see a list like:

v -1.227048 -1.158217 1.417186
v -1.227048 -1.576351 1.426747
v -1.202536 -1.576423 1.426747
v -1.202536 -1.169531 1.417441
v 0.064052 -1.584108 1.403254
v 0.064052 -1.186619 1.394201

Where every line represents a vector in the vector space. Note that ‘v’ in front of the files which indicates a vector. Once you have loaded the vectors, you want to connect each of the faces. A face is a connection of multiple points, in the form of a triangle, quad etc. In our case we will only support triangles. If the concept of faces is still unclear to you, consider a cube has 6 faces (sides). Every face is defined on a single line and consists of several vectors, in our case exactly three. You will find a list like this in the file:

f 1 2 3
f 1 3 4
f 5 6 7
f 5 7 8
f 9 10 4
f 9 4 3
f 8 7 10
f 8 10 9

Note that the points indicate the index of the vector array you have stored earlier.  E.g. every point of the face consists of an x,y and z value.  Thus, every line consist of 9 numbers (3 for each coordinate).

Once we know all of the faces, we can display them and end up with something like this:

Wavefront OBJ Model

Finally, we need to load the normal vectors (e.g. orthagonal lines from the faces) to display the correct lighting. These can either be calculated from the faces we already have, or be loaded from the file. (vn is the indicator). We end up with this scene:

Wavefront OBJ Model OpenGL

In code this will look like this:

 

/*
 *
 * Demonstrates how to load and display an Wavefront OBJ file. 
 * Using triangles and normals as static object. No texture mapping.
 * https://talkera.org/opengl/
 *
 * OBJ files must be triangulated!!!
 * Non triangulated objects wont work!
 * You can use Blender to triangulate
 *
 */
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define KEY_ESCAPE 27
 
using namespace std;
 
/************************************************************************
  Window
 ************************************************************************/
 
typedef struct {
    int width;
        int height;
        char* title;
 
        float field_of_view_angle;
        float z_near;
        float z_far;
} glutWindow;
 
 
 
/*************************************************************************** 
  OBJ Loading 
 ***************************************************************************/
 
class Model_OBJ
{
  public: 
        Model_OBJ();                    
    float* Model_OBJ::calculateNormal(float* coord1,float* coord2,float* coord3 );
    int Model_OBJ::Load(char *filename);        // Loads the model
        void Model_OBJ::Draw();                                 // Draws the model on the screen
        void Model_OBJ::Release();                              // Release the model
 
        float* normals;                                                 // Stores the normals
    float* Faces_Triangles;                                     // Stores the triangles
        float* vertexBuffer;                                    // Stores the points which make the object
        long TotalConnectedPoints;                              // Stores the total number of connected verteces
        long TotalConnectedTriangles;                   // Stores the total number of connected triangles
 
};
 
 
#define POINTS_PER_VERTEX 3
#define TOTAL_FLOATS_IN_TRIANGLE 9
using namespace std;
 
Model_OBJ::Model_OBJ()
{
        this->TotalConnectedTriangles = 0; 
        this->TotalConnectedPoints = 0;
}
 
float* Model_OBJ::calculateNormal( float *coord1, float *coord2, float *coord3 )
{
   /* calculate Vector1 and Vector2 */
   float va[3], vb[3], vr[3], val;
   va[0] = coord1[0] - coord2[0];
   va[1] = coord1[1] - coord2[1];
   va[2] = coord1[2] - coord2[2];
 
   vb[0] = coord1[0] - coord3[0];
   vb[1] = coord1[1] - coord3[1];
   vb[2] = coord1[2] - coord3[2];
 
   /* cross product */
   vr[0] = va[1] * vb[2] - vb[1] * va[2];
   vr[1] = vb[0] * va[2] - va[0] * vb[2];
   vr[2] = va[0] * vb[1] - vb[0] * va[1];
 
   /* normalization factor */
   val = sqrt( vr[0]*vr[0] + vr[1]*vr[1] + vr[2]*vr[2] );
 
        float norm[3];
        norm[0] = vr[0]/val;
        norm[1] = vr[1]/val;
        norm[2] = vr[2]/val;
 
 
        return norm;
}
 
 
int Model_OBJ::Load(char* filename)
{
        string line;
        ifstream objFile (filename);    
        if (objFile.is_open())                                                                                                  // If obj file is open, continue
        {
                objFile.seekg (0, ios::end);                                                                            // Go to end of the file, 
                long fileSize = objFile.tellg();                                                                        // get file size
                objFile.seekg (0, ios::beg);                                                                            // we'll use this to register memory for our 3d model
 
                vertexBuffer = (float*) malloc (fileSize);                                                      // Allocate memory for the verteces
                Faces_Triangles = (float*) malloc(fileSize*sizeof(float));                      // Allocate memory for the triangles
                normals  = (float*) malloc(fileSize*sizeof(float));                                     // Allocate memory for the normals
 
                int triangle_index = 0;                                                                                         // Set triangle index to zero
                int normal_index = 0;                                                                                           // Set normal index to zero
 
                while (! objFile.eof() )                                                                                        // Start reading file data
                {               
                        getline (objFile,line);                                                                                 // Get line from file
 
                        if (line.c_str()[0] == 'v')                                                                             // The first character is a v: on this line is a vertex stored.
                        {
                                line[0] = ' ';                                                                                          // Set first character to 0. This will allow us to use sscanf
 
                                sscanf(line.c_str(),"%f %f %f ",                                                        // Read floats from the line: v X Y Z
                                        &vertexBuffer[TotalConnectedPoints],
                                        &vertexBuffer[TotalConnectedPoints+1], 
                                        &vertexBuffer[TotalConnectedPoints+2]);
 
                                TotalConnectedPoints += POINTS_PER_VERTEX;                                      // Add 3 to the total connected points
                        }
                        if (line.c_str()[0] == 'f')                                                                             // The first character is an 'f': on this line is a point stored
                        {
                        line[0] = ' ';                                                                                          // Set first character to 0. This will allow us to use sscanf
 
                                int vertexNumber[4] = { 0, 0, 0 };
                sscanf(line.c_str(),"%i%i%i",                                                           // Read integers from the line:  f 1 2 3
                                        &vertexNumber[0],                                                                           // First point of our triangle. This is an 
                                        &vertexNumber[1],                                                                           // pointer to our vertexBuffer list
                                        &vertexNumber[2] );                                                                         // each point represents an X,Y,Z.
 
                                vertexNumber[0] -= 1;                                                                           // OBJ file starts counting from 1
                                vertexNumber[1] -= 1;                                                                           // OBJ file starts counting from 1
                                vertexNumber[2] -= 1;                                                                           // OBJ file starts counting from 1
 
 
                                /********************************************************************
                                 * Create triangles (f 1 2 3) from points: (v X Y Z) (v X Y Z) (v X Y Z). 
                                 * The vertexBuffer contains all verteces
                                 * The triangles will be created using the verteces we read previously
                                 */
 
                                int tCounter = 0;
                                for (int i = 0; i calculateNormal( coord1, coord2, coord3 );
 
                                tCounter = 0;
                                for (int i = 0; i Faces_Triangles);
        free(this->normals);
        free(this->vertexBuffer);
}
 
void Model_OBJ::Draw()
{
        
glEnableClientState(GL_VERTEX_ARRAY);                                           // Enable vertex arrays
        glEnableClientState(GL_NORMAL_ARRAY);                                           // Enable normal arrays
        glVertexPointer(3,GL_FLOAT,     0,Faces_Triangles);                             // Vertex Pointer to triangle array
        glNormalPointer(GL_FLOAT, 0, normals);                                          // Normal pointer to normal array
        glDrawArrays(GL_TRIANGLES, 0, TotalConnectedTriangles);         // Draw the triangles
        glDisableClientState(GL_VERTEX_ARRAY);                                          // Disable vertex arrays
        glDisableClientState(GL_NORMAL_ARRAY);                                          // Disable normal arrays
}
 
/***************************************************************************
 * Program code
 ***************************************************************************/
 
Model_OBJ obj;
float g_rotation;
glutWindow win;
 
void display() 
{
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();
        gluLookAt( 0,1,20, 0,0,0, 0,1,0);
        glPushMatrix();
                glRotatef(45,0,1,0);
                glRotatef(90,0,1,0);
        //      g_rotation++;
                obj.Draw();
        glPopMatrix();
        glutSwapBuffers();
}
 
 
void initialize () 
{
    glMatrixMode(GL_PROJECTION);
        glViewport(0, 0, win.width, win.height);
        GLfloat aspect = (GLfloat) win.width / win.height;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
        gluPerspective(win.field_of_view_angle, aspect, win.z_near, win.z_far);
    glMatrixMode(GL_MODELVIEW);
    glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.1f, 0.0f, 0.5f );
    glClearDepth( 1.0f );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
 
    GLfloat amb_light[] = { 0.1, 0.1, 0.1, 1.0 };
    GLfloat diffuse[] = { 0.6, 0.6, 0.6, 1 };
    GLfloat specular[] = { 0.7, 0.7, 0.3, 1 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, amb_light );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
    glEnable( GL_LIGHT0 );
    glEnable( GL_COLOR_MATERIAL );
    glShadeModel( GL_SMOOTH );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glDepthFunc( GL_LEQUAL );
    glEnable( GL_DEPTH_TEST );
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0); 
}
 
 
void keyboard ( unsigned char key, int x, int y ) 
{
  switch ( key ) {
    case KEY_ESCAPE:        
      exit ( 0 );   
      break;      
    default:      
      break;
  }
}
 
int main(int argc, char **argv) 
{
        // set window values
        win.width = 640;
        win.height = 480;
        win.title = "OpenGL/GLUT OBJ Loader.";
        win.field_of_view_angle = 45;
        win.z_near = 1.0f;
        win.z_far = 500.0f;
 
        // initialize and run program
        glutInit(&argc, argv);                                      // GLUT initialization
        glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );  // Display Mode
        glutInitWindowSize(win.width,win.height);                                       // set window size
        glutCreateWindow(win.title);                                                            // create Window
        glutDisplayFunc(display);                                                                       // register Display Function
        glutIdleFunc( display );                                                                        // register Idle Function
    glutKeyboardFunc( keyboard );                                                               // register Keyboard Handler
        initialize();
        obj.Load("cessna.obj");
        glutMainLoop();                                                                                         // run GLUT mainloop
        return 0;
}

You can compile using:

g++ obj.c -o obj -lGLU -lGL -lglut -fpermissive

If you do not see any model, make sure you have the correct file path, and that the camera is in the right position. If the model looks deformed, you may have forgotten to triangulate the object.