Loading 3D files from OpenGL – OpenGL Tutorial 7 by Osama Hosam

Tutorial 7: Loading 3D files from OpenGL


When you work with OpenGL you will find the environment without modeling tools. Actually, OpenGL was not designed basically for creating models; instead it is for animation and rendering. However you still able to create models with OpenGL but this will lead to long time developing and wasting time which can be reduced by other techniques. One of these techniques – which is the subject of this tutorial- is to create the model with modeling software like 3D max and load it to OpenGL.

3D Loading concept

Models will be created in 3D Max and then saved with the extension .3DS This extension defines a type of files with a specific structure. Our objective is to get into this file and extract two types of information

  • Vertices
  • Polygons

The models will be defined by triangular mesh. The basic polygon of the mesh is a triangular which has three vertices. See Fig.1 to understand this concept

Fig.1 Dolphin represented with triangular mesh

In the figure, the Dolphin is represented entirely by a collection of rectangles. Each rectangle has three vertices (x, y, z) our objective is to read the rectangle collection from the file and draw it in OpenGL

3DS file structure

To read the 3DS files you need to understand the structure of that type of files. The files contain “Chunks”. Every chunk is defined by a hexadecimal number. Each chunk type is different from the other, the differentiation is done by the hexadecimal number or the ID of the chunk, the following are the list for the chunks available:

  • Chunk ID: 4d4d Description: Main chunk, contains all the other chunks
  • Chunk ID: 3d3d Description: 3D Editor chunk, objects layout info
  • Chunk ID: 4000 Description: Object block, info for each object
  • Chunk ID: 4100 Description: Triangular mesh, contains chunks for 3d mesh info
  • Chunk ID: 4110 Description: Vertices list
  • Chunk ID: 4120 Description: Polygons (faces) list

The last two types are the most important chunks, since they hold the whole structure information of our model. However there are many other types of chunks which are not listed here, like the chunk represents the model shading, lights and materials. We removed them from this tutorial for the sake of simplicity.

The program structure

We have implemented a class called CThreeMaxLoader, the class contains the following structures and global constants which are implemented into the same file assembly “ThreeMaxLoader.h”

#define MAX_VERTICES 80000 // Max number of vertices (for each object)
#define MAX_POLYGONS 80000 // Max number of polygons (for each object)

The above two variables put the limit of the number of vertices to 80000 and for the number of polygons to 80000, extra vertices or polygons will not be considered, if you have a huge model you need to change the above two variables.

// Our vertex type
typedef struct{
    float x,y,z;

The vertex_type holds the vertex coordinates, since we will work in the 3D environment we need to define x, y, and z.

// The polygon (triangle), 3 numbers that aim 3 vertices
typedef struct{
    int a,b,c;

The polygon_type is basically a triangle with three vertices, a, b, and c.

// The object type
typedef struct {
	char name[20];
	int vertices_qty;
    int polygons_qty;

    vertex_type vertex[MAX_VERTICES]; 
    polygon_type polygon[MAX_POLYGONS];

} obj_type, *obj_type_ptr;

Name, holds the model name. This will be extracted from the file. Also we defined a collection of vertices and polygons which will be extracted from the file. Obj_type and *obj_type_ptr are the model and its pointer after loading them from the file to the memory.

CThreeMaxLoader contains the following function which responsible for loading the 3DS file:

static char CThreeMaxLoader::Load3DS (obj_type_ptr p_object, char *p_filename)
	int i; //Index variable
	FILE *l_file; //File pointer
	unsigned short l_chunk_id; //Chunk identifier
	unsigned int l_chunk_lenght; //Chunk lenght

	unsigned char l_char; //Char variable
	unsigned short l_qty; //Number of elements in each chunk

	unsigned short l_face_flags; //Flag that stores some face information

	if ((l_file=fopen (p_filename, "rb"))== NULL) return 0; //Open the file

	while (ftell (l_file) < filelength (fileno (l_file)))		{
		fread (&l_chunk_id, 2, 1, l_file); //Read the chunk header
		//printf("ChunkID: %xn",l_chunk_id); 
		fread (&l_chunk_lenght, 4, 1, l_file); //Read the lenght of the chunk
		//printf("ChunkLenght: %xn",l_chunk_lenght);

		switch (l_chunk_id)
			//----------------- MAIN3DS -----------------
			// Description: Main chunk, contains all the other chunks
			// Chunk ID: 4d4d 
			// Chunk Lenght: 0 + sub chunks
			case 0x4d4d: 

			//----------------- EDIT3DS -----------------
			// Description: 3D Editor chunk, objects layout info 
			// Chunk ID: 3d3d (hex)
			// Chunk Lenght: 0 + sub chunks
			case 0x3d3d:
			//--------------- EDIT_OBJECT ---------------
			// Description: Object block, info for each object
			// Chunk ID: 4000 (hex)
			// Chunk Lenght: len(object name) + sub chunks
			case 0x4000: 
					fread (&l_char, 1, 1, l_file);
				}while(l_char != '' && i<20);

			//--------------- OBJ_TRIMESH ---------------
			// Description: Triangular mesh, chunks for 3d mesh info
			// Chunk ID: 4100 (hex)
			// Chunk Lenght: 0 + sub chunks
			case 0x4100:
			//--------------- TRI_VERTEXL ---------------
			// Description: Vertices list
			// Chunk ID: 4110 (hex)
			// Chunk Lenght: 1 x unsigned short (number of vertices) 
			//             + 3 x float x (number of vertices)
			//             + sub chunks
			case 0x4110: 
				fread (&l_qty, sizeof (unsigned short), 1, l_file);
				p_object->vertices_qty = l_qty;
				//printf("Number of vertices: %dn",l_qty);
				for (i=0; ivertex[i].x, sizeof(float), 1, l_file);
					//printf("Vertices list x: %fn",p_object->vertex[i].x);
					fread (&p_object->vertex[i].y, sizeof(float), 1, l_file);
					//printf("Vertices list y: %fn",p_object->vertex[i].y);
					fread (&p_object->vertex[i].z, sizeof(float), 1, l_file);
					//printf("Vertices list z: %fn",p_object->vertex[i].z);
					//Insert into the database


			//--------------- TRI_FACEL1 ----------------
			// Description: Polygons (faces) list
			// Chunk ID: 4120 (hex)
			// Chunk Lenght: 1 x unsigned short (number of polygons) 
			//             + 3 x unsigned short (polygon points) x (number of polygons)
			//             + sub chunks
			case 0x4120:
				fread (&l_qty, sizeof (unsigned short), 1, l_file);
				p_object->polygons_qty = l_qty;
				//printf("Number of polygons: %dn",l_qty); 
				for (i=0; ipolygon[i].a, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point a: %dn",p_object->polygon[i].a);
					fread (&p_object->polygon[i].b, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point b: %dn",p_object->polygon[i].b);
					fread (&p_object->polygon[i].c, sizeof (unsigned short), 1, l_file);
					//printf("Polygon point c: %dn",p_object->polygon[i].c);
					fread (&l_face_flags, sizeof (unsigned short), 1, l_file);
					//printf("Face flags: %xn",l_face_flags);

			//------------- TRI_MAPPINGCOORS ------------
			// Description: Vertices list
			// Chunk ID: 4140 (hex)
			// Chunk Lenght: 1 x unsigned short (number of mapping points) 
			//             + 2 x float (mapping coordinates) x (number of mapping points)
			//             + sub chunks
			//----------- Skip unknow chunks ------------
			//We need to skip all the chunks that currently we don't use
			//We use the chunk lenght information to set the file pointer
			//to the same level next chunk
				 fseek(l_file, l_chunk_lenght-6, SEEK_CUR);
	fclose (l_file); // Closes the file stream

	return (1); // Returns ok

The function is a static member of CThreeMaxLoader class, so it can be called directly without instantiating an object from the class.

The function simply loops through all the available chunks and check for the type of chunks needed. Then it saves the vertices and polygons in the object referred by an obj_type_ptr .

Loading the object to OpenGL

In OpenGL we have used the function in the previous section to load a file named “chesspawn.3ds” to OpenGL. First we defined rotating variables which will be used to rotate the object the variables are

float rotation_x=0; float rotation_x_increment=0.06f;
float rotation_y=0; float rotation_y_increment=0.1f;
float rotation_z=0; float rotation_z_increment=0.03f;

Then we defined a variable of type obj_type to hold the vertices and polygon of our vertices

obj_type object;

In the init() function we called the Load3DS function to load the file “chesspawn.3ds” to the object variables. Here is the full implementation of the init() function

void init()
	glClearColor(0.0, 0.0, 0.0, 0.0); // This clear the background color to black
	glShadeModel(GL_SMOOTH); // Type of shading for the polygons

		 // Projection transformation
	glMatrixMode(GL_PROJECTION); // Specifies which matrix stack is the target for matrix operations 
	glLoadIdentity(); // We initialize the projection matrix as identity

	glEnable(GL_DEPTH_TEST); // We enable the depth test (also called z buffer)
	glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); 

Notice how we called the function Load3DS directly without taking an instance of the class CThreeMaxLoader. This is because Load3DS is defined as static member function.

We have loaded successfully the model from the file to the “object” variable. We need to go through all the vertices in the “object” variable to draw the polygons and the entire model. This will be done in the render function, see the following code segment

void render()
	int l_index;

	glMatrixMode(GL_MODELVIEW); // Modeling transformation

// Rotations of the object (the model matrix is multiplied by the rotation matrices)

		// TODO: Add your message handler code here
	rotation_x = rotation_x + rotation_x_increment;
    rotation_y = rotation_y + rotation_y_increment;
    rotation_z = rotation_z + rotation_z_increment;

    if (rotation_x > 359) rotation_x = 0;
    if (rotation_y > 359) rotation_y = 0;
    if (rotation_z > 359) rotation_z = 0;

// glBegin and glEnd delimit the vertices that define a primitive (in our case triangles)
    for (l_index=0;l_index < object.polygons_qty;l_index++)
        //----------------- FIRST VERTEX -----------------
        // Coordinates of the first vertex
        glVertex3f( object.vertex[ object.polygon[l_index].a ].x,
                    object.vertex[ object.polygon[l_index].a ].y,
                    object.vertex[ object.polygon[l_index].a ].z); 
		//Vertex definition

        //----------------- SECOND VERTEX -----------------
        // Coordinates of the second vertex
		//float x= object.vertex[ object.polygon[l_index].b ].x;

        glVertex3f( object.vertex[ object.polygon[l_index].b ].x,
                    object.vertex[ object.polygon[l_index].b ].y,
                    object.vertex[ object.polygon[l_index].b ].z);
        //----------------- THIRD VERTEX -----------------
        // Coordinates of the Third vertex
        glVertex3f( object.vertex[ object.polygon[l_index].c ].x,
                    object.vertex[ object.polygon[l_index].c ].y,
                    object.vertex[ object.polygon[l_index].c ].z);

First we rotated the model by using the rotation variables, it is self explanatory. Then we loop through the entire model to get the detailed vertices and use the 3 coordinates to draw a Triangle. The loop will load the entire vertices; OpenGL will connect every consecutive three vertices together to form a triangle. This will be done until the entire model is finished. The resulting model is shown in Fig.2

Fig. 2 Chesspawn object made by 3D Max and loaded to OpenGL

Source Code

To download a sample code of the above tutorial click here