CHAPTER 5
Hand coding models is not difficult if the models are very simple, like the triangle from the previous chapter. Using this technique to create complex models is not practical. It is far better to create models using a 3-D modeling application. The examples in this book were created using Blender 2.66, which is an open source 3-D modeling application available from http://www.blender.org/. Models can be created using Blender, or many other 3-D modelers, and exported to various 3-D file formats. The files can then be loaded into our applications. This allows for much more complex models since visually creating models is far simpler than working with the vertices themselves.
There are many 3-D file formats, each having advantages and disadvantages over the others. In this chapter, we will examine a simple but flexible format that is fairly universal and standardized. It is called the object file format, and it was originally developed by Wavefront Technologies. Object files have an “obj” file extension. We will write a class that parses object files and creates meshes from the vertex information in them.
Note: A mesh is a collection of triangles that forms a 3-D model. Mesh and model are interchangeable terms in the current context. In past versions of DirectX there was a mesh interface, but DirectX 11 does not contain this interface, so meshes must be loaded and manipulated manually. This is a little extra work, but it is a lot more flexible.
There are several advantages to the object file format. It is plain text and easy to read and parse. The files are human readable and writable; they are not binary. Human readable plaint text requires more storage space and is generally slower for the computer to parse than binary formats. For instance, the value 3.14159f takes 8 bytes in plaint text, but only 4 bytes in binary as a 32 bit float. The size of the data is negligible for the small models we will be working with, as is the time the CPU needs to parse the text into variables. For larger projects where the models are more complicated or there are a lot of them, you might think about either using a binary file format or writing your own format.
The file format we will be using is also a simplified version of the entire object file specification. We will write a class that loads only object files specifically designed for it. Object file parsers can be written to ignore any of the unknown syntaxes within the file. We can write an object file with texture coordinates and normal information, along with the vertex positions, and our program can ignore whatever specifications it does not need until we examine texturing and lighting.
Another downside to the format is that the vertex buffers are not indexed in an efficient manner. In a previous book, we looked at using index buffers to reference vertices multiple times on the GPU and it saved us some GPU memory. The method we will be examining is not recommended for complex geometry, because without using an indexed topology, the GPU must store many times more vertices than is actually necessary. For instance, we need only 8 vertices to store a cube on the GPU. We can efficiently index the vertices using an index buffer. However, the object files we will be loading store a cube as 12 vertices: 3 vertices per triangle, 2 triangles per face, and 6 faces.
Note: When you feel comfortable with this simple text based OBJ format, you could develop your own format specifically for storing the models in a manner best suiting the unique circumstance. We are examining the OBJ file format as an example of reading basic models. You can easily write your own custom 3-D file format which stores the information exactly as you need it, and which is far smaller and faster to parse than the OBJ format.
Object files are stored using a small scripting language with a very simple syntax. It is a plain text file with lists or arrays of vertex positions and other information. Each element of the arrays is specified on its own line. There are arrays of vertex positions, texture coordinates, vertex normal, and faces. A face in this context is a flat surface. Faces are defined by a collection of indices that reference values in each of the arrays (the vertex position, texture coordinate, or normal arrays). A face definition can reference any of the values in each of the arrays, allowing for great flexibility. There are many other keywords that can be used in object files. The following is only a brief description of the ones we will be using in our program.

Figure 5.1: Spaceship in Blender
Figure 5.1 is a cropped screen shot of the spaceship model we will load as it appears in Blender. You can see a light source and a camera as well as the model. The light source and camera will not be exported to the object file, only the spaceship itself.
The following code is the object file for the spaceship in Figure 5.1 as exported by Blender 2.66. The spaceship model is very simple, but even this simple mode creates a rather lengthy object file. The following code table shows the code for the spaceship.
# Blender v2.66 (sub 0) OBJ File: 'spaceship.blend' # www.blender.org v 0.245036 -0.277313 -3.008238 v 1.000000 -0.277313 0.638722 v -1.000000 -0.277313 0.638721 v -0.245036 -0.277313 -3.008238 v 0.332394 0.200748 -0.653899 v 0.999999 0.200748 0.638722 v -1.000000 0.200748 0.638721 v -0.332394 0.200748 -0.653899 v 0.924301 -0.171645 0.476444 v 0.294592 -0.171645 -1.523556 v 2.173671 -0.171645 0.066711 v 2.173671 -0.171645 0.476444 v 0.924301 -0.071908 0.476444 v 0.294592 -0.071908 -1.523556 v 2.173671 -0.071908 0.066711 v 2.173671 -0.071908 0.476444 v -0.921304 -0.073625 0.467630 v -0.288946 -0.073625 -1.532370 v -2.170674 -0.073627 0.057897 v -2.170674 -0.073627 0.467630 v -0.921304 -0.173363 0.467630 v -0.288946 -0.173363 -1.532370 v -2.170674 -0.173364 0.057897 v -2.170674 -0.173364 0.467630 vt 0.585247 0.203295 vt 0.000325 0.320701 vt 0.000323 0.000323 vt 0.800338 0.208366 vt 0.693343 0.208366 vt 0.585894 0.000324 vt 0.606542 0.475453 vt 0.219632 0.475452 vt 0.027282 0.321348 vt 0.929796 0.869160 vt 0.929795 0.946102 vt 0.607903 0.869160 vt 0.007848 0.968632 vt 0.000323 0.892059 vt 0.607257 0.968632 vt 0.585894 0.213707 vt 0.972548 0.227766 vt 0.972548 0.306642 vt 0.607189 0.747112 vt 0.810796 0.747112 vt 0.608702 0.773718 vt 0.577718 0.698455 vt 0.577718 0.477807 vt 0.606542 0.476099 vt 0.465133 0.534562 vt 0.399188 0.534562 vt 0.399188 0.518510 vt 0.557042 0.517863 vt 0.400036 0.517863 vt 0.399188 0.497305 vt 0.819439 0.606656 vt 0.999676 0.321348 vt 0.999677 0.717546 vt 0.626993 0.719207 vt 0.607189 0.656306 vt 0.818792 0.658818 vt 0.607189 0.719854 vt 0.810834 0.719854 vt 0.812349 0.746465 vt 0.830351 0.868513 vt 0.609613 0.868513 vt 0.828641 0.839677 vt 0.531725 0.534562 vt 0.465780 0.534562 vt 0.531725 0.518510 vt 0.399188 0.476099 vt 0.556194 0.476099 vt 0.557042 0.496658 vt 0.153905 0.688352 vt 0.439413 0.868513 vt 0.000323 0.818146 vt 0.000323 0.495980 vt 0.063200 0.476099 vt 0.060943 0.687706 vt 0.584527 0.121258 vt 0.907786 0.000323 vt 0.000323 0.393413 vt 0.607903 0.946102 vt 0.233352 0.869160 vt 0.585894 0.320701 vt 0.812309 0.773718 vt 0.606542 0.696746 vt 0.465133 0.518510 vt 0.556194 0.497305 vt 0.949339 0.760147 vt 0.818792 0.321348 vt 0.608703 0.746465 vt 0.607903 0.839677 vt 0.465780 0.518510 vt 0.400036 0.496658 vt 0.042889 0.868513 vt 0.398542 0.687706 vn 0.000000 -1.000000 -0.000000 vn -0.000000 1.000000 0.000000 vn 0.757444 0.633792 -0.156800 vn -0.000000 -0.000001 1.000000 vn -0.979238 -0.000001 -0.202714 vn 0.000000 0.980001 -0.198995 vn -0.953839 0.000000 0.300320 vn 0.646008 0.000000 -0.763331 vn 1.000000 -0.000000 0.000000 vn 0.000000 0.000000 1.000000 vn 0.953476 0.000000 0.301469 vn -0.645477 0.000000 -0.763779 vn -1.000000 -0.000000 -0.000000 vn -0.000001 1.000000 -0.000000 vn 0.000001 -1.000000 0.000000 vn 0.888496 0.000002 -0.458885 vn -0.382358 0.902665 -0.197479 s off f 1/1/1 2/2/1 3/3/1 f 5/4/2 8/5/2 7/6/2 f 1/7/3 5/8/3 2/9/3 f 2/10/4 6/11/4 3/12/4 f 3/13/5 7/14/5 4/15/5 f 5/16/6 1/17/6 4/18/6 f 13/19/7 14/20/7 9/21/7 f 14/22/8 15/23/8 11/24/8 f 15/25/9 16/26/9 12/27/9 f 16/28/10 13/29/10 9/30/10 f 9/31/1 10/32/1 11/33/1 f 16/34/2 15/35/2 13/36/2 f 21/37/11 22/38/11 18/39/11 f 22/40/12 23/41/12 18/42/12 f 23/43/13 24/44/13 19/45/13 f 24/46/10 21/47/10 17/48/10 f 17/49/14 18/50/14 20/51/14 f 24/52/15 23/53/15 21/54/15 f 4/55/1 1/1/1 3/3/1 f 6/56/2 5/4/2 7/6/2 f 5/8/16 6/57/16 2/9/16 f 6/11/10 7/58/10 3/12/10 f 7/14/17 8/59/17 4/15/17 f 8/60/6 5/16/6 4/18/6 f 14/20/7 10/61/7 9/21/7 f 10/62/8 14/22/8 11/24/8 f 11/63/9 15/25/9 12/27/9 f 12/64/10 16/28/10 9/30/10 f 12/65/1 9/31/1 11/33/1 f 15/35/2 14/66/2 13/36/2 f 17/67/11 21/37/11 18/39/11 f 23/41/12 19/68/12 18/42/12 f 24/44/13 20/69/13 19/45/13 f 20/70/10 24/46/10 17/48/10 f 18/50/14 19/71/14 20/51/14 f 23/53/15 22/72/15 21/54/15 |
There are several methods for adding this code to your project. You could add a text file to your solution and copy the above code, but usually models are added as references to external files. Copy the above code and save it to a text file called spaceship.obj. Right-click on the project name in the solution explorer and select Add existing item. Find the spaceship.obj file and add it to the project.
Visual Studio will assume the file is a regular relocatable machine code object file, because of the obj extension. This is no good, our file is a model, and it is not a binary code file. Right-click on the spaceship.obj file in your solution explorer and select Properties from the context menu. You will see the file properties form where you can change the Item Type value from Object to Text (see Figure 5.2).

Figure 5.2: Change Object File Item Type
If you do not change the file type, Visual Studio will think the file is corrupted, as it clearly does not contain the information that a standard binary object file should contain. Click Apply after you change the item type setting, then click OK.
Comments: Any line that begins with the cross hatch or hash symbol (#) is a comment. The first line in the spaceship.obj file is a comment that Blender 2.66 wrote to identify the modelling application and the second line is a comment with Blender’s website.
Vertices: Lines beginning with the “v” keyword specify vertex positions. The spaceship.obj file above has the vertices specified directly after the initial comments, but the actual order is irrelevant. Vertices could be mixed in with faces, normal, and texture coordinates. The “v” is followed by a space delimited series of three or four floating point values. The values specify the location of the vertex in 3-D space using the order X, Y, Z with an optional W component. For instance, the following specification is a vertex positioned at X=0.245036; Y=-0.277313; and Z=-3.008238:
v 0.245036 -0.277313 -3.008238 |
If there is a W component, our model reader will ignore it. The W is usually included to assist in matrix multiplication and other manipulations. Modern computers process four floating point values as easily as three, since they do so in a SIMD manner. If the W component is included, it will almost always be set to either all 1.0f or all 0.0f.
Texture Coordinates: Texture coordinates are specified using the “vt” keyword. The “vt” is followed by two or three values that represent U, V, and the optional W coordinate of the texture. For our purposes, textures use only the (U, V) coordinates, and they will be standard normalized UV texture space. These values will range from 0.0f to 1.0f. We will look in detail at UV coordinates and texturing later. The texture coordinates have been included in this file, even though we will not wrap a texture around our model in this chapter. The following line specifies a texture coordinate with U=0.585247 and V=0.203295:
vt 0.585247 0.203295 |
Vertex Normals: Vertex normals are specified using the “vn” keyword. The “vn” is followed by three floating point values delimited with spaces. We will examine what normals are and a very common use of them when we look into lighting a little later. The values are in the order (X, Y, Z) so the following line specifies a normal which points in the direction of X=0.757444; Y=0.633792; and Z=-0.156800:
vn 0.757444 0.633792 -0.156800 |
The values from a normal definition in the object file format specify a vertex normal, or the direction in which a vertex is pointing. They are not face or surface normals. The value is normalized and will define a normal with length 1.0f. A common strategy for extracting a face normal from a collection of vertex normals is to average the normals of the vertices that define the face. The normalized average of the vertex normals is used as the face normal.
Faces: Faces are defined on lines beginning with the “f” keyword. Lines beginning with the “f” keyword can be broken into space delimited vertices, each described with a slash delimited list of references into the above mentioned arrays. The order that attributes are defined is position, texture, and normal. A face can be described using three or more of these specifications, but for our purposes we will assume all faces are triangles. That means there will be three specifications per “f” keyword:
f 16/34/2 15/35/2 13/36/2 |
The above line specifies three vertices, each with its own position, texture coordinate, and normal. These vertices create a single 3-D triangle in our model. The first element, which I have colored blue, 16/34/2, means the 16th vertex position, the 34th texture coordinate, and the 2nd normal specified.
Note: The indices reference the arrays using a “1” based counting system. The first element in each array has a subscript of 1. C++ references arrays use a “0” based system, so when our program reads these lines, we must can subtract one from each index to convert between the two systems. Alternatively, we could add a dummy value to the C++ arrays at position 0, which is never referenced by the faces.
Note: Object files can be accompanied by a material file with an MTL extension. This file describes the material used by each of the objects described in the OBJ file. For our purposes, the materials will be hardcoded except for the UV texture coordinates.
If you are using Blender 2.66, or a similar version with the same OBJ exporter, you might like to export your own models with the same settings as I have used to export this spaceship. Selecting similar options for an OBJ file export will create models that can be loaded using our ModelReader class. Figure 5.3 shows a screen shot of the Blender OBJ exporter. The left panel is where these settings are changed.

Figure 5.3: Setting OBJ Model Export Options
The following table (Table 5.1) shows the settings I chose to export the spaceship model.
Table 5.1: Blender OBJ Export Settings
Setting | Value |
|---|---|
Selection Only | False |
Animation | False |
Apply Modifiers | False |
Include Edges | False |
Include Normals | True |
Include UVs | True |
Write Materials | False |
Triangulate Faces | True |
Write Nurbs | False |
Polygroups | False |
Objects as OBJ Objects | False |
Objects as OBJ Groups | False |
Material Groups | False |
Keep Vertex Order | False |
Scale | 1.00 |
Forward | -Z Forward |
Up | Y Up |
Path Mode | Auto |
Note: Object files can contain more than one object. In our example, there is only the spaceship model, but the format allows for many models to be defined in a single file. Each object in a file is given a name, and each object has its own list of vertices, normals, texture coordinates, and faces. If there is more than one object, each is specified after the “o” keyword. The “o” keyword is followed by the particular object’s name. All vertex information until the next “o“ keyword correspond to the last named object.
Note: The OBJ file syntax allows for nontriangular faces to be specified. For instance, square faces can be constructed using four vertices, or pentagonal faces can be made from five. This format allows for more succinct face definitions. When a model is loaded using this capability, the faces must be converted to a collection of triangles, since DirectX renders meshes using triangles lists. In the following examples, I have assumed the faces are all triangles and I have exported models using the “Triangulate Faces” option that will be available in many 3-D modeling applications.
Now that we have examined the basics of the object file format, we can write a class that loads and parses these files to create the mesh for a Model object in our application. I have called the new class ModelReader, and it can be added to the project as we left it at the end of the previous chapter. Add two files to your project, ModelReader.h and ModelReader.cpp. The following two code tables show the code for this new class.
// ModelReader.h #pragma once #include <Windows.h> #include <string> #include "Model.h" using namespace DirectX; class ModelReader { // Reads and returns the data from a text file in the current folder // this method also returns the size of the file in bytes static char* ReadFile(char* filename, int& filesize);
// Methods for reading an int or float from a string static float ReadFloat(std::string str, int &start); static int ReadInt(std::string str, int &start); public: static Model* ReadModel(ID3D11Device* device, char* filename);
static std::string ReadLine(char* data, int length, int &start); }; |
The ModelReader class is designed to be used statically; models are read from files using the ReadModel method. It is a helper class that loads text from a file and parses it into a simple array of Vertex structures, which are then used to generate a Model object. The spaceship.obj file contains texture coordinates and normal, but these are presently ignored by ModelLoader, and only the vertex positions are read from the file. The ReadModel method returns a new Model object that has the vertices specified in the file. At present, our vertex structure (defined in the Model class) contains positions as well as colors. We will look at texturing shortly, but for the moment I have used the rand() method to generate random colors and ignored the UV coordinates. This will allow us to see our object’s shape without reading and mapping the texture.
There are a few interesting things to note about the code for the ModelReader::ReadModel method that may help you write a custom model reading class. It reads the model’s vertices into a std::vector because we do not know how many vertices are in the file and they are dynamically sized. After the vertices and indices are read from the file, all the indices must be reduced by 1, because OBJ files use a base 1 counting system to reference elements in the arrays, and C++ uses a base 0 counting system as mentioned.
After this, I have made an additional temporary vector called verts, which is used to convert the separate arrays of positions, UV’s, and normal into instances of our Vertex structure. This step and the next could be combined into a single for loop. I have kept them separate for clarity in the example code.
Finally, something to be very careful of is the winding order of the 3-D program and the 3-D file format. DirectX uses a clockwise winding order to know which face is the front and which face is the back for each of the triangles in a mesh. The OBJ file format uses the reverse, a counter-clockwise winding order. For this reason, if we wish to render the OBJ file properly, we must reverse the winding order of the triangles. This is a simple matter of swapping any two of the points that create each triangle. I chose to swap them during the for loop of the final copy from the verts std::vector to the Vector structure array. If we do not do this, DirectX will think the backs of the triangles are facing the viewer, and it will cull the triangles and render black for the model.
To load a model, first include the ModelReader.h header in the SimpleTextRenderer.h file, see the following code table.
// SimpleTextRenderer.h #pragma once #include "DirectXBase.h" #include "Model.h" #include "VertexShader.h" #include "PixelShader.h" #include "ModelReader.h" // This class renders simple text with a colored background. ref class SimpleTextRenderer sealed : public DirectXBase |
In the SimpleTextRenderer::LoadDeviceResources method of the SimpleTextRenderer.cpp file, we can create our model. I have removed the code that we used to hard code our triangle and replaced it with new code that calls the ModelReader::ReadModel method. The following code table shows the changes to the code. I have commented out the code to be replaced, but this code can actually be deleted.
void SimpleTextRenderer::CreateDeviceResources() { DirectXBase::CreateDeviceResources(); // Define the vertices with the CPU in system RAM //Vertex triangleVertices[] = // { // { XMFLOAT3(-1.0f,0.0f, 0.0f), XMFLOAT3(1.0f, 0.0f, 0.0f) }, // { XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // { XMFLOAT3(1.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.0f, 1.0f) } // }; // Create the model instance from the vertices: //m_model = new Model(m_d3dDevice.Get(), triangleVertices, 3); // Read the spaceship model m_model = ModelReader::ReadModel(m_d3dDevice.Get(), "spaceship.obj ");
// Create the constant buffer on the device |
You should be able to run the application at this point and see a colored spaceship model; it will not look like a spaceship, because the camera is rather close and looking directly at the back of the model. Let’s raise the camera a little so we can get a better view of the mesh. The camera’s position is set in the SimpleTextRenderer::Update method; change the Y value to 5.0f so the camera is above and behind the mesh looking down. This change is highlighted in the following code table.
// View matrix defines where the camera is and what direction it is looking in XMStoreFloat4x4(&m_constantBufferCPU.view, XMMatrixTranspose( XMMatrixLookAtRH( XMVectorSet(0.0f, 5.0f, 2.0f, 0.0f),// Position XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),// Look at XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f) // Up vector ))); |
Now when you run the application you should see the colored spaceship in Figure 5.4. It looks a little strange because it’s being rendered with random colored triangles. Only the vertices from the original model are actually being applied.

Figure 5.4: Spaceship Model with Vertices