OpenGL Course - Create 3D and 2D Graphics With C++

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
you're about to learn about opengl from victor gordon he has created an excellent course for beginners with simple to follow instructions and informative diagrams welcome to this beginner's course on opengl if you're wondering what opengl is well it's a cross-platform graphics api that allows you to render 3d and 2d scenes with the power of your graphics card usually when programming we use our cpus which are great at doing a huge variety of tasks one after another in a linear fashion but when we deal with graphics a lot of the tasks are very similar and don't usually depend on one another so the fastest way to get a result would be by doing all of them at the same time in parallel this is what gpus or graphics cards excel at opengl is simply a way for us to communicate with the gpu by the end of this course you'll be able to import basic 3d models such as this into your application and have a firm grip on the basic concepts of computer graphics everything from basic buffers used to send data to the gpu to classic lighting algorithms and shaders the only things you should be familiar with before we start are the basics of the c plus plus programming language and the basics of visual studio since that's the ide we'll be working with the first thing you'll want to do is to check you have the latest drivers for your graphics card and the latest version of visual studio next make sure you download the 64-bit windows installer for cmake and go ahead with the installation now download the source package for glfw lastly you need to download glad make sure you select c c plus plus version 3.3 core ignore everything else and press the generate button now press on the glad.zip to download it once you've finished installing cmake in visual studio open up visual studio and click on create new project select empty project for c plus click next type in a name for your project make sure the check box is checked and remember in what folder you've placed your project open up the folder of your project and create a new folder naming it libraries now we'll create another two folders in libraries naming them lib and include this is where we later import all our libraries into now we need to extract the glfw zip file and open up cmake in cmake select the folder we've just extracted as the source folder then create a new folder named build in the source folder and select it as the build folder now click on configure and after making sure you have the same settings as me click finish now again make sure you have the same configuration as me and click on configure again click on generate and once everything is done exit from cmake now we want to build glfw so open up the folder you've extracted then build then open up glfw.s in visual studio right click here and select build solution if this gives you any errors it probably means that you've messed around with the build and we'll just have to regenerate glfw using cmake and follow this step again once your build finished successfully exit from visual studio now it's finally time to import our libraries in so open up the glfw extracted folder and go into build source then debug and i'll cut and paste glf3.lib into your lib folder that's inside your libraries folder back to the extracted glfw folder going to include cutting and pasting glfw into the include folder of your project assuming you follow the steps correctly you can now delete the extracted glfw folder and open up glad.zip open up include and extract the two folders into your project's include folder now go folder back and open up source extracting glab.c into your project's main folder we'll now configure our visual studio project to know where to access the libraries from so open up your project and make sure you have 64-bit selected now go to project properties and select all platforms for your platform then go to vc plus plus directories open up include directories and point to the include folder in your libraries folder click ok and now do the same for your library directories with your lib folder once you're done with that go to linker input and open up additional dependencies here we'll write glfw3.lib and opengl32.lib don't worry even though it's called opengl 32 it's still the 64-bit version is just the naming convention now click ok and ok the last thing you have to do is to drag the glut.c file from your project folder into your source files folder in visual studio then right-click source files click add new item select c plus plus file name it main.cpp and click on add now in order to test that everything is fine include this and type int main brackets curly brackets return 0 semicolon and run if you've done everything correctly you should get no errors so now we're gonna pick up where we left off and create the window we're gonna use glfw to do that so the first thing we need to do is initialize it so we can properly use its functions and since we've initialized it we should also terminate it before the function ends so i'll add this at the end of the function now glfw doesn't really know what version of opengl we're using so we need to tell it that we can do that by giving it so-called hints with the special function that takes a type of hint and a value for example here i'm giving it a hint that we are going to specify the major version of opengl that we are using and then i give it the version itself which is three since we are using opengl 3.3 now i'm just gonna do the same for the minor version which is the exact same one the last hint we have to give it is about which opengl profile we want to use so i'll type glfw underscore opengl underscore profile now an opengl profile is sort of like a package of functions as far as i know there are only two packages core which contains all the modern functions in compatibility which contains both the modern and outdated functions we only care about the modern ones so i'm going to use the core profile now for the window itself this is the data type of a window object in glfw let's just name it window and use the create a window function this will take five inputs the width of the window the height of the window the name of the window whether we want it full screen or not which we do not and the last thing is not important and just to be on the safe side i'll add a bit of error checking in the case in which the window fails to create so great we now have a window object problem is glfw isn't the brightest kid around the block so i'll now have to tell him that since we've created the window we would also like to use it who would have thought this tells glfw to make the window part of the current context a context being a sort of object that holds the whole of opengl it's a bit abstract as context can hold and do many things but we'll just go with this for now now just like before once we are done with the window itself we want to delete it so let's do that at the end of the main function if you run the program now you'll maybe be able to spot a window pop up and instantly disappear into oblivion that happens because once the main function finishes creating the window it just continues to go about its business and tries to reach the end of itself which when it does it ends the whole program in order to stop that from happening we need to make a while loop which will only end on the condition that the window should close that condition will only happen when we press the close button or if another function tells the window to close and now just one more thing before we get our glorious window we need to tell glfw to process all the pulled events such as the window appearing being resized and other such things if we don't process those events the window will just be in a state of not responding and now press run and watch the fruits of your labor but i find this white window kind of boring so let's add some color to it we'll start doing that by finally using glad and telling him to load the needed configurations for opengl then we'll tell opengl the viewport of our window which is just the area of the window we want opengl to render in that will go from the bottom left corner of the window coordinates 0 0 to the top right corner of the window coordinates 800 800 in our case now in order to go forward i'll have to introduce some computer graphics concepts so lesson time as you probably already know screens display a bunch of images really really fast which give us the illusion of motion these images are called frames screens display frames by changing from the pixels of the display to the pixels of a frame these are changed one by one from the top left of the screen to the bottom right of the screen now while the screen is loading the pixels from the current frame and displaying them the next frame is being written in the background away from our eyes once the screen finishes displaying the current frame it switches it with the next frame and starts displaying dot well in the background the no previous frame is being overwritten with new information these two frames are called buffers and they're simply a space of storage in memory for pixels the buffer from which information is being read to be displayed on the screen is called the front buffer while the other one on which information is being written is called the back buffer now let's write the actual code we're gonna tell opengl to prepare to clear the color of a buffer and give it another color i'm gonna give it a nice navy blue in the normalized decimal rgb form of color and set the last number the alpha to one the alpha number dictates the transparency of the color one being opaque and zero being completely transparent now we want opengl to execute the command we've told it to prepare for so we'll use this function specifying we want to use the command on the color buffer we'll learn more about types of buffers in the next video so at this point we have a back buffer with the color we want and the front buffer with the default color so in order to see our color we'll want to swap the buffers since only the pixels on the front buffer are being displayed now press run in tada we don't have a nicely colored window that you can show off to your imaginary friends and the last thing that's left to do is just to write some comments so that you can make sure you understand what's happening try writing them with your own words but if you find that difficult then feel free to copy mine i'll leave the source code in the description and the pdf with some exercises so you can practice what you've just learned so now let's add a triangle to the mix but first i'll have to introduce you to something called the graphics pipeline the graphics pipeline is essentially just a series of functions which takes some data at the beginning and then at the very end of the graphics pipeline it outputs a frame now the input is called the vertex data this is just an array of well vertices though they're not mathematical vertices since each vertex besides having a position also contains other data such as color or texture coordinates the first phase of the graphics pipeline is called the vertex shader the vertex reader takes the positions of all the vertices and transforms them or if you want to it can keep them the exact same way your choice once all the transformations are done the shape assembler takes all the positions and connects them according to a primitive but what's a primitive i hear you asking well a primitive is just a shape such as a triangle or maybe a point or a line each primitive interprets the data differently for a triangle it would take three points and then draw a triangle between them whereas a line would take two points at a time and draw lines between them up next we have the geometry shader which can add vertices and then create new primitives out of already existing primitives but this one is a bit more complex so we won't be seeing it for a long while next comes the rasterization phase where all the perfect mathematical shapes get transformed into actual pixels so what before was a perfect mathematical triangle now becomes just a bunch of pixels which are just kind of a bunch of squares but these pixels don't really have any color to them so here comes the fragment shader which is one of the most important shaders so the fragment shader adds colors to the pixels this depends on many many things such as the lighting or the textures or shadows at this point you might have multiple colors for just one pixel because of multiple objects overlapping so that's fixed in the last phase as is the blending of transparent objects into the final color now for the actual coding so sadly opengl doesn't provide us with defaults for the vertex in fragment shaders so we'll have to write our own but since this tutorial doesn't focus on the shaders themselves but rather the overarching process of using the shaders i'll simply copy paste the shader scene don't worry though i'll be looking at those in a future tutorial so first let's specify the coordinates of our vertices for now we're going to work in 2d so we're going to ignore the z-axis as for the x and y axis well their origin is located in the middle of the window with x pointing to the right and y pointing up now the coordinate system is normalized which means that the leftmost part of the window for x is negative one and the rightmost part of the window for x is positive one while for y the lowermost part of the window is negative one and the uppermost part of the window is positive one so now i'll make an array of data type gl float why gel float well you would probably use normal floats but these may differ in size from the floats that opengl uses so it's just safer to go with the opengl version there is a link down in the description to a wikipedia page where you can see all the data types that opengl has now i'll just name these vertices and add some coordinates now in this array every three floats will represent one coordinate i'll first add the coordinate of the left corner then the right corner and then the top corner this may look kind of complicated but that's because i chose nice coordinates such that i get an equilateral triangle but you can put any coordinates you want to as long as they're between negative one and positive one so great now we have three coordinates so let's just fit them into the graphics pipeline and get a nice triangle well sadly it's not that easy we may have the source code for our vertex shader and our fragment shader but we don't have the shaders themselves so shaders are an opengl object and these are just kind of like in the background in the memory and we can only access them by references aka a value in fact all opengl objects are accessed by a reference keep that in mind as it is very important so let's create a value aka a reference to store our vertex shader in so i'll type in g l i g-l-u-i-n-t which is the open gel version of an unsigned integer aka a positive integer i'll name it vertex shader and use gl create shader to create the shader and get the reference value and as an input you'll have to specify what kind of shader you want in this case we want the vertex shader so now that we have a vertex shader let's fit it the source code we copy pasted earlier we'll use gl shader source and the first thing we're going to give it is the reference value then we're going to specify that we're only using one string for the whole shader then we're going to point to the source code and then the last thing doesn't matter just write null now the thing is that the gpu can't understand the source code so we'll have to compile it right now into machine code so i'll just use gl compile shader and give it the reference value so great now we have a vertex shader so not just do the exact same thing for the fragment shader only replace everywhere you see vertex with fragment if you get lost just check my code now in order to actually use both of these shaders we'll have to wrap them up into something called a shader program so just like before let's create a reference value name it shader program and use geocreate program this time not specifying anything else because they're just one type of shader program and to attach a shader to the shader program you're gonna use gl attach shader first plugging in the reference to the shader program then plugging in the reference to the shader itself once you've finished attaching your shaders we want to wrap up the shader program we're going to use geo link program for that and we're just going to pass it in the shader program reference and just to keep it tidy i'm now gonna delete the shaders we've created before because they're already in the program itself so great now we're done with the shaders so you would be tempted to press run but nothing is gonna happen because we haven't done anything with our vertices we haven't even told opengl how to interpret them so let's do that now so sending stuff between the cpu and the gpu is kind of slow so when you do send stuff you want to send it into big batches these big batches are called buffers do not confuse them with the front and back buffers from the last tutorial though so let's create a vertex buffer object where we'll store our vertex data and as always we're gonna use a reference integer for it now the vbo is actually an array of references but since we only have one object we only need one so for now we're just gonna leave it like this we can create the buffer object by using gl gen buffers and giving it one as the first argument because we only have one 3d object and then pointing it to the reference now let me introduce you to the concept of binding binding in opengl means that we make a certain object the current object and whenever we fire a function that would modify that type of object it modifies the current object aka the binded object so now i'll just bind it using gl bind buffer and giving it gel array buffer and the reference of the vbo now i used gl array buffer because that's the type we need to use for the vertex buffer if you want to see the other types of buffers i left a link in the description to the opengl documentation where you can look up this function and it will tell you what other kinds of buffers there are now let's actually store our vertices in the vbo we'll do that by using gl buffer data we first specify the type of buffer the total size of the data in bytes we can just use a c plus function to get that and now we give the actual data itself so the vertices and finally we specify the use of this data this is done by typing gl underscore and now we choose between stream static and dynamic stream means that the vertices will be modified once and used a few times static means that the vertices will be modified once induced many many times and dynamic means that the vertices will be modified multiple times and used many many times you need to specify these in order to improve performance so now we continue with another underscore and again choose between draw read and copy draw means that the vertices will be modified and be used to draw an image on the screen and you can imagine what the other two do so great now we have a nicely packed object with our vertex data but opengl doesn't know where to even find it in order to do that we make use of another object called a vertex array object this source pointers to one or more vbos and tells opengl how to interpret them vios exist in order to quickly be able to switch between different vbos so let's go back and put vao in front of vbo now let's generate the vreo using gl gen vertex arrays seeing that we only have one object and pointing to the vao reference make sure you generate the vao before the vbo the ordering is very important now let's find the vao so that we can work with it now let's configure it so that opengl knows how to read the vbo we'll do that by using the function gl vertex attribute pointer first we pass the index of the vertex attribute we want to use a vertex attribute is a way of communicating with a vertex shader from the outside i'll talk more about this in the tutorial for shaders the first input is going to be the position of the vertex attribute which in our case is zero the next input is how many values we have per vertex in our case that's three because we have three floats next we're going to tell what kind of values we have we just have floats now the next input only matters if we have the coordinates as integers but we don't have that so just write in gl underscore false now we have to give the stride of our vertices which is just the amount of data between each vertex in our case since we have three floats then that is just three times the size of one float and the last thing is called the offset which is a pointer to where our vertices begin in the array but since our vertices begin right at the start of the array we're gonna give this weird pointer void so finally we've configured the vertex attribute now in order to use it we need to enable it using gl enable vertex attribute array and give it 0 because that's the position of our vertex attribute now this next step is not mandatory but it's nice to have just so you can be 100 sure you won't accidentally change a vba or vio with a function we basically just combined both the vbo and voa by binding to zero now once again make sure you have the same ordering of your functions as i do in the case of the vio in vbo because the ordering is extremely important and just to keep everything nice and clean let's go to the end of our main function and delete all the objects we've created so far and now just a few more lines of code and we'll finally have a triangle first copy and paste the gel clear color and gel clear functions into the while loop now let's activate the shader program we've created like a thousand years ago and now let's bind the vao so that we're telling opengl that we want to use this one it's not really necessary to do this because we only have one object in one video but it's good to get used to this now for the drawing function itself we're going to use gel draw arrays specifying the type of primitive we want to use triangles in our case the starting index of the vertices 0 in our case and then the amount of vertices we want to draw since we're drawing one triangle and a triangle has three vertices that's three and last but not least we make sure we swap the buffers so that the image gets updated each frame and finally after a long long journey we can finally press run and see a nice triangle pop up now i can imagine you probably feel a bit overwhelmed by the amount of information you just got don't worry though it's normal i was also a bit overwhelmed by it at the beginning so now a good way to make sure you understand most things is to add comments to all the steps and functions and rewatch certain parts that you don't fully understand yet if you want to know more about individual functions simply look them up in the documentation of opengl it helps more than you'd expect so as always i've left the link in the description to a pdf with exercises for you to do exercising and messing around with the functions really helps you learn what each of them does so now we're going to build on top of that by introducing the concept of index buffers as you hopefully remember we can draw a triangle by telling opengl to use the triangle primitive between three vertices in this case vertices 0 1 and 2. easy enough right but what about this case well here we have three triangles so that means we need 9 vertices 0 1 2 3 four five and six seven eight but as you can probably see we have duplicate vertices which means that we're just wasting memory space so let's rewrite the vertices but this time without duplicates so in this case we have vertices 0 1 2 3 4 and 5. if we give this to opengl it will draw a small upside down triangle on top of a big triangle which is not what we want in order to prevent that we need to make use of an index buffer an index buffer tells opengl the order in which it should go over vertices here it first goes over 0 4 3 then 4 1 5 and then 3 5 2. we solve the problem of duplicate vertices by simply visiting the same vertices twice using indices so now let's actually implement this i'll first add the new vertices to my array and now i'll create an array of data type g l u i n t for the indices and type in the indices we saw previously generating the index buffer is very similar to the vertex buffer so let's first create its reference value next let's generate the reference value and store it in evo specifying we only have one object in order to do something with it we need to make it current aka bind it and also specify its of type gl element array buffer now we link it to our indices array if you don't know what these inputs mean check out my previous tutorial now we unbind it but make sure you unbind it after you unbind your vao since the ebo is stored in the vio so if you unbind it before unbinding the vao you're essentially telling opengl that you don't want the vao to use your ebo and to keep things clean we'll delete it at the end of the main function the last step is to replace gl draw arrays with gl draw elements specifying the primitive we want to use how many indices we want to draw the data type of our indices and the index of our indices which in our case is zero now just press run and enjoy your triangles as always i've left the source code and some exercises in the description now let's organize things a bit since as you can see we have a lot of stuff going on in here let's start by moving the shaders into their own separate text files open up the solution explorer and create a folder called shaders in the resource files folder now let's add a new item selecting utility then text file and naming it default.word open it up and copy paste the vertex shader source code into it make sure to get rid of the one variable you have and all the quotes and slash ends now do the exact same thing for the fragment shader only name it default.frag instead of default.vert now let's create our very own shader class go to header files add new item header file and name it shaderclass.h in this file we'll declare our class and other functions related to it first let's write hash if ndef shader class h hash define shader class h hash and if this lets c plus know not to open up the file twice since that would create variable clashes now we'll need to declare a function that will read the shader text files i will not go through the details of it though since the function itself is irrelevant to opengl just know it outputs the contents of a text file as a string now let's declare the shader class which will simply be an opengl shader program that's nicely wrapped up give it a public id aka reference declare a constructor that will take in the shader source codes and two functions activate and delete once you're done with that go to source files and create a cpp file named shaderclass.cpp i'll start by including shaderclass.h and then copy pasting the filereader function link in the description to all the source code now let's write the shader constructor first let's get the strings from the text files into two variables and then convert and store them into character arrays now we just need to copy paste all the shader related code from the main function modifying it slightly by replacing shader program with id and changing vertex shader source to vertex source and fragment shader source to fragment source don't forget to also write the activate and delete functions by again copy pasting from the main function great we've made the shader class next let's make a vertex buffer class create a header vbo.h and include glad for the opengl functions now create a vbo class giving it a public id variable and a constructor that takes some vertices in their sizing bytes the size of the vertices is in the gl size iptr data type since that's what opengl uses for sizes in bytes now just add some declarations for the bind unbind and delete functions then create a c plus file vbo.cpp and copy paste the same functions as me changing the name of some variables such as vbo to id make sure to also copy paste in the code for the other functions now repeat the same procedures we've done with the vbo for the ebo making sure to replace jill array buffer with gl element array buffer and now finally the vertex array class create a header and include glad like before but this time also make sure to include vbo.h as we'll need to link a vbo to the vao create the vio class give it a public id create a constructor without any inputs and then a link vbo function that will take a vbo and the layout which we'll learn more about in the shaders tutorial don't forget to declare the usual bind unbind and delete functions once you're done with that create the vao.cpp file and include the vao header now copy paste in the relevant functions but make sure to bind and unbind the vbo in the link vbo function make sure you've written everything the same way i did great now we're done with creating all the classes all that's left to do now is to go back to the main function include all the headers we've created and replace all the default functions we had with the new functions and classes i'll also move the vertices and indices to the top of the file as i think it looks better if you press run everything should work just like before as always the final step is to add your own comments in order to make sure you understand all of this if you run into any problems the source code is in the description now let's finally learn more about shaders you can think of shaders as functions that run on the gpu since they are similar to functions they can take inputs and also have outputs so let's take a look at our default vertex shader while looking at this you might think it's c code but it's actually opengl shading language aka glsl which has a similar syntax to see the first line contains the version of glsl we are using since we have opengl 3.3 we need to use glsl 330. the second line takes an input a plus using the layout with location zero layouts help opengl with the vertex data it receives in this case we say that on the zeroth layout there is a vector data type for positions now for the main function we simply assign gl position of eq4 with all our positions plus an arbitrary one for the fourth dimension which we can ignore for now opengl recognizes the keyword gl position and knows it needs to use it as the position for the vertex you can think of this shader as outputting gel position even though it doesn't specifically do that on the other hand the fragment shader specifically says on the second line that it outputs a vic-4 color now for the main function we simply gave it a color in rgba format to use for all the vertices but instead of having one color for all the points let's give each vertex its own color so i'll start by writing rgb values after each position in the vertices array then i'll add a second layout with location 1 that takes a vector named a color but since the fragment shader is the shader that takes care of colors we need to output the colors from the vertex shader to the fragment shader to do that i'll output a vectory named color and in the main function make it equal to the a color imported from the vertices array now in the fragment shader i'll input the exact same vectory named color it's very important to give inputs and outputs the same name since otherwise opengl wouldn't know to make the link between them in the first place and the final step for this shader is to make frag color equal to color since that's what we are outputting now we should configure the vertex attribute pointers but first we need to modify a function from the vao class let's change link vbo to link atrib and add four variables num components type stride and offset now do the same in the vao.cpp file and add in the new variables as inputs to the gl vertex attribute pointer just like me so we are sending the shader an array of a bunch of bytes in order for opengl to know how to interpret all of them we tell it how to do so in the gel vertex attribute pointer function first we specify the layout location in our case 0 for coordinates slash position and 1 for color then the number of components per layout 3 in both of our cases then the type of our components gl float and finally the stride in offset the stride is the distance in bytes between the beginning of one vertex and the end of another which in our case is six times the size of a float since each vertex has six floats the offset is simply the initial offset of a layout in bytes for the coordinates that's zero because they are right at the beginning well for the colors the offset is three times the size of a float in bytes since the first three components are coordinates now let's write all of this in code by linking the attribute for coordinates and colors from our vbo to our vao now if you press run you should see that the triangles have a nice gradient of color you might be asking yourself why there is a gradient of colors even though we only specified a couple of colors well that's because if a primitive has different colors for its vertices then opengl will automatically create a nice gradient from one color to another this is called interpolation and it is used in other things besides colors as well now let's look at the second method of getting inputs and outputs within and outside of a shader this second method makes use of uniforms uniforms are sort of like universal variables that can be accessed by other shaders and can even be accessed from within the main function of the program without the use of a vao let's create a float uniform called scale in our vertex shader now let's just add to each coordinate the corresponding coordinate multiplied by the scale what this essentially does is it increases the size of our triangles so if the scale is let's say 0.5 then our triangles are 50 percent bigger one thing to note though is that you should never declare uniforms if you're not going to use them since otherwise opengl will delete them automatically and that may cause errors okay now in order to give the uniform a value we must first get its reference value in the main function we'll do that by using gel get uniform location and store the reference in an opengl unsigned integer variable called unid the function will take us inputs the reference of the shader program and the name of the uniform now in order to give it a value we must use the gl uniform function but we can only do so after activating our shader program so make sure you write it after shaderprogram.activate gel uniform is a bit of a weird function as its name's suffix changes depending on the data type we are inputting if you want to know all the variants of the function look it up in the documentation i left in the description we are going to use gel uniform 1f since we are only inputting one float and then for the inputs we'll use the uni id and 0.5 f now if you press run our triangles should be bigger as a final step i will add some error checking in the shader class since we don't have debugging for our shaders and so it would be nice to get a basic error message when a shader fails to compile if you want to know how the error functions work i left a link in the description to a wikipedia article on them don't forget to add comments to everything to make sure you understand what you've written and as always the source code and some exercises are left down in the description so now let's take a look at textures textures can be one-dimensional two-dimensional or three-dimensional but in this tutorial we'll only look at the two-dimensional textures as they're the most common type of texture so the first thing we'll want to do is to import an image into our program so that we can make it into a texture and display it in order to do that we're going to use this popular open source library called sdb to install it go to your project folder then libraries and then include now create a folder named stb and inside of it create a text file named stb underscore image.txt now go to the link i left in the description press ctrl a to select everything and then copy paste it into the text file we've just created make sure to save it and then rename it to stb underscore image dot h now create a cpp file named stb.cpp in your project source files and write the following into it so that we'll only use the things we need from this library now right click the cpp file and click on compile make sure to only do this once that's it now if you want to use the library simply include the header file into the file you want to use it in i'll do that in the main.cpp file now before we get to the texture let's make sure we have the coordinates for a square so that we can better see our texture when displayed don't forget to also change your indices and the gl draw elements function run your program to make sure you do indeed get a square if everything is all right then let's import our imaging keep in mind that square textures in powers of 2 such as 1024 by 1024 pixels or 2048 by 2048 pixels are better optimized than textures with the random number of pixels so do try to make them fit this format i'll be using this image of popcat that's 512 by 512 pixels which i'll put into a textures folder in resource files don't forget to also put the image in your project's main folder first we have to create three integer variables to sort the width and height of the image in pixels and the number of color channels it has then we'll store the image itself in an unsigned character array named white using the function sdbi underscore load and giving it the location and name of the image the pointers of the variables we created and zero that's it for importing it in easy right now let's create the texture object itself just like any opengl object we'll first create a reference variable of type glu int and name it texture now just use gel gen textures to generate the texture object giving it the number of textures you want one in our case and the pointer to the reference variable since we've created it we also want to delete it at the end of the main function we now need to assign the texture to a texture unit you can think of texture units as slots for textures that come together as a bundle these generally hold about 16 textures and allow the fragment shader to work with all 16 textures at the same time to insert our texture in the slot of a texture unit we simply need to activate the texture unit we want using gel activate texture plugging in the index of the texture unit and then binding our texture with gel bind texture inserting the texture type and its reference value since we now have our texture binded that means that this would be a good time to adjust its settings to our liking first we'll have to choose how we want our image to be processed when scaled up or down we can choose gel nearest which keeps all the pixels as they are this is preferred when working with pixel art or we can choose gel linear which creates new pixels according to the pixels nearby this generally results in a blurrier image which one you choose depends on your needs in a certain context for now i'll just go with gel nearest i'll use the gel text parameter i function to tweak our texture settings and input the type of our texture the setting we want to modify and the value we want to give our setting i'll modify both the mean filter and mag filter settings now the second setting is how we want our texture to be repeated we can choose between gel repeat gel mirrored repeat gel clamp to edge and gel clamp to border the first one simply repeats the image the second one repeats the image but mirrors it every time it repeats it the third one stretches the borders of the image and the last one simply puts a flat color of your choice outside the image note that you can mix and match them since they only apply on one axis so you could have the texture repeat on the vertical axis but only have a flat color on the horizontal axis this axes are named str corresponding to the common x y and z axes just like before we'll use gel text parameter i plugging in the texture type the setting we want to modify and the value of our setting make sure to do this for both the s and t axes if you want to use dual clamp to order you'll have to also use gel text parameter fv plugging in the texture type gel texture border color and the color of the border which should be an array of 3 or 4 floats now that our settings are complete we can generate the texture using gel text image 2d and inputting the following the type of texture 0 the type of color channels we want the texture to have the width the height 0 this is just the legacy leftover thing the type of color channels our image has the data type of our pixels and the image data itself the most common types of color channels are glrgb in glrgba the first one is for jpegs and the second one is for pngs if you want to look at all the types of color channels and pixel data types i left a link in the description to the opengl documentation if at the end of this tutorial you get an error during compiling then it might be because you specify the wrong type of color channels or pixel data type for reading your image since we've already imported the image data into the texture we'll want to delete the data using sdbi image free and also unbind the textures so we don't accidentally do something to it the last thing you'll want to do after that is to use the gl generate mipmap function plugging in the data type of the texture this will generate the mipmaps for the texture which are just smaller resolution versions of the same texture that are used when the texture is far away for example great we have the texture but we did not specify how we want the texture to be mapped on the vertices so let's do that now by adding coordinates to our vertices the coordinates of the texture go from 1 to 0 on both axis starting from the bottom left corner if you would give coordinates higher than 1 then the texture will be repeated such as in this example now that we've modified our vertices we also need to add a new layout to our vertex shader and make sure to modify our vertex buffer object since the fragment shader deals with colors we need to export the texture coordinates to the fragment shader and import them like so the last thing we need to do in the fragment shader is to create a uniform of type sampler 2d call it text 0 then equal the frac color to the function texture which takes text 0 and takes coordinates as inputs the uniform tells opengl which texture unit it should use so in the main function create a uniform just like in the shaders tutorial and assign the uniform the index of our slot 0 in this case make sure to do this after you've activated the shader program the last thing we need to do is to bind the texture object in the main function and we're done if you press run you should see a majestic texture appear on your square but wait it's upside down well that's because opengl reads images from the bottom left corner to the top right corner while sdb reads them from the top left corner to the bottom right corner so all we have to do to fix this is to write sdbi said flip vertically on load true before we load in the image file to wrap it all up i will create a custom texture class just like in tutorial number four don't forget to add comments to everything you've written so that you can make sure you understand what's happening the source code and some exercises to test your knowledge are in the description as always now let's finally leave the boring 2d plane and ascend to three dimensions but first a little correction for my past videos in the vio and texture class i forgot to make the vbo and shader inputs references so just add an and sign like so now for the main part as i have mentioned before opengl restricts our coordinates to normalized coordinates so in order to bypass this and enrich our coordinates with a wider range for three dimensions we can contract and expand the different coordinates using matrices if you don't know much about matrices then you should still be able to somewhat follow along but i highly recommend you watch street blue one braun's linear algebra playlist first as it will help you later down the line in any case we'll be using this nice little library called glm for our matrices so go to this website that's down in the description and click on download go to your libraries folder then include now open up the zip file go into glm and now extract the folder named glm into your include folder once that's done go to your project and include the following parts of the library that's it we can now use matrices easy all right theory time so in order to get a nice 3d image in perspective we need to apply different matrices to different coordinates let's take a look at these types of coordinates first we have the local coordinates these are the coordinates whose origin is the same as an object's origin these are usually located at the center of an object but that's not always the case then we have the world coordinates whose origin is at the center of the world these coordinates usually contain the location of other objects next we have the view coordinates which have the same origin as a camera or viewpoint notice that these do not yet account for perspective perspective is added in the next set of coordinates which are the clip coordinates these are essentially the same as the few coordinates except they clip aka delete any vertices outside the normalized range and can also account for perspective and the final coordinates are the screen coordinates where everything is flattened out such that it can be viewed on your screen now in order to move from a coordinate system to the next we make use of matrices there are three main matrices we make use of the model matrix which takes local coordinates to world coordinates the view matrix which takes world coordinates to view coordinates and the projection matrix which takes view coordinates to clip coordinates the final transformation from clip coordinates to space coordinates is done automatically all these matrices we are applying are 4d but we won't have to actually write them ourselves since glm can take care of that so let's create the model matrix we start by creating a variable called model of type math4 and equaling it to the identity matrix which is simply math4 1.0 this is called initialization and it must be done since otherwise the matrix is full of zeros and any transformation we would apply on it would result in the same empty matrix initialize the other two matrices as well the model matrix can remain ocities since our object is in the center of the world coordinates and that's fine for now but right now our camera is also in the center so we want to move back and also a bit up but it's not really us doing this but the world itself we are essentially moving the whole world around us instead of us moving around the whole world in any case to do this we are going to use the function translate plugging in our view matrix and the vectory which will indicate in which direction and how much to move the whole world let's just move it down 0.5 and forward 2.0 note that the z axis is positive towards us and negative away from us and for the projection matrix we'll use the perspective function plugging in the field of view in radians the aspect ratio of our screen and the closest and furthest point we can see in other words if something is closer than 0.1 units to us then it will be clipped and if it's further away than 100.0 units it will again get clipped you can see this in games when you are sometimes able to see through walls or characters while bumping into them or when objects disappear at far away distances notice that i've used the function radians to transform my degrees into radians let's also create two variables for the width and height of our window so we can easily modify them from now on now all that's left to do is to import these matrices into the vertex shader we can of course do this by using uniforms so let's simply declare three uniforms of type mod 4 and assign them values in the main function like so we first input their location then one then gel false since we don't want opengl to transpose them and finally we need to use the function value ptr which we give the matrix to this is done so that we can point to the matrix object itself not the data in it don't forget to have the shader activated before assigning these values then go back to the vertex shader and equal gel position to the projection matrix times the view matrix times the model matrix times avec 4 whose 3d coordinates are a pos and the fourth dimension is 1. now if you run the program you'll notice that things look different though you can't really see the perspective since the plane is flat and parallel to us so let's make a pyramid i'll start by setting the coordinates colors and texture coordinates then the indices and finally i'll modify the gel draw elements function which i'll generalize like so so we don't have to bother with it again i'll also use this brick texture which you can find in the description now if you press run we should see our pyramid but sadly it just looks like a triangle so let's make it spin so that we can see it in its 3d glory we simply have to apply the rotation function to the model matrix giving it the matrix we want to modify the rotation in radians and the axis we want to rotate it about in this case that would be straight up so y equals one then just create a simple timer that will change the rotation by half a degree every sixtieth of a second if you press run you should see the pyramid spinning around but you'll also probably notice some weird glitches that's because we're not telling opengl to account for the depth of these triangles so it's unsure which triangle to put on top of which one to fix that we simply need to enable depth testing once using gel enable chill depth test and when we clear the gel color buffer bit we should also clear the gel div buffer bit and now we're finally done and have a nice 3d spinning pyramid don't forget to add comments to everything you've written to make sure you understand it and as always the source code is in the description so now let's add a camera class to make our lives easier first start by creating a header file named camera.h preventing c from opening it twice and including all of the following for the camera class we'll put everything in the public section starting with the victory for the position of the camera a victory for the orientation aka direction of the camera and another victory for the up direction now we'll also want the place to store our width and height in the last two variables will be the speed of the camera and the sensitivity of the camera when looking around i'll also make a very simple constructor a matrix function which will create and send the view and projection matrices to the shader and a function to handle all the inputs now create a new cpp file named camera.cpp and include the camera header for the constructor we'll just assign values to the width height and position for the matrix function we first want to initialize our matrices and then we'll make use of the lookout function the lookout function takes the position from which you want to look at something the target you want to look at and the up vector in our case this would be the camera position the camera position plus our orientation and the up vector the orientation vector is always of length 1 and we'll be using it as the vector we want to look at so to say for the second matrix we'll just do what we did in the last tutorial then we'll simply export our matrix to the vertex shader good now let's go to the main function and include the camera header let's delete the old stuff including this scaled uniform we had left over from a few tutorials ago and create a camera object named camera giving it the width and height variables in the position 002 now in the while loop delete the old stuff again and use the matrix function naming the uniform cam matrix the last thing we need to do before getting back to what we had last tutorial is to go to the vertex shader and replace all the previous uniforms with a cam matrix uniform now if you press run you should have the exact same thing we had last time except for the spinning pyramid and the fact that everything is now much cleaner in the main function now let's just add some inputs to the camera i came up with these on my own so they might not be the best but they work just fine i'll start off with the wasd controls for which we'll simply need to add or subtract from our position the speed times the orientation or the right vector which we get by doing a cross product between the up vector and orientation vector make sure to use ifs and not else ifs since we want to be able to modify multiple of these at the same time in order to go diagonally now i'll just add space for going up and control for going down and make it so that when i hold shift my speed increases and when i release it my speed goes back to normal now include the inputs function in the main while loop and then if you boot up the program you'll see that you can move about though you can't really look around so let's add some mouse controls so i'll do an if and say that if i hold down the left mouse button i want my mouse to disappear and if i release it i want my nose to appear again now i'll create two doubles for the x and y positions of the mouse and assign them values using the function glf w get cursor position plugging in the window and coordinate references then create two variables for the rotation of our orientation vector and make them equal the sensitivity of the mouse times one of their coordinates but make sure to adjust both coordinates since they normally start in the top left corner but we want them to start in the middle of the screen and be normalized so subtract from them half the height or width and then divide them by the height or width now in order to prevent the camera from being able to do a barrel roll we want to preemptively calculate the new position of the orientation around the x axis and if the angle between the new orientation and the up or negative up vectors is not less or equal than 5 degrees then we assign the new orientation to the orientation then we also apply the y rotation to the orientation which we always apply since we want to be able to spin around as much as we want at the end of the function we also want to set the position of the mouse to the center of the screen so that the mouse doesn't end up wandering in a corner and stopping us from freely moving the camera now if you start the program you'll notice that if we try to look around our camera jumps we can easily fix this by adding a new variable to the header named first click and then in the inputs function in the mouse part we want to check if it's the first time we press the leftmost button since we've looked around if it is then we reset the mouse position to the center and make the first click variables false the last thing we need to do is to make the first click variable true when releasing the left mouse button now everything should be running smoothly as always the source code is in the description and don't forget to add comments to everything to make sure you understand it so now we'll add some lighting to our scene first we'll slightly modify the camera class so that we can efficiently use the cam matrix on multiple objects and make sure the new camera functions work properly in the main function next we'll add a cube that will serve as our light source watch my previous tutorials if you don't know how to do this especially my coordinates video if you don't know what the light pose vector and light model matrix are for then if you start out the program you should see your main object in a small cube that's completely white so as you may have noticed in that nice little game called real life light can have multiple colors usually it's white and because of that it shows the true colors of objects so to say but if a light source is let's say red then all objects will appear reddish we can simulate this by multiplying the color of an object with the color of our light both of which are in rgb for example if our light has the rgb values of 1 0 0 then when it's multiplied with the color of the object the green and blue parts will become 0 while the red part will stay the same since it's multiplied by one this simulates what happens in real life as only the red color is reflected back to us even though the object is actually orange so let's make a vic for called light color and make it completely white then we'll want to export it to both the light fragmentator and the fragment shader of our object in the light one we'll simply use it as the color of the object while in the object shader will multiply it by whatever you currently have in your frag color we are now able to simulate the color of the light hitting the object so next we'll want to simulate the intensity of that light you've probably noticed that the higher the angle between a surface and the source of light is the less intense color is on that surface this is the seen on a sphere where you can clearly see the gradient of the intensity along the curve of the sphere in order to get this angle and calculate the intensity we'll need a position of the light which we already have and a way to know the slope of the surface the traditional way of doing this is to represent the slope of the surface by a normal vector so so far we've had coordinates colors and texture coordinates inside the vertex now we'll also want to add normals normals are unit vectors aka vectors of length 1 that help us calculate how light should act on a certain object this can be either perpendicular to the surface of one triangle called phase normals or arranged in a different way such as being perpendicular to the plane created by all the adjacent vertices for example called vertex normals if you go for the first option you will get what's called flat shading where all your triangles are clearly visible if you go for the second option things look a lot smoother and nicer but which one you choose depends on your mesh and on your artistic style since we have a pyramid we'll go with a flat shading since the smooth shading looks weird on very angular geometric shapes such as cubes or pyramids now since the normals will be different on each side of the pyramid we won't be able to fit all of them in just five vertices like we have here we'll have to duplicate some vertices in order to change their normals so i'll just do that plugging in my pre-calculated normals for each face don't worry if some of your normals are not unit length it does not matter since we can just normalize them in the shader later don't forget to also change your indices the gel draw elements function in case you haven't automated it like me and add a new attribute to the vao so now for the vertex shader we'll include the normal layout and also export it to the fragment shader along with the current position which we'll use to calculate the direction of light for consistency purposes i'll also import the model matrix for the pyramid now in the fragment shader we import the normal and current position and the light position uniform then in the main function first normalize the normal vector and then get the light direction vector by simply subtracting the current position from the light position and normalizing it we now have the two vectors we need the lighting we're implementing now is called diffuse lighting so i will create a float called diffuse then in order to make it such that the larger the angle between the two previously calculated vectors the less intensity the light has we can simply use a dot product between them which is equal to the cosine of the angle between them since they are normalized now this can also be negative and since we don't want negative colors we'll take the maximum value between this and zero all that's left to do is to multiply the diffuse float with the color of the object a texture in our case if you now boot up the program you will notice that we have some nice lighting but that the areas not touched by a light are extremely dark this is realistic in our case where we only have one object in or in a vacuum but generally speaking it's not realistic a slide will bounce from other objects that are around onto our object this bouncing is expensive to calculate and pretty advanced so for now we'll simulate it by simply giving a base value of brightness this type of lighting is called ambient lighting so just create a float called ambient and give it a value like 0.2 and then add it to the diffuse value in the front color boot up again and notice this looks a bit better the last type of lighting we have to add in order to complete our lighting algorithm which by the way is called phong shading is the specular light this is dependent on the angle you look at an object from so you'll need to import the camera position into the fragment shader first now create a float specular light which will represent the maximum intensity of this type of light now we need a view direction which should just be the composition minus the current position normalized and the other thing we need is the direction of the reflection which we can get by using the reflect function plugging in the negative direction of the lighting since we want the vector to be towards the plane not away from it and the normal then create a float spec amount which will store how much specular light we have at a certain angle with our camera so the larger the angle between the fuse direction and reflect direction is the weaker we want the specular light to be so just use a dot product like before and take the max between this and zero then we'll want to raise this to a power the higher the power is the more point-like and defined the specular light will be i will go for a lower power such as 8. now create a float named specular and multiply the specular light with the spec amount last thing to do is to add the specular float to the diffuse and ambient floats now you have some nice decent lighting for your scene don't forget to add comments to everything you've written to make sure you understand it and as always the source code is in the description today i'll show you how to improve specular lighting with the use of specular maps i'll start by slightly modifying the texture class so that it's easier for us to assign multiple textures to the same shader then i'll delete the pyramid we had and simply create a flat plane i'll also import this wood texture in the specular map you can find this in the description i'll also assign the wood texture to the plane now as you can see the specular light is just sort of flat areas that shouldn't reflect much light such as the cracks in the wood still somewhat do so and the areas that should reflect a lot of light such as the smooth tops of the wood don't really do so in order to fix this we can make use of something called a specular map these types of maps are black and white textures that tell the program where specular light should appear and how strong it should be at that point the whiter a pixel on the specular map is the more specular light you'll get and the blacker a pixel is the less specular light you'll get the idea is really simple so to implement it we must first import a specular map into our program make sure to import it using gl red not gl rgba since it only has one color channel not four then simply export it to the shader and bind it in the main function now import the texture in the shader and in front color multiply its red value by the specular light and add that to the other texture which is only multiplied by the diffuse and ambient lighting now that's it for the specular light now i'll just change the value to make the light look better on the wood and if you then boot up the program you should see that the specular light looks much better and it makes the whole texture look better overall as always the source code is in the description now let's take a look at different types of light there are three main types of light point directional and spotlight point lights illuminate the environment in all directions but the intensity of their light withers as it gets further away this is the type of light we've used still now except we didn't incorporate the loss of intensity so let's quickly do that first let's create a vector function named point light and copy paste into it all the things from the main function and make frag color equal the output of this function so the intensity of light has an inverse square relationship to distance in real life but in computer graphics we use a somewhat more complicated equation to better control the properties of the light instead of having one over distance squared we have one over a quadratic equation with respect to distance this quadratic equation has two constants a the quadratic term and b the linear term we can modify this to change how fast the intensity dies out and how far the light reaches there is no general perfect number for these constants you just have to play with them and see what works for your scene just know that usually the numbers are smaller than one if you want your light to reach somewhat far so to implement this we first need a distance to the light the distance is simply the length of the vector that comes from subtracting current position from light position since we already use this in our diffused lighting i'll create a variable named light vector so that we don't have to calculate it twice now we can get a distance by simply using the length function lastly we just write out the equation with the variables and apply it to the specular and diffuse lighting here are some examples of the results i got using different constants the second type of light is the directional light this is usually considered to be so distant from your scene that the light rays it emits are essentially parallel to one another just like the rays of the sun this has no dimming and is actually the easiest of the three to implement we just need to copy paste part of the code from the point light and instead of calculating the light direction based on some positions we simply give it a constant y direction as a normalized vector 3. just note that it should point in the opposite direction you want it to affect due to the way i wrote the code here so if you want the light to come from above it should point up not down and if you press run you'll see everything is lit up as it should be the last type of light is the spotlight the spotlight only lights a conic area just like a flashlight or a disc clamp for this we'll copy paste the point light code again and begin by adding two floats which will represent the cosine values of two angles the first angle is the angle between the inner cone and the direction of the light and the second angle is the angle between the outer cone and the direction of the light we make use of these two cones in order to have a nice gradient between darkness and light areas since if we only made the use of one cone we would just have a direct cutoff from light to darkness i wrote a cosine value directly in order to save computational power do not write the inner and outer cones in terms of angles since that will require extra calculations and it will slow down the program now we need to calculate the angle between the current port that is being lit in the central direction of the light using a dot product this will give us a cosine value which is perfect to compare with our own cosine values the last step is to get the intensity of the current spot which is done using this formula make sure to also clamp it between 0 and 1 so you don't get weird values and lastly just multiply the diffuse in the spectral lighting by the intensity variable start the program and you'll see that the light is restricted to one area don't forget to write comments for everything you've written to make sure you understand it and as always the source code is in the description in this tutorial we'll wrap up all the classes we've made till now and a big part of the code in the main function into a mesh class that will also serve as a stepping stone for importing models in a future tutorial first a rough definition of a mesh a mesh is a data set that almost always contains vertices often contains indices and sometimes contains textures as well and they are generally used to create 3d models so let's create a header file for the class first we'll want to include the string library and then the vio ebo camera and texture classes since we want the three of our classes dependencies to look like this now for the class itself i'll declare three vectors named vertices indices and textures note that these are c plus plus vectors and not glm vectors we are using vectors instead of arrays since we can't know the size of our arrays so it's best to keep things flexible in terms of storage as you can see we get some errors but don't worry we'll fix those after we finish writing everything in here next we also want to store our vio since we'll be initializing it using this class then for the constructor we'll simply input the vertices indices and textures and lastly we'll make a function draw that will take in a shader and a camera now for the errors let's go to the vbo class if you've been paying attention you probably would have noticed that our vertices vector contains a structure named vertex but we don't have one so far so let's create one right here i'll simply name the structure vertex and give it three vectories for the position normal in color and then a vector for the texture coordinates having our data packed in such a structure is nicer than simply having all the data in one array at least in my opinion don't forget to also include the glm library though now we need to patch up the vbo class constructor to accept the vector of vertex structs instead of an array so let's also include the c plus plus vector and since we're using vectors instead of arrays we can now also get rid of the size input since we can calculate that inside the class so in the vbo.cpp file we can replace the size by vertices.size times size of vertex and for the data we just write vertices dot data if you return to the mesh class you should have no more errors now let's do what we did for the vbo class to the ebo class as well replacing the array with a vector now for the main part let's create the mesh.cpp file insert writing the functions for this class let's start with the constructor by simply assigning the inputs to the variables of the class then copy paste the initialization of the vio from the main function and modify the variable's names slightly and also the way the attributes are stored we want the position first then the normals then the colors and finally the texture coordinates since we've modified this we should go to the vertex shader and rearrange the new order of the vertices sections properly and then do the same thing in the fragment shader as well it's not mandatory to keep them in this specific order when importing or exporting them but it just looks nicer to have them in the same order everywhere though it is important to have them in the proper order when choosing the layouts in the vertex shader now let's continue with our mesh class by writing the draw function we'll start by activating the shader and biting the vio as usual now we'll have to do something a bit more complicated in order to properly load the textures since we won't always know how many textures a mesh has so let's create two unsigned integers that are equal to zero and are named num diffuse and num specular this will keep count of our two types of textures then we want to go through all the textures where we'll have a temporary string called num and another temporary string called type which will store the type of the current texture the problem is that our textures don't store their type yet i mean they do it's just that they store a different kind of type in any case we'll want to modify our texture class by changing the type variable to a constant character array and so also the type of the input in the constructor then in the texture.cpp file we'll want to make all the text type equal gel texture 2d since 99 of the time that's what we'll be using now let's go back to our mesh.cpp file and create some checks we'll check if the type is diffuse and if it is make the num equal to a string of num diffuse and then increment it by 1. then we'll do the same check for specular and increment num specular now we want to take care of the uniform using our function text unit plugging in the shader then a c string composed of our type plus num and the unit of our texture then simply bind the texture so our uniforms will be named diffuse 0 diffuse 1 diffuse 2 specular 0 etc so in our fragment shader we want to replace our text 0 uniform by diffuse 0 and our text 1 uniform by specular 0. then again go back to the mesh.cppp file and send our composition and count matrix uniforms and also write the gl draw elements function all that's left to do now is to modify our main.cpp file first we can delete all the includes and only include our mesh class then we'll have to modify the vertices array and after that in the main function make an array for our textures as well delete the vio initialization and create vectors for our vertices indices and textures using the arrays from before then create a mesh named floor with the vectors we've just created do the same for the light except we don't have any textures on the light so we'll just place the same textures as a placeholder in the while loop we can just delete all the drawing stuff for the light and floor and replace them with our draw function and finally we just need to get rid of some of the deletes at the end of the file if you press run everything should now be fine if you have any errors go over the video again or check out the source code in the description as always you should add comments to everything you've added to make sure you understand what's happening in this tutorial i'll show you how to import 3d models into your opengl application by building a very basic importer this tutorial will also be longer than usual and only have visible results at the end so please have patience and pay attention to what i'm doing otherwise you might end up with a chain of errors by the end a small note first as you may know when it comes to storing images the industry is pretty standardized with the use of pngs and jpegs but when it comes to 3d models there are dozens upon dozens of file formats many of which are proprietary this diversity of file formats makes it a lot more difficult to export and import models from one program to another in the spirit of standardization i will use the gltf file format for this tutorial as it is made by the same people who've made opengl the chronos group and it seems to be a promising file format that could become the standard in the future i left a link to a nice article on this by the developers of the godot engine in the description now for the actual tutorial gltf makes use of the json file structure so the first thing we'll need to do is to install niels lofman's json library from github to be able to parse json files that does it for the libraries now we can create a header file for our model class a model being a group of meshes then we'll make a constructor that will take in the name of a file and the draw function that will take in a shader and a camera in the private section we'll want to store the name of our file a vector of bytes with all the data of the model and a json object which i'll explain more about in a moment now for the model.cpp file let's read the gltf file in the constructor using the same function we used to read the shaders then we parse the text and store it in the json variable so json files work like dictionaries within dictionaries dictionaries have keys and values associated to those keys if you give a dictionary a key it will point you to a certain value this json object abstracts the gltf file into such structures so let's just store our file and then equate our data variable to a function get data we want this function to get us a vector of points from an external binary data file so let's create a string named byte sticks to hold the raw text to get the location of the file we can look at the buffers key which will point us to an array where we want to look at the first element which will again be a dictionary so we again want to use a key this time the uri key this uri gives us the name of a dot bin file which contains the binary data then we just get the text put it in a vector and return it so now that you have your first taste of the json file let's take a closer look at it the file has dictionaries which contain arrays of other dictionaries thus making it a sort of tree but it's not a very nice tree since many branches will contain indices that will point us to other branches so things will get entangled pretty quickly in order to avoid confusion in such a situation i like to start from the leaves and work my way down to the root of the three keep in mind that i will be taking some shortcuts in the gltf file since otherwise this importer would become too complicated with that out of the way here is a simplified view of one of the main branches of the tree and the one we care most about at the top we have our data that's stored in the buffers but to know which parts of it we should read we need to take a look at the buffer views then again these will only tell us where to look not what to look for in order to know what types of data types we are reading we need to look at the accessors so great the accessories tell us that we are reading an int or maybe a fake tree for example the problem is that they don't tell us what these are used for a vectory could be both a position and a normal to find out which is which we need to once again take a step back it's just that this time we'll have to split in two if we look at the attributes then we know which accessors are part of which vertex attributes aka positions normals texture coordinates etc if we look at the primitives we can find the accessor of the indices of our mesh keep in mind that the attributes are pointed to by the primitives these also point to materials which point to textures but we'll ignore that since materials often have pbr data and since we haven't learned about that yet we'll skip directly to the textures ignoring the materials if we get back to our main branch the primitives are pointed to by the meshes which are again pointed to by the nodes and lastly the scenes in our case we'll have one scene 99 of the time so we can ignore those and just focus on the notes which will contain pointers to all our meshes don't worry if you don't fully understand what i just went through it's normal to not fully comprehend it from the start you should get a better idea of it when we write the code so let's start with the leaves by assuming we get an accessor node let's declare a get floats function and get indices function then we'll also want to declare three functions that will take the array vectors outputted by the get functions and group them into glm vectors so we'll first want the buffer view the count the byte offset and the type from the accessor notice how i use the value function instead of brackets to get the byte offset in buffer view the brackets will simply give you the value of the key while the folded function will give you the value of the key if it exists otherwise it will give you the second input these two parameters are not always found in gltf files so i want a backup value for them in case they do not exist while the other two always exist so i don't need any backup to see which of these are always found and which or not check out the specification of gltf on github link in the description now using the index of the buffer view we got we want to create a json variable for the buffer view dictionary from which we'll get the byte offset now to explain all the parameters we got the buffer view index simply pointed us to the correct buffer view as mentioned a second ago the count tells us how many floats we have to get the accessor byte offset tells us from what index to look at the data with respect to the beginning of the buffer view and the byte offset tells us where that buffer view starts the reason we have two different offsets is that sometimes two different accessories may share the same buffer view for example we could have a vector3 buffer view which is shared by a positions accessor and a normals accessor and finally the type tells us how we should group the floats it can either be scalar vector vectory or vect4 using this type we'll want to get the number of floats per vertex then in a for loop go over all the data we'll store the bytes in a temporary array which will then using the function mem cpy to transform the bytes into a float which will then store in the variable value and push it in the float fake vector at the end simply return the float veck now i'll do the same thing for indices except this time we won't check the num per vert since indices are not stored in vertices instead we'll check the data type of the index itself and according to that read it differently from the data the index can either have type 5125 which is an unsigned integer type 5123 which is an unsigned short or type 5122 which is a short now for the grouping functions we'll simply go over all the floats in the float vector and group them in their corresponding glm vectors really easy stuff using the get floats function we'll be able to get all the attributes of a vertex so the next logical step would be to combine those attributes in a vertex so let's declare a function for that which takes the vertices normals and texture coordinates and combines them into a vertex which will be put in a c plus vector very straightforward stuff notice that i did not include the colors in here since even though we declared them in our vertex they are actually very uncommon in 3d models so we can ignore them we now have the vertices and indices all that's left for a complete mesh is the textures but before we take care of those we should first modify our texture class a bit first we should remove the last two inputs of the constructor since we'll atomize the first one and the second one is the same 99 of the time for the first one we'll simply check the number of color channels a texture has and assign a corresponding color type to the texture then for the second one we just replace it everywhere with gel unsigned white so now let's create a get textures function we want to skip directly to the images key which will give us an array of your eyes here we loop over all of them while keeping track of the unit of each texture then if the texture contains base color in its name it's a diffuse texture if the name contains metallic roughness we can somewhat use this texture as a specular map keep in mind that this way of checking the type of a texture is not ideal and may differ from model to model also the reason the metallic roughness just sort of works for a specular map is because it's something that's meant for pbr the problem with this function though is that it can load the same texture multiple times which we don't want to do since it will use up a lot of ram to prevent that we want to create two variables loaded text name and loaded text we'll use loaded text name to determine whether or not we've loaded a texture and loaded text to store the textures for all meshes to use then in the function we'll add a skip variable which will become true if the texture we are currently trying to load has the same name as a texture in loaded text name if it becomes true we don't load it anymore instead simply pushing an already loaded texture to the textures vector we can also replace the unit by the size of the loaded text vector as well this does it for our textures we don't have all the elements needed for a mesh so let's make a load mesh function that will take in as an argument the index of a mesh we want to load we'll also want to create a meshes vector to store all the loaded meshes into so first we want to get the indices of all the accessors of the vertices then we'll simply use our get floats and group floats functions for the attributes of the vertices after that we assemble our vertices and get our indices and textures then we just create a mesh with these three vectors and push it to the meshes vector now we can load multiple meshes by simply iterating over them the problem is that we don't know where to place those meshes so they will likely all be within one another at the origin in order to know where to place them and how big they should be we must extract the matrix transformations from the gltf file and we can't just get the matrix of each mesh but must instead keep track of what children each mesh has in order to multiply the matrices of each mesh together all the information for the matrices and the relationship between meshes is in the nodes so let's create the function named traverse node which takes the index of a node and the matrix making it half the identity matrix as a default value then let's also create vectors for different types of transformation matrices we have translations which move the mesh rotation switch rotated scale switch change its size and matrices which are a combination of the offer mentioned but we can't know if the first three will be used or just a matrix so we need to account for both we must first modify our mesh class by adding these transformations to the draw function as inputs with default values that don't change anything and then sending them to the shader at the end of the function don't forget to also create uniforms for these transformations in the vertex shader and multiply them like so i put a negative sign in front of the rotation matrix since that's what i found to work it might be a bug in my code or the fact that glm and gltf use different rotation standards aka clockwise versus counter clockwise and since we're in the vertex shader we also need to rotate the texture coordinates by 90 degrees which we can do with this matrix this again either has something to do with the bug in the code or different standards what's important is that it works decently well now for the function itself we'll want to get a current node as a json object and then extract the transformations if they exist notice how for the rotation quaternion i get the indices in the order 3 0 1 2 instead of 0 1 2 3. this is because gltf encodes quaternions as x y z w while glm uses them as w x y z again just standardization problems now we want to combine all of these transformations including the matrix we get us an input then we check if we have an index to a mesh and if we do we push all the transformations and load the mesh after that we check if the mesh has any children and if it does we want to apply this same function to all of them while also giving them the current matrix we have so this function is recursive and so we'll split along the parts of the tree till it gets to all the nodes now we can write this function in the constructor and give it 0 as an input the last thing we need to do is write our draw function and that's it for the model class in the main.cpp file we just have to include the model class and delete all the old mesh related stuff then get some models create a model object and draw it in the main while loop here are some examples of models i've been able to load in i've left some models that should 100 work in the repository of this tutorial on github so if you're getting some errors check those models to see if you have a problem with your code or the model you're trying to load in again keep in mind that this importer is in no way complete and is in fact very basic so don't expect to be able to load in any model you find on the internet i find that a decent amount of models from sketchfab.com work pretty well especially if they don't have too many meshes that's it for this tutorial it was a really long one but also a very rewarding one if you ask me as always don't forget to add comments to everything you wrote to make sure you understand it and the source code is in the description bye
Info
Channel: freeCodeCamp.org
Views: 763,793
Rating: undefined out of 5
Keywords:
Id: 45MIykWJ-C4
Channel Id: undefined
Length: 106min 23sec (6383 seconds)
Published: Tue Apr 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.