 So now let's look at example 2.1. Hello triangle, which is just going to draw a single triangle and By the way, this other window that pops up. This is the one we're creating with glfw and drawing into This here is where any console output is going to appear and we don't have any console output yet There is of course a way to disable this other window if we don't want to draw it But for now we're going to we're just want to keep it around So that we can see any console output indicating errors and perhaps other information down the line Anyway, so the code for this well in the render loop. It's quite simple We just have these three new lines But there's quite a bit of boilerplate that we have to set up for the drawing of the triangle as we'll explain So in modern open gl effectively what you're doing is you're setting up a so-called shader program which consists always at least of a vertex shader a piece of code that processes all the individual vertices and Generally also will include a fragment shader though That is technically optional and fragment in open gl terminology refers to the pixels So the vertex shader is going to take some vertex data and spit out some vertices in clip space From there the vertices are passed on to an optional geometry shader where it can Consider the vertices that make up a primitive like a triangle or a triangle strip It can consider them as a whole and do some kind of transformation or even eliminate them from rendering But that's optional There's also optionally after that a tessellation shader, which does kind of similar business You can add in more vertices, but in a different way, which is useful in some more advanced circumstances Particularly for level of detail scaling so for things further in the distance You don't want them to render when they get close You don't want to fill in more detail, but that's a whole topic. We probably will never get to it's a fairly advanced thing and Then after the vertex shader geometry shader and tessellation shader You then have the fixed function part of the whole graphics pipeline in which the so-called primitive assembly is performed The the vertices are constructed into their ultimate triangle form and then the individual triangles are rasterized And the rasterization is where it figures out what pixels to draw precisely and with what values as inputs So the fragment shader for each rasterized pixel is getting some vertex information And this job mainly is to spit out a color value to determine what color that pixel should be and Again, most commonly we're just going to have a vertex shader and then a fragment shader and in this case They're going to be very simple Here's their text which we'll talk about in a minute. They're written in a language called GL SL Graphics library shading language, which is really quite simple. It's a C like language That's stripped down in a few respects like for example It's not even a concept of pointers and has a few particulars that make it convenient to work with vectors and matrices and Do some common graphics math operations? But we'll discuss those things as they arise. Anyway, zipping back down So we're going to define a shader program which in this case is going to consist of a vertex shader and a fragment shader That has to be compiled and linked as we'll show and Then here GL bind vertex array this VAO is simply just an int value Which is representing a handle to a so-called vertex array object, which kind of Confusingly does not directly contain information out vertices. Those are stored in what are called vertex buffer objects But the vertex array object has reference to one or more vertex buffer objects, which we'll also create later And so when you see a GL bind function that's saying that open GL Is setting a global variable in its internal state that determines what subsequent calls operate upon so in this case GL draw arrays it is going to draw the data in the VAO, which is currently bound Using the program which we have currently set in this case this shader program and when you call draw arrays That's where you specify What the vertex data is what are they represented in this case? It's gonna be triangles where every three vertices represent a single triangle The three here that indicates the number of vertices we're going to render and we're drawing a single triangle which is of course made up of three vertices and Zero this is just an index into the array where we're going to start in this case We want to render all of the vertices in the array. So we're going to start from the first at index zero Now actually because we're going to be rendering the same thing every frame We could just move these two calls use program and bind vertex array We could just do that before the loop because it's not going to change subsequently. It's going to always be the same bindings But say if I had a different shader program to execute and possibly a different vertex array object And I wanted to issue another draw call in my frame say below or before Then I would have to do this binding for both separate calls each time in the loop Because you only have one program in use at any time and you only have one vertex array bound at any time And that affects what draw arrays will do when it's cold Now we can also actually unbind things like say you could make this call with zero for the handle to effectively unbind any vertex array But generally there's not much reason to do that as far as I understand and there's definitely not any reason to do so here So it's commented out Okay, now let's back up and see how we actually create the shader and compile it There's actually quite a lot of business involved here We'll look at the actual shader code very last thing. That's actually very simple But first how do we compile them and get our program that we can use? Well here first for the vertex shader you call create shader Specifying what kind of shader you're creating you get this handle int that we will use in subsequent calls GL shader source. This is where we set the actual code associated with this vertex shader handle so we specify the the handle and in this case we just have a single String of vertex shader code, but we could have multiple that makes up our single shader And so this one here is indicating the count of strings We're passing in and the value it expects is actually not a not a char pointer, but a char pointer pointer That's why we use the ampersand here on our on our string If we had an array of strings it would be an array of char pointers and so it'd be a char pointer pointer value That's why we're passing in a double pointer and lastly this could be an array of ints Which indicate the length of the strings, but when this is null it assumes the strings are null terminated So we don't need to pass in a length next thing we compile the shader And yes, it is strange that for your shader code You are including the source as text with your program in your shipped program, and then it's being compiled at runtime This is a little problematic in some ways Probably because it means that you're paying the cost of compilation at runtime Except shader code typically in even complex programs Your shader code is typically not that large So that's usually not the main concern more problematic is that the compilation is being done by the graphics driver And it has been the case that quite annoyingly the graphics drivers do have some bugs in their compilers So they don't produce valid code or they behave differently from driver to driver and that creates headaches So this is not ideal. There is a more recent solution for OpenGL There's a new format of intermediate code representation for shaders called SPIR-V SPIRV, I don't know how it's pronounced SPIR-V Anyway, it's not a fully compiled version of shaders, but it is Pre-processed by compiler into an intermediate form and so it's less reliant on Compiler code within the drivers and it cuts down on how much compilation work has to happen at runtime, etc So that's the story there, but anyway, we have to compile our shader and It's possible, of course that the compilation fails for some reason because maybe our compiler code isn't valid So we're going to call get shader IV and ask about the compile status Indicating the vertex shader handle success will be set to zero if there was some kind of failure And so we're going to want to call get shader info log to get information about what went wrong Notice we've created this char array info log of 512 bytes So effectively we're saying that we can only read errors up to a 512 bytes long And then we're taking that error and printing it out to standard output Now for the fragment shader Exactly the same deal except notice when you say create shader you specify fragment shader rather than vertex shader It's all the same logic. We set the source compile the shader Check for an error message see if it compiled successfully So now we have our fragment shader and vertex shader handles with their successfully compiled shaders But now we need to link them together. We need to create one Shader program and so we call create program gets us back a handle for a new shader program We attach the shaders to the shader program. We're touching the vertex shader and also the fragment shader After attaching then we actually do the linking with link program Check for link errors that might have occurred and lastly once we have our compiled program Here shader program. We don't actually need the vertex shader and fragment shader objects We can dispose of those and generally we should so let's delete them with delete shader Okay, just to refresh you now. So now we have this shader program and we're done with that part But what about VAO here? We need to define this and get a vertex array object well First we just have this array vertices and array of floats and as we see in the vertex shader We're not going to be doing any transformation We're just gonna be taking these vertices verbatim as they are defined in this array and passing them on Straight to the fragment shader and so they're going to be expressing clip space where say For this first vertex. This is going to be a point which is negative point five negative point five negative point five for X Meaning halfway in between the left edge of the screen and the center of the screen negative zero point five y Meaning halfway down between the vertical center of the screen and the bottom of the screen. That's where that point is so negative point five negative point five is in the Dead center of the lower left quadrant of clip space And for the right vertex here, it's the same deal except it's positive point five for the act so we're in the dead center of the bottom right quadrant and Zero point five now we have a point exactly between the top two quadrants So that's our triangle is just a standard float array But this is of course stored in memory on the CPU side It's stored in main system memory But the GPU only works with data that has been uploaded to the GPU memory and so mainly for that reason That's why we have to create these vertex array objects and vertex buffer objects So again, these things will be represented by ints which are just handles unsigned ints in this case and here gen vertex arrays is creating our vertex array object and Gen buffers is creating our vertex buffer object and for convenience when you call these you can create multiple in just one call In this case We just want one of each and we pass in a pointer to an int and the call fills in a value and if this were an Array events you'd pass in a pointer to the first element and you specify something other than one you specify the length either right and would fill them all in with New handle values for new vertex arrays and same deal for gen buffers Okay, so that's how we create these things and then when we work with the vertex array objects and vertex buffer objects Many of the calls that manipulate them You don't explicitly pass in the handle these calls will affect the VAO or VBO that is currently bound So we call bind vertex array specifying our vertex array object and jail bind buffer Specifying what kind of buffer it is because there are different kinds of buffers actually and we'll see a few of these different kinds later But for now, this is just an array buffer Specifying the handle and now that these are bound these operations like jail buffer data and vertex a trip pointer and Enable vertex a trip notice in all these calls. We're not specifying any handles of the VAO or VBO It's just assumed because those are what are currently bound So buffer data that takes the currently bound buffer and it is what is actually populating the data It's taking the data in this case from our vertices array And copying all that data into VBO And we of course have to specify the number of bytes which we can get with size of here over the array The last argument here this constant static draw That is a hint to open jail about how the data is going to be used Strictly speaking, I believe you get the same behavior get the same output regardless of what constant you set here But for the purpose of memory layout on the GPU side This hint can influence how the memory is laid out and then access which can affect performance So in some cases you're going to want to study what the options are there and choose the best option Static here is indicating that the state is never going to change It gets uploaded once the GPU and doesn't get modified afterwards and draw means that the main purpose of the state Is this going to be used in draw calls? And now jail vertex a trip pointer. This is where for our vertex array object We are specifying for one of its indexes the vertex array object is just made up of indexes each referencing a vertex buffer This call sets within the vertex array at one of its indexes. It sets what buffer it is referencing And what is the nature of the data in that buffer? That's what this defines. So for index zero of a vertex array object We are setting it to be associated with the currently bound buffer The number three here is the count of elements that we're going to be reading from the buffer in this case three three vertices Geo float that's specifying the type of the values in this case their floats False the same. We don't want the values normalized and this applies to really fixed point values I'm honestly vague on why you had ever said this to true, but in this case, it's false And then this value here is a so-called stride It specifies the size of each element or rather actually the distance between the first byte of one element And the next byte of the other Because as we'll see it could be the case that within your buffer you have multiple attributes That are interleaved and so from one element to the next to read the next attribute of the same type You're going to want to skip over stuff in between Anyway, in this case, it's three times the size of float which should be four bytes So this will be 12 because it's 12 bytes that makes up each one of our vertices And lastly you have a pointer value strangely, which is really meant to just be an offset You know, you can't actually add Pointers together in c or c plus plus. It's an invalid operation because it usually produces meaningless data I don't know why but for whatever reason this is a void pointer and rather than just an integer What we want to say here is that we're reading the first element in the buffer We're not skipping over any at the start If for whatever reason we wanted here to skip over the first vertex, this would be the value three And again, it is strange that this is a a pointer rather than simply an integer I don't know why they made it a pointer Maybe they thought this is somehow more type safe like it protects you from accidentally passing in integers when you're You're supposed to note an offset so they make you cast it to a pointer Anyway, for whatever reason it has to be a void pointer Anyway, this whole call is setting up index zero within our vertex array object Associating it with the currently bound buffer But having configured that it isn't yet enabled So we have to separately afterwards call enable vertex a trip array for index zero And now we have a vao that is ready for use in a draw call So that's how we set up the vao Last thing to note though before looking at the shader code is that you generally should deallocate these things You should delete them And of course, you can't delete the vertex array object while you're still using it for your draw calls But in my testing, it seems that you can delete the buffer and we don't need it actually to render anymore Because I don't know exactly what's going on in terms of the memory allocation But it seems like the vao has sufficient handles to the actual vertex memory stored on the gpu And you can delete the the buffer object is just the handle part that it seems you're safe to delete So there's a lot of ceremony there to set up our shader program and our vao It's a lot of boilerplate, but don't worry because this is mostly going to remain the same in later exercises So we're not going to have to cover all that garbage again So lastly looking at the actual shader code Notice that this string here is a so-called. Um, what do they call these raw strings in c++? It's the way you can write a Multi-line string you write r the double quotes And you just choose some kind of delimiter name I just wrote here like a here doc is you might be familiar with in in php And then you peran and then it ends the string ends where we see peran in the same The same word the same string of text after So as long as I don't have the word here in all caps anywhere in my string then this is fine It knows where the end delimiter is So this is the actual content of my string white space included So first thing in a shader you specify what version of GLSL you're using in this case we're using 3.3 So you write that 330 and then core is in core profile for open gl And every shader has a main function where execution kicks off And in a vertex shader the objective of the shader is to Output a value for gl position you want to assign gl position Before leaving main and that's the only real strict requirement of what your vertex shader In fact any shader has to do is that vertex shaders have to assign to gl position And in this case what we're doing for gl position is well Here we're defining what input the vertex shader is receiving As set up by the a trip Down Here For index zero of a vertex array object We're getting these vertices and so that's the data that's going to be received In that's the reserve word meaning this is input variable of type vec3 with the name a pause And the layout this is location equals zero that's specifying where from the vertex object We're getting what index in this case index zero of that vertex array object Anyway, so it's a vec3. It's three float values We though need to make that a vec4 because that's what gl position is is vec4 Remember in clip space we have the so-called w value that the x y's and z's are going to get divided by when we go to normalize device coordinates For our purposes. We don't want any perspective division. We're just drawing a 2d triangle So we'll set w the homogenous coordinate to one x y and z will be divided by one and so they'll be unchanged So that's our vertex shader Our fragment shader here is defined to have one output called frag color Which because it's the first and only vec4 output variable of the shader It's understood to be the the output color for the pixel Which in this case, we're just going to set to a hard coded color value of r of one g of point five b of point two and alpha of one So it's fully opaque and we're just doing this for every fragment every pixel of the triangle In later examples, we're going to want the color of course to be influenced by Which fragment this is which pixel of the rectangle and where it corresponds to in 3d space? And we're going to want to do texture mapping and all that so we're going to want to get An interpolated value of the three vertices of the triangle as input to our fragment shader But in this case, we're filling in the same solid color for every single pixel regardless We don't care about any input values here. We're not taking any input from the vertex shader Now actually one last thing as a convenience in glsl Because the first arg here is a vec3 It knows that what we want to do is take the x y and z and expand them out to be the respective x y and z of the vec4 So this is just more convenient way to get the same result Like I mentioned glsl has some convenience syntax for working with Vectors and matrices and and this is one nice convenience Okay, now before leaving this example, let's build it one more time Build successfully run it It all appears the same as we saw before But it's want to point out one thing if we expand the window notice that it is stretching And this is because again our geometry is defined in terms of clip space Which is then being scaled by according to the the viewport values It's being scaled to some dimensions of a viewport for screen space And interlogic for our viewport. We're not maintaining any consistent aspect ratio So that's why as we resize the window it is not Maintaining its aspect ratio. It's being stretched Which for our purposes here is just fine But in other cases like say if you're rendering a 3d world typically Regardless of what the aspect ratio of the end resolution You don't want it to look stretched and squashed in different resolutions And you have to handle that and how you process your your vertices and As we'll get into in later examples