So far weve only been using a cube and thats not very exciting is it. We probably want to expand beyond this cube if we want to make more impressive 3d graphics. We want to be able to import .fbx files that store the vertices data without us having to declare it everytime, just fetch the file and display that information accordingly. So lets do this.
Assimp:
Assimp is an open sourcce library that loads a model files data, once Assimp has loaded the file we can retrieve the data we need from Assimp. When you import the model it creates a scene object that contains the data of that model. If you want to read more about how Assimp exactly functions you can read more here.
https://learnopengl.com/Model-Loading/Assimp
Mesh Class:
LearnOpenGL introduces the mesh class at this point so lets explain that class and how it works exactly. Lets take this step by step.
Position: where the models position is.
Normal: The directions for each surface
TexCoords: the texture coordinates
Tangent & Bitangent: These are honestly a little bit confusing but the way i understand it is that they both along with the normal contribute to making the UV mapping make sense accordingly. An object is only really textured on its outside and is hollow inside so we have to both orient the textures accordingly to how the model should look and make sure they are properly displayed in which the normal points. Tangent tells the X(U) direciton and Bitangent tells the Y (V)direction.
The standard unsigned int id, this ofcourse stores the texture ID that will be created with glGenTextures, and the string ofcourse stores its type, our Mesh::Draw() function will look at the type to decide which uniform name to use when binding the texture.
now lets actually look at the mesh class instead of the file.
So the class has three global variables that are lists (vectors).
Vertices, indices and textures, now these should seem familiar to you by now but here we go again
Vertices = all the vertex data (positions, normals, uvs etc etc), Its convenient that we made that struct before right? Because now we get to use it!
indices: lets tell our program which vertices to connect and in what order!
textures = textures. But more interesting now because we have diffuse, normalmap and specular textures etc etc.
Then we have this very boring constructor that takes arguments and asigns its arguments to the class variables, boring boo! But we do call a function in it that is setupMesh(), neato.
Lets take a look at this draw function.
The function takes a reference to the shader object yknow so we can acess it and everything.
each texture type can have mutliple textures so we can refer to them properly we need a numbering system with ints. Just add 1!, texture1, texture2 etc etc.
Then we loop through all the textures and actiavte each texture accordingly and bind it.
Then we figure out the names of the uniform name and in that figure out what kind of texture it is, specular, diffuse etc etc. Then we bind that accordingly.
Then finally we draw the mesh yknow with drawElements etc.
So lets go through this one more time
VAO remembers how to read our vertex data and which EBO is attached.
VBO = this is like a chunk of memory that stores vertex data, its positions, normals, texture coords, tangents, bitangents etc etc.
EBO carries the indices (which vertices make which triangles)
Then we bind the VAO accordingly to openGL.
Proceeding to upload the vertex data into the VBO and doing the same with the EBO.
Then we do the classic song and dance of the vertexAttribgame with the offsets and making sure things are bound accordingly to our vertices and indices array.
Then finally we unbind the VAO with glBindVertexArrray(0);.
Model Class:
Now lets implement a model class, the model class will essentially create an object that includes a bunch of different meshes. A house for example is built out of multiple different meshes, so storing them all inside a "model" would be ideal.
So we have a few public class variables here so lets talk about those.
textures_loaded stores all the loaded texture, it would be expensive and stupid to load all the textures time and time again in order to draw a model. What if a model reuses a texture a lot? Should we load it everytime? Ofcourse not thats not a good idea.
meshes, this is the list of our meshes that the model consists of.
directory, this stores the path to the folder where the model file came from, so if textures are stored with relative paths we can find the properly.
gammacorrection, this is just a boolean flag for handling gamma correction when loading textures, not really used right now.
Then we have our constructor, So in order to create a model we have to give a filepath, and as soon as the object is constructed it loads its model, immediately upon creation all the models meshes, data and textures are ready!
Then we have our wildly simple render function, It just loops over all our meshes inside the model and calls the draw function passing in a reference to the shader.
Now that our Model class is set up, we need a way to actually load the data from a model file. That’s where loadModel() comes in.
This function takes a file path, gives it to Assimp, and builds a scene out of it.
So we create an Assimp::importer objectm this class reads the files and returns a scene, whats a scene? well its a container for essentially everything inside the models file. Assimp handles its meshes, materials. textures etc etc with hierarchies. A part of our model can have a child. I like to think of this like how it is in Unity, lets say you have a Wall with a cutout of a door, then you have the door. You attach the door to the wall (so now the door is a child of the wall) so you can just rotate the door without rotating the whole wall.
Anyway then we read the file through our path variable which is provided within our argument.
aiProcess_Triangulate makes sure that every face of the model is converted into triangles cuz triangles are awesome.
aiProcess_triangulate apparently flips the Y coordinate of the texture coordinates, OpenGL treats the top of a texture as 0, other programs do differently i guess.
aiProcess_genSmoothNormals So if the model doesnt have normals this will automatically generate them.
Then were onto a quick sanity check code, that our code is actually working, no errors.
If everything works out we continue and we extract the directory path and then finally we process the root node, process node then goes through all its children, extracts those meshes and stores it in our Model object.
Here we loop through all meshes that the node references, we stores the indices of each mesh if there are any in our global scene->mMeshes array.
Then for each index we call processMesh, thats what converts our Assimp mesh into the Mesh class. Then we push back the processed mesh into our meshes vector which our Model object has.
After handling the meshes in the current node we Go to its children.
Now here we have a doozy, took me a long time to make this function actually work and this seems to work so lets go through this massive function here.
Local containers for vertices, indices and textures, this the stuff that make up a mesh right so we need to define and extract that.
Lets build the vertex list.
aiMesh stores positions in mVertices, mNormals and up to 8 UV sets in mTextureCoords. This is pretty standard right you loop through each vertex insid the mesh and retrieve these and add to the vector list. Push_back ofcourse adds the element to the end of the vector list.
We build the index list, Assimp gives faces and we flatten their indices into one indices array to be used by our EBO.
Here we snatch that material for this particulary mesh, we will read the textures from this material right heeeeere
Oh wee a lambda exciting. A little local helper function (addTex) inside our procesMesh function, So it can access textures and mat because of [&] you know it has its refrences. When its called it loads all the textures ofa certain type from the material and then appends them all into the main texture list. SO eachcall just loads a new category of textures and adds them to the list.
I mean ye lets add all the different textures, not that complicated just looks like right old mess. Small little algorithm function find_if and none_of with a cute little lambda. Diffuse and base_color are different names for the same thing so its worth it to check both and if we dont have those check ambient and all that.
lets load a materials textures! Basic arguments, what material are we loading? What type of material? Whats the name we want to assign to it in our shader, and the gamma thing. Whether to load textures in sRGB or linear color space. Returns the textures vector list that we will append to the meshs texture list.
We loop over all the textures of that type, GetTextureCount() is a helper function that asks how many textures of a type it has. Then we fetch that texture.
bool skip, we check if we have already loaded it, if we have skip that stuff. If not load it now.
TextureFromFile reads the actual image and creates an opengl texture and returns its id. Then we fill in the rest, Type of texture and its path.
Add it to our local list with push back and then we add it to the global cache with textures_loaded.push_back(t) so other meshes can reuse it.
Okay now lets take a look at how we implement the model class inside our main c++ file
We build the shader and hook up the sampler uniforms. We immediately set the sampler uniforms to the texture units, GL_Texture_0, Gl_Texture_1 etcetc. This must match what Mesh::Draw does though when it binds textures, but eh.
We load the model asset via our constructor.
Then the render loop is as usual but its very satisfying. Anyways, heres the result
Oh and heres the link to the model https://sketchfab.com/3d-models/survival-guitar-backpack-799f8c4511f84fab8c3f12887f7e6b36
and shoutout to https://learnopengl.com
So like I'm sort of done. I feel like I have gotten the basics of openGL sort of under my hands. I do not think it would be very beneficial to go through every section of LearnOpenGL, specificly the advanced stuff. I think learning opengl has reached a pretty natural conclusion. Now it is time to stop learning OpenGL and to start Doing OpenGL. Lets start making something. Following tutorials and guides and such is one thing for me but now it is time to actually start making something. Exciting! I feel it in my bones. I have gotten to the point were I feel like I will learn more by making an actual project instead of approaching OpenGl as something to "learn". So I will be making a OpenGL graphics Demo! (Although I will probably still consult learnOpenGL quite a lot) Yay, I will document it all the same as this series but it will be called, Doing OpenGL instead. Although before I begin that I will do a quick little series on making a 3d rasterizer, that will be called something that sounds nice, have not figured that out yet. Anyways, Looking forward to actually take a step back and get a little look under the hood of OpenGL, I feel like the maths part is my weakest aspect of graphics programming so it will certainly help. Anyways GG go next shoutout to
https://sketchfab.com/3d-models/survival-guitar-backpack-799f8c4511f84fab8c3f12887f7e6b36
https://github.com/assimp/assimp
https://github.com/nothings/stb
https://www.glfw.org/download.html
https://github.com/Dav1dde/glad
and you


















Top comments (0)