(Return to list of OpenGL 3.3 tutorials)
Download (3.25 MB)
10419 downloads. 15 comments
15th tutorials is here! 15 is a nice number, but 20 is more round, so I'll try to make 20 tutorials by the end of first year of my tutorial writing, which began on 19.09.2011. I hope I'll make it . And what is this tutorial about? Well, as you have noticed, the same scenery was in past 5 or so tutorials, and it became boring. Also modeling world with pure numbers is a real pain in the ass . So being able to load a 3D model from external file will make tutorial scenes nicer and more interesting. It's also important to get familiar with concepts like face, meshes and other related with 3D models. Model file format I chose to be the first is Wavefront OBJ. It's because it's really easy to read (it's a text version of file, not binary version) and to render. We'll go through loading code, even though I won't explain every single line, just what every important bunch of lines does, and I hope that from my explanation you'll understand the loading pretty well .
Before we begin with loading a model, it would be a nice thing to abstract things related to 3D models and make them clear. The first and very basic term is vertex - your model will surely consist of several vertices (thank you, Captain Obvious ). If we connect 2 vertices, we'll get an edge. The CLOSED set of edges is called a face. The typical face type is triangle, consisting of 3 vertices and 3 edges - <0, 1>, <1, 2> <2, 0>. Closed is written with capital letters, because face cannot be just three points, that form a line strip for example. I guess that word face is derived somehow from facing - you can tell that face is facing a direction (calculate its normal), whereas line strip doesn't face anything. Finally, polygon is a set of faces, like quad is set of 2 triangle faces.
From these basic terms, we get into higher structures. Surface, or smoothing group is a set of polygons, that have the same smoothing technique and parameters. Great example why to separate some polygons into smoothing groups is cylinder. There is a need to create 3 smoothing groups - top, bottom and sides - like this:
We can also create several groups of the polygons, that represents sub-objects of model. Like when you have a human model, groups defined can be left leg, right leg, left arm, right arm, head and so on. This is good for creating animations for example. And finally, all the groups together form a mesh, and it's basically whole 3D model. I hope that's clear.
Now let's have a look at how OBJ model file looks like. There are many lines in .obj file, each one can have several formats - some define vertices, some faces, some smoothing parameters and so on. We don't need to parse all line types, just important ones. To parse data from lines, I use STL stringstream, which behaves the same way as console input and output cin and cout, however data are not input through console, but from given string (like if you'd have that string written into console). The lines I'm parsing are these:
- v x y z [w] - vertex definition, x y and z coordinates are obligatory, w is optional
- vt u [v] [w] - vertex texture coordinate, v and w coordinates are optional, although usually there will always be u and v coordinates
- vn x y z - vertex normal, it might not be unit, so it's good to normalize the normals, just to be sure
- f face data - definition of face, can be in several formats, either only vertices, in that case it's f v1 v2 v3..., or vertex/texture coord/normal, in that case it's f v1/vt1/vn1 v2/vt2/vn2 v13vt3/vn3, or it's vertex/normal combination, in that case it's f v1/vn1 v2/vn2 v3/vn3
- s - shading model, once we'll probably handle it, but not in this tutorial
- usemtl - which material to use, for now we'll also ignore it
The version of OBJ loader in this tutorial will only load vertices, faces, texture coordinates and normals. Material data aren't defined in OBJ file directly, but rather in external file with .mtl extension, usually with same filename as obj model file. So our loader will just find texture name in mtl file for now. So let's review loading (look into source for implementation details, I really don't think it's important to explain every single line now. We'll create a new class for handling obj models - CObjModel. This class has one VBO and one VAO, that are created when the model is loaded. Let's have a look at loading particular elements:
Vertices: For every line beginning with v we'll parse the at least 3 numbers, and add a new vertex to the list (std::vector is used for this purpose). Vertices are indexed from 1 in obj file, but it doesn't matter now, we'll just add them to our vertices list, but when referencing them in faces, we must not forget this fact. I also set a flag using (iAttrBitField bitfield, that there has been at least one vertex defined. It's just for later check of model consistency, to handle errors like referencing normals or texture coordinates, when none were defined.
Texture coords: For every line beginning with vt we'll parse the at least 2 numbers, and add a new texture coordinate to the list (again, std::vector is used for this purpose). Texture coordinates are also indexed from 1 in obj file. I also set a flag that there has been at least one texture coordinate.
Normals: For every line beginning with vn we'll parse the at least 3 numbers, and add a new normal to the list (what else than std::vector would be used for this purpose ). Normals are indexed from 1 in obj file. I also set a flag that there has been at least one normal.
Face: Nothing difficult, all we need to do is to detect face format correctly, face parameters should match iAttrBitField bitfield, if they don't, my loading code will pronounce model file to be invalid. I admit it's not the best choice, but when someone defines model normals and later don't use them anywhere, what's it good for? And also if there were 2 defined faces, one in vertex/normal format and another in vertex/texture coordinate/normal format, then we would need two VAOs for rendering, to disable undefined vertex attributes, and then it would make loading and rendering really cumbersome. But most models you will find across internet are normal in this way, so don't worry.
After successful face format detection, we parse the indices of vertex attributes, and then simply copy vertices data from our std::vectors into VBO, and also increment number of faces.
When whole file was parsed correctly, loading code creates corresponding VAO object according to iAttrBitField bitfield, and then also loads a material. Material loading is defined in separate function loadMaterial. For now, this function only looks for map_Ka attribute, that defines texture file name of ambient texture, that should be placed on model (ambient just means, that this is the texture that interracts with ambient component of light, so we'll treat it as main texture). I had to modify texture loading code a little, I added support for 32-bit textures, because model in this tutorial uses dds (Direct Draw Surface) texture with alpha channel as well.
My loading code isn't perfect yet, but it should be able to load and correctly parse free obj models download from internet. There are many sites on the internet with royalty free models, like http://thefree3dmodels.com. That's the site I download model in this tutorial from. My code shouldn't crash even in some bad file cases, although I didn't test it on some corrupted files, I think it should work correctly (but this is just an hypothesis now ).
After loading is done, rendering is really not difficult, it's just a matter of binding our VAO and calling one glDrawArrays. And what models did I use in this tutorial? Well, see for yourself:
Yeah! It's a Thor model and SpongeBob model ! I also changed the skybox again. Geometry shader was simplified, and it doesn't produce additional geometry now. It would be better to remove it at all now, but it was easier to just let it be there and remove unneccesary code line (I was too lazy to remove it and return functionality to vertex shader ).
This is really the end of 15th tutorial . Its purpose was to teach you how to load and render OBJ model file. There was really nothing else new. So that's really it for today, I hope it was useful for you, and in next tutorial, we'll have a look at rendering to a texture (I will probably create something like moving Thor model displayed on some screen, so it will be like Avengers movie ). Thank you if you read it this far .
Download (3.25 MB)
10419 downloads. 15 comments
|oRf2Angx (email@example.com) on 11.12.2015 21:18:48|
|… .well is say just Whenever you arrived at our wtbeise, the initial you need to know is it is possible to buy the highest quality and most expensive ipad case, furthermore your favorite apple ipad cases as nicely as ipad add-ons. You may uncover hundreds|
|KebRuYolSS (firstname.lastname@example.org) on 11.12.2015 21:09:35|
|great Website Admiring the time and effort you put into your wbeiste and in depth info you offer. It is excellent to come across a blog every once in a whilst that isn’t the same out of date rehashed material. Great read! I’ve saved your internet site and I’m includ|
|Radoslaw (Radoslaw645@wp.pl) on 16.06.2015 13:13:38|
|Geometry shader is wrong.|
This is properly:
layout(triangle_strip, max_vertices = 3)out;
uniform struct Matrices
in vec2 vTexCoordPass;
in vec3 vNormalPass;
smooth out vec3 vNormal;
smooth out vec2 vTexCoord;
mat4 mMVP = matrices.projMatrix*matrices.viewMatrix*matrices.modelMatrix;
for(int i = 0; i < 3; i++)vNormalTransformed[i] = ( matrices.normalMatrix * vec4(vNormalPass[i], 1.0) ).xyz;
for(int i = 0; i < 3; i++)
vec3 vPos = gl_in[i].gl_Position.xyz;
gl_Position = mMVP*vec4(vPos, 1.0);
vNormal = (vec4(vNormalTransformed[i], 1.0)).xyz;
vNormal = normalize(vNormal);
vTexCoord = vTexCoordPass[i];
ps. Somebody know how repair flickering SpongeBob ass?
|Jason (email@example.com) on 01.03.2015 21:53:36|
|It is a very helpful, wonderful and generous sharing. |
When I tried to build the code, I got many fata errors like failed to find gl/glew.h. Where can I get glew.h and associated c++ code?
|dimitrius (firstname.lastname@example.org) on 25.08.2013 11:56:44|
|You have an error in the geometry shader. You multiply a vector by a matrix insted |
multimatrices.normalMatrix * vec4(vNormalPass[i], 1.0).
Therefore, the light is wrong.
|Alessandro Silva (email@example.com) on 22.05.2013 05:17:11|
|You realize that you loaded the bump map instead of the colormap on the thor model ???|
|dodo (firstname.lastname@example.org) on 27.04.2013 08:36:14|
|thaks, this is very helpful for me.|
|Chris (email@example.com) on 20.04.2013 04:22:04|
|Anyone who can help me get this running would be greatly appreciated|
|Chris on 20.04.2013 04:11:42|
|Can anyone actually get this to run? When I bring it into Visual Studio I get a ton of errors for missing header files.|
|Abdul (firstname.lastname@example.org) on 22.02.2013 05:15:56|
|It is very good effort, I am thankful for sharing this.|
But i face a problem how to load a model, because i am beginning stages. When i run this code in Visual studio it generate Plz guide me
|Avithohol on 31.07.2012 20:38:37|
|Very well organized tuts, very useful. Thank You for the time to put this together. Keep on coding :)|
Greetings from Hun