In this lesson, we will learn how to load a 3d model from an obj file. .obj files are ascii based files, so they are easy to read. Along with .obj files is the .mtl file, which is the material library. We will also learn to load the materials for the model using the .mtl file. .obj files are large and contain no animation, so you will usually want to use a different model format for video games. However, the .obj format is a great file to start with when learning how to load models! This is a longer lesson, so i will go through in sections, instead of line by line like i usually like to do. Let’s get to it!
.obj files have been around for a long time. They are one of the oldest 3d model formats out there, but still widely used. .obj files do not contain header information like other file formats. Headers are the beginning of a file which will give you information about it. For example a header in a 3d model file might tell you how many vertices, texture coordinates, normals, materials, subsets, and faces there are in the file. Since .obj files do not contain this information, we can do one of two things. The first thing we could do is read the file twice. The first read would gather information like how many vertices, faces, normals, etc. there are, so we can initialize arrays for each. The second read would get the actual model information, like vertex positions, and store them in the initialized arrays. This is the inneficient way of how it used to be done before we had something called “vectors”. The second way we can load an obj file, and the way we will do it, is use vectors. A vector is a dynamic array (which you should already know), where instead of initializing it with a set size, we can “push_back” elements to increase the size as we need. Another cool thing about vectors is they clean themselves, so we do not need to “delete” them like we do standard arrays. Using this method, we only need to read the file once, and store the models information in the vectors as we go. Each piece of information in an obj file is (supposed to be…) put onto it’s own line, where a string of one or more characters (eg. “v”) explains what the rest of the information on that line is used for, and “\n” is the terminating character of each line. Here is an example of a sample .obj file taken from the one this lesson uses:
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 26.07.2011 13:47:43
mtllib spaceCompound.mtl
#
# object Window
#
v -8.6007 1.3993 10.0000
v -8.6007 8.6007 10.0000
...
# 4 vertices
vn 0.0000 0.0000 -1.0000
# 1 vertex normals
vt 0.0000 0.0000 0.0000
vt 0.0000 1.0000 0.0000
...
# 4 texture coords
g Window
usemtl Window
f 1/1/1 2/2/1 3/3/1
f 3/3/1 4/4/1 1/1/1
# 2 faces ## "#" - Comments
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware Comments start the line with "#". Whenever you read a "#", you can skip that line. ## "mtllib" - Material Library File mtllib spaceCompound.mtl mtllib is the start of a line which contains a material library filename. A material library is a separate file that goes with .obj files, which define the materials for each group. .obj files don't always contain a library file, but they usually do. Not only that but once in a while they may contain multiple library files. It shouldn't be too hard to modify the code to take this into consideration when reading an obj file. ## "v" - Vertex Positions v -8.6007 1.3993 10.0000 Lines containing a vertex position are started with "v". Vertex positions follow the (x, y, z) format, where each number is separated by a single space. ## "vn" - Normals vn 0.0000 0.0000 -1.0000 Lines containing a vertex normal are started by "vn". Vertex normals follow the (x, y, z) format, where each number is also separated by a single space. They are used to specify which direction a triangle is facing, which can be used to impliment lighting. To get smooth lighting across a surface you will usually want to do something called "normal averaging", which is finding the normal for each face sharing a single vertex, then average them together to get that vertices normal. In this lesson we will not need to load vertex normals from the file, as we will learn how to create our own vertex normals. However, for completion, we do load the normals in from the file, but do not use them. ## "vt" - Texture Coordinates vt 0.0000 1.0000 0.0000 Lines containing texture coordinate data are started with "vt". Texture coordinates are used to correctly map an image to a triangle or set of triangles. They follow the (u, v, w) format, where each number is separated by a single space. Usually you will only need a "u" and "v" coordinate for mapping 2d images, but sometimes you might use a cubic texture (3d texture) to map round objects such as spheres, where you will need the "w" coordinate. ## "o" - Objects o spaceCompound The obj file for this lesson does not specify "o". Objects are a group of groups. An example is a town, where you might have a couple houses, which would each be considered objects, and each house contains groups, like windows, doors, walls, roof, etc. ## "g" - Groups (Subsets) g Window Groups are a set of faces which all use the same attributes, such as the same material. In this example, we have a group for the window, each of the rooms, and each of the doorways between rooms. We will call groups "subsets" in this lesson. ## "usemtl" - Group Materials usemtl Window Like what was said a moment ago, each group contains a Material, and lines specifying which material to use are started with "usemtl". The materials are found in the material library (.mtl) file usually accompanying an obj file. In this lesson, we have two materials, a transparent material for the window, and a textured material for the walls. ## "s" - Smoothing Groups s 1 ... s 2 ... Smooth shading can also be turned off s off Smooth shading can be used to for smoothing across groups. This can create a nice round looking surface. We will not get into this in this lesson, so we will just skip lines with "s". ## "f" - Faces A face containing a position, texture coordinate and normal
f 1/1/1 2/2/1 3/3/1 A face containing a position, and a normal
f 1//1 2//1 3//1
A face containing a position, and a texture coordinate
f 1/1 2/1 3/1
A face containing only a position
f 1 2 3
Lines defining a face are started with “f”. Faces are a set of three or more vertices, where each vertex follows the format “Position/Texture Coordinate/Normal”. Each vertex is separated by a space. Each vertex defined in a face can contain a position and/or a texture coordinate, and/or a normal, where each number is separated by a “/”. If a vertex contains only a position, there is no “/”, if it contains a position and a texture coordinate, there is only one “/”, which is between the two number. If the vertex contains a position, texture coordinate and a normal, there is a “/” between all three numbers. And last, if it contains only a position and a normal, there is a “//”, or TWO “/” between the numbers.
The numbers represent an index value STARTING WITH “1” from the beginning of the file. I stress the “starting with 1” because C++ arrays start with “0”, so you MUST subtract one from these numbers before storing them. I’m going to try to make this a little more clear. Consider the line “f 1 2 3”. This face contains only a position for each vertex. So at the beginning of the file, the first vertex position was read, “v -8.6007 1.3993 10.0000”. The “1” in the face “1 2 3” represents this vertex position. However, in C++ (in our code) when we stored this vertex position, we stored it in the [0] element of the vector. So when we store this face, we need to actually store it like this “0 1 2” instead of “1 2 3”. And when there is multiple groups and objects, the numbers are still based on the collective index value for each defenition starting from the beginning of the file.
One more thing about faces, is they are not always stored as triangles in an obj file. Sometimes they are stored as squares or other shapes containing more edges. Here is an example
f 1 2 3 4 5
DirectX can only work with triangles. So we must take this into consideration when getting these faces. To fix this problem, we will turn each polygon with more vertices than three into triangles. We will do this by taking the first three vertices in a face and making a triangle of it. Each vertex defined after is a new triangle. We can make a new triangle by taking the very first vertex defined in the face, and use it as the first vertex for every other triangle in the current face. Then take the last vertex of the triangle before the current one, and use that as the second vertex in the current triangle. For example, the polygon above is defined by “1 2 3 4 5”. We will need to turn this into three triangles, because the first three make up the first triangle, and the two after the first three are two new triangles. So the three triangles will look like this:
tri1 = “1 2 3”
tri2 = “1 3 4”
tri3 = “1 4 5”
I want to mention one more thing about faces in obj models. Some programs such as lightwave are able to make shapes that are concave… An example of a concave shape is pacman, or a star. The problem with this is that when we create triangles from the vertices of this shape, some of the triangles will “overlap” other triangles. Have a look below.
You can fix this problem by calculating the angle between edges to find where it is concave, and retriangulate the face depending on the concavities, but we will not do that in this lesson.
The mtl file is the material library for the object file. This file will contain the surface properties of each group. Not all obj models include a material library file, but we will assume our’s do. As was mentioned above, this lesson’s model uses two materials, one for the window, and the other for the walls. Loading the model is not the ONLY new thing in this lesson. We have now created a “SurfaceMaterial” structure, which will hold the various surface properties of each subset, such as diffuse color, transparency, and textures. We will cover this structure soon enough, but for now lets look at the mtl file’s contents. This is the mtl file that we will be using for this lesson.
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 26.07.2011 13:47:43
newmtl Window
Ns 32.0000
Ni 1.5000
d 0.8000
Tr 0.2000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0100
Kd 0.0000 0.0000 0.0100
Ks 0.3500 0.3500 0.3500
Ke 0.0000 0.0000 0.0000
newmtl MetalPanels
Ns 10.0000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka metalpanel.jpg
map_Kd metalpanel.jpg
map_bump metalpanelnormals.jpg ## "#" - Comments
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware mtl files also contain comments, and again they start the line with "#". We can skip lines starting with "#". ## "newmtl" - New Material
newmtl Window A new material is started with "newmtl". The word after "newmtl" is the name of the material. Everything after "newmtl" is that materials defenition, until another "newmtl" is reached, or the end of file (eof) is reached. ## "Ns" - Specular Power
Ns 10.0000 A line starting with "Ns" is the specular power of the surface. The value can range from "0" to "1000", where 1000 is the highest power of specular, or most reflective of light. ## "Ni" - Optical Density
Ni 1.5000 A line starting with "Ni" is the optical density of the surface. The value can range from "0" to "10", where 10 is the most dense. Optical density is used to "bend light" for transparent surfaces. ## "d" - Transparency(it's 不透明度)
d 0.8000 A line starting with "d" is the transparency of the surface. The value can range from "0" to "1", where 0 is completely transparent, or invisible. ## "Tr" - Transparency
Tr 0.2000 A line starting with "Tr" is also the transparency of the surface. Some obj models use either "d", "Tr", or both to define the transparency of a material. The value can range from "0" to "1", but this time 1 is completely transparent or invisible. ## "Tf" - Transmission Filter
Tf 1.0000 1.0000 1.0000 This one has to do with light passing through a transparent surface. The light passing through a transparent surface is filtered by this attribute. ## "illum" - Illumination Model
illum 2 The illumination model is a value between 0 and 10. Each number is a different equation which impliments lighting and shading for a surface. We won't worry about these here. ## "Ka" - Ambient Color
Ka 0.0000 0.0000 0.0100 This is the color of the surface when no light is hitting it. Usually this is the same as the diffuse color of the surface, so in this lesson, we will load this value into the diffuse color of our material if there is no diffuse color defined. ## "Kd" - Diffuse Color
Kd 0.0000 0.0000 0.0100 This is the actual color of our material. If there is no diffuse texture defined for the material, we will use this color to color the surface using this material. ## "Ks" - Specular Color
Ks 0.3500 0.3500 0.3500 We will not use specular color in this lesson, but we will in an upcoming lesson. This is the color of the specular light which is reflected off of shiny surfaces. ## "Ke" - Emissive Color
Ke 0.0000 0.0000 0.0000 Emissive color is the color of a surface when it "lights up". For example a lightbulb. When a lightbulb is off, it might look dark grey, but when it is turned on, it turnes bright white. Emissive lighting is used to "light up" the surface which is sending light out. In video games, a lightbulb may not actually light itself up when sending out light, so we need to give it emissive light. This is not used in this lesson, but would be easy to impliment if needed. ## "map_Ka" - Ambient Color Map
map_Ka metalpanel.jpg This is the texture used for the ambient color of a surface. This is almost always the same as the diffuse texture。 ## "map_Kd" - Diffuse Color Map
map_Kd metalpanel.jpg This is the texture or image used to do the actual coloring of the surface. ## "map_Ks" - Specular Color Map
map_Ks metalpanel.jpg This is the texture or image used to say how "shiny" the different parts of a surface are. ## "map_bump" or "bump" - Bump Map
map_bump metalpanel.jpg
bump metalpanel.jpg Bump maps are used to give the surface actual texture, where some parts of a surface may appear to be a little deeper, while others appear to be sticking out. We will be using normal maps in place of bump maps in a later lesson. # Right Hand Vs. Left Hand Coordinate Systems Directx uses a "Left Hand Coordinate System". What this means is that the positive direction of the z-axis is facing away from the camera, while the right hand coordinate system's positive z-axis is facing towards the camera. positive y is facing up, positive x is facing right. A lot of modeling programs such as maya and 3ds max use a right handed coordinate system. Since directx uses a left had coordinate system, we will need to convert these models into a left hand coordinate system. To convert from right hand to left hand, we must do a couple things. First is invert the z-axis of the vertice's positions by multiplying it with -1.0f. We will also need to invert the v-axis of the texture coordinats by subtracting it from 1.0f. Finally we will need to convert the z-axis of the vertex normals by multiplying it by -1.0f. I have put a boolean variable in the parameters of the load model function, where if it is set to true, we will do the conversion, and if it is set to false we will not do the conversion. # Transparent Subsets We have taken transparency into consideration when loading models in this lesson. Remember from the blending lesson, that the blending technique works by "blending" whats already on the render target with the transparent object. Because of this, we need to make sure that the transparent subsets are the last thing drawn. We have also talked about multiple transparent objects in the blending lesson, but I will quickly go over the solution here. We will not impliment this technique, but a more advanced technique of the drawing order of transparent objects would be to find their distance from the camera, and draw them from furthest to nearest. You may run into this problem which is why I wanted to mention it. # The Removal of the Mesh Interface (eg. ID3DX10Mesh) Direct3D 11 has removed the mesh interface. Because of this, we now need to handle all the 3d model stuff by hand, such as the vertex and index buffers, subset counts, mesh intersection (which was damn usefull for picking a 3d object with the mouse). Anyway, its not a huge problem, but instead of loading the 3d model from the file directly to a mesh object, we will now need to store the vertices and index list into buffers, and keep track of subsets and materials. I wanted to mention this to you before we begin! If you have read other lessons on this site, you have no doubt noticed they don't use classes. This is because I try to make the lessons's code easy to read straight through (classes can make reading code more time consuming because you have to jump around the code to see whats happening). Classes make code MUCH more efficient, and you will most definitely want to use classes when making a game. So in the bottom of this lesson I have included an exercise to make a mesh class including methods to load the model from a file and to draw the models subsets, and members to store the models information to replace the removed mesh interface.