 Hello, this is Indigo CS. Thanks for coming by. Today I wanted to talk about WebGL. And I have been really interested in graphics programming for a long time. And I also think that JavaScript is possibly the best programming language to ever grace mankind. So WebGL is the perfect combination of the two. It's way for you to write front end client side applications that use the full power of OpenGL, or at least OpenGL ES. I think it's the 2.0 standard that WebGL uses. So I'm going to do a bunch of video tutorials, pretty much until I get bored of these, which should take a very long time. In this first one, all we're going to do is we're going to set up WebGL. So you can see I have a little canvas right here. It's green or whatever color that is. And I've drawn a simple triangle. So this is getting all the boring overhead stuff out of the way. I'm going to talk a little bit about the graphics pipeline and how that works. So if you want to fully understand how that works, I recommend looking up a different write-up on that, because there is quite a lot that goes on. This is a simple mock-up of how a graphics application works. And when I say graphics application, think video game, especially I want you to think about if you've ever played on the N64 or the PlayStation 1, one of those really old video games where everything looked really boxy, or really triangle-y because that kind of accents the way graphics works, and it'll make a little bit more sense. So on a traditional video game, you're loading things from the hard drive. I'm going to point something out with WebGL, and that is that you're loading it from somebody else's hard drive over the internet, which means that data transfer is a lot slower than it's going to be in a traditional video game, or animation tool, or whatever you happen to be making. So once you have all the information over to your user, it's going to run really quick. It's going to run great. Fantastic, awesome, definitely slower than a C++ native application, but pretty quick. But the loading times are going to be easily 10 times worse, so if you're looking at making the next MMO RPG, just make sure you keep that in mind, and it might be worthwhile instead of having a loading page have something a little bit more dynamic. All right, so the first thing you need to do when you start up a WebGL app is you just need to initialize WebGL. So in this example right here, I have an HTML5 canvas right here. This canvas, I call it game surface width of 800, height of 600, and that's the surface that WebGL uses to draw. You then need to get an OpenGL context, and the context is pretty much OpenGL is a state machine. You can think of it as. And so the context is the tool that you will use to perform all of these graphics functions, so clearing the screen, drawing the triangles, all of it. And then after you've done that and you've initialized everything, which in WebGL is much simpler than it is with OpenGL and DirectX in C++ or Csharp or Java. So after you've done that, you're going to go through a lot of these steps, which I'll cover these mostly later. More or less, you just need to update the global variables that your graphics card is using for all the draws. After that, you need to draw everything. And usually, you use triangles. So this is just one triangle, but if you think back, Super Mario 64, what was that game? Mario 64? Any old good old game on the N64 is Legend of Zelda. Why not? Everything looks blocky, because everything is usually made of triangles in games. And so each individual triangle does need to be drawn. And usually, they're drawn as larger mesh objects, an entire thing, so an entire character link, perhaps. And the reason for that is these draw calls are actually relatively expensive. So try to group as much as possible as you can together. Once you've issued the draw call, what happens is it goes over to the vertex shader, and it takes all those points that define a square cube or whatever you have, every single triangle, and it runs them through a vertex shader. And the job of the vertex shader is more or less to take those points from the 3D space that you gave it and put it into 2D screen space. So in OpenGL the way that works is because you have different screen resolutions and we don't really want to mess with that additional detail, you have a coordinate system that goes from negative 1 to 1 on the x-axis, left to right respectively, and then negative 1 to 1 on the y-axis, bottom to top respectively, bottom to top. That's important. That's different than how you probably have learned with top to bottom if you've done direct x or, I don't know, for some reason that threw me off. Maybe it throws you off, maybe it doesn't. But the vertex shader pretty much transforms each vertex into a position, and then if you have additional information you want to give every vertex, that's fine too. So in this one I give every vertex a color as well. So this one's a red one, green one, blue one. After it goes from the vertex shader, it goes through the rasterization step, which pretty much is filling in the triangle. So like in this example, I have three points. The rasterizer says, all right, how many pixels are there in between? Which pixels should be drawn? And what exact positions on the screen are there? So maybe this one is at 0.5, or no, this is going to be 0.0 and 0.5 with a color of straight red. And you can see on the color it illustrates it much better than the positions, I think, how it kind of blends between the two. How if you get in the middle, this is exactly between red and green, and this is exactly between red and blue. And everything in between is a smooth continuum. The rasterizer does that with every single property you assign to each vertex. So that allows you to pretty much have for every pixel on the screen information, even though you're only providing three points of data. After it goes through the rasterizer and all those values are assigned, it goes through the fragment shader, which is where you decide for every single one of those pixels, or pixel fragments more accurately, which color you would like that pixel to be. And you can take that from all the information that you get in the vertex shader. After that goes to the frame buffer, which we're going to skip right over, because we don't need right now. And it goes out to your screen. And that is the gist of it. So without further ado, let's move this over here. And I have a Sublime Text window open right here. I'm going to save this new file. Let's see. Oh, it looks like this is some stuff I was writing before. So the first thing we're going to need is an index file. I'm just going to call that index.html. And that's going to be the actual HTML file. And I'm just going to write that up really quick. We really don't need very much. So my for WebGL simple triangle. So I'm just going to put two things in here. I'm going to put a canvas element, which I'm going to give the ID WebGL. I'm not going to call it WebGL canvas, because I don't want you to think that's a special name or anything. I'm going to call it game surface, why not? I'm going to put a little message in here. Your browser does not support HTML5. If that message shows up, what that means is that they're using a really old browser that they shouldn't be using. At this point, there's no excuse for using a non-HTML5 compliant browser. Then I'm going to put one more thing. I'm just going to put a little message in italics. Demo is above this text. This just lets us see kind of where the canvas begins and where it ends before we actually start drawing things. So now if I open this up on my file system right here, you can see it looks like we're great. And I haven't set a size yet, so I'm going to do that right now, width equals 800, height equals 600. Perfect. Cool. Now, call me old fashioned, but I do like to keep my files separated. We're going to be writing a lot of JavaScript, and the JavaScript is going to be doing the most important stuff. So I'm going to separate this out into a separate file and include it here. ScriptSource equals app.js script. Now, the reason I'm doing it inside the body tag and not inside the head tag is because JavaScript is immediately loaded as soon as the browser sees it, and it's immediately executed. And I'm going to be having to deal with this canvas. So I want the JavaScript to be loaded after the canvas element is, if at all possible. And if I were to put it in the head, I would not be guaranteed which one would run first, which I'm going to do another thing to make sure that that doesn't matter. And that is, I'm going to be assigning the onload function here, and I'm just going to write a function maybe initialize demo. I just realized it might be a little bit too zoomed out for you. All right. There we go. Hopefully that's better. So if you couldn't read before, here's the code that I had written. Sorry, I forgot that you're probably not watching this fully blown up on a 1440 monitor or whatever. All right. Cool. So in here, this is going to be what happens when our page is fully loaded and ready to go. So I'm going to bring up the console here. I'm just going to quick write a console.log to make sure this is working. This is working. Great. Doing good so far. So step one, if you're a member, is I needed to initialize WebGL. So to do that, we need two things. We need to get the actual canvas element, which I'm just going to call canvas, equals. I'm going to use the document.getElement by eddy. And I pass it in the name of any HTML element, and it will get me this whole thing as a JavaScript object. So if I do that, now canvas represents this thing right here. Now I need to get the openGL context, which I'm going to do by saying gl equals canvas.getContext WebGL. Now for Chrome and Firefox, this is all you need to do. But for Internet Explorer Edge, and I think Internet Explorer 11 is the first version of IE that supports it. Edge supports it, and Safari support it, but both of those don't support it fully or completely yet. They're not compliant. I don't know why, but you have to be using a different context that behaves the exact same way for the purposes of my tutorials. It's just an experimental context, I suppose. And it is called the experimental WebGL context. If they're still using a browser that somehow does not work, let's just tell them like your browser does not support WebGL. It's not very fresh. We should be fine. I'm going to put in a little message here, too. console.log WebGL not supported without experiment, falling back on experimental WebGL. Now if we view it here, this is in Chrome, so that should be fine. But if I pull it up an edge, we should see, yes, right here, WebGL not supported falling back on experimental. It should work the same between the two. So I'll keep this open so we can use it later. Great. So now that we have our context, that is initializing WebGL and you're done. It's pretty easy, right? If I wanted to adjust the size of the canvas at this point, I could by saying canvas.width equals, let's say document, I think it's window.interwidth, I could do something like that maybe. Yep, looks like that is it. And just to be careful, something to point out is if you do this, you do any dynamic size adjustments. Also be sure to say gl.viewport 00 window.interwidth. And this just changes what OpenGL thinks that it's rendering to. So I'm not actually going to make those changes. I'm going to save that for later tutorial when I add some more shine on it. Just be aware that that is something. So let's start off by just clearing our OpenGL context to that flat color that I showed. So the way you do that is you set which color you want to use for clearing operations by the gl.clearColor function. And in here, you give it a red, green, blue, and alpha value. Alpha is always going to be 1.0. That just means opaque. Anything less, it means transparent all the way down to 0, which means invisible. Let's assign some numbers. I believe these were the ones that I used before. Cool. And as you can see, nothing happens yet. This is just you can think of it as setting the color of the paint that it's going to use. It's not actually performing the paint. So the way you actually perform the paint is with a gl.clear call. And if I do just this, it shouldn't do anything. Yeah, so it's expecting an argument. And the argument in here is you need to sell it what to clear. Without going into too much detail, graphics applications have multiple buffers that they render to. The ones that you really have to be concerned with right now are the color buffer and the depth buffer. The color buffer is the one that actually stores what color all the pixels should be. And the depth buffer does something kind of cool. It stores how deep into the screen that pixel was. So it's really good for z-ordering. Say you have two things. You have a square and you have a circle. And the circle is behind the square. The depth buffer, when it draws a pixel from the square, might put a value lower than the value of the circle, which means that it's closer to the screen. And so if the program goes to draw a pixel from the circle and it notices that that pixel from the square is already there, it will just throw it away and ignore it because the depth buffer already had a value that supersedes what was attempted to be drawn. So let's clear both of those really quick. Color, buffer, bit is how you do that. And I believe this one actually should work if I just do that, yeah. That should work. But for good practice, I'm gonna do both. So, or gl.depthbuffer, bit. And if you've done any C or C++ programming, this probably looks more familiar. If you've done like DirectX or OpenGL in more of the low level programming languages. So great, so now we've drawn our canvas. So you can see that some OpenGL calls is happening. So now what we need to do in order to get the triangle going, this is where things get a little bit hairier. You have to set up the entire graphics pipeline program in order to get this to work. So we're going to need a vertex shader and a fragment shader. And a vertex shader looks something like this. This is one that I wrote when I was making sure that I knew it was going on for this tutorial. So these are what we're going to end up with. One of the things, JavaScript's a little bit picky and I don't wanna load these from a separate file, so we're just gonna write these inline as strings. But with a vertex shader, what you do is you take in input parameters called attributes and if you're writing a C function, so let's say function vertex shader, the attributes would be parameters like this, vert position, vert color. And then the outputs from a vertex shader are these varying ones. So you might return something like this, frag color, let's do it this way, vert color, frag color, jail position. Okay, so I just drew this out to be a little bit more familiar looking. This would be what it would look like in JavaScript. This is what it looks like in the GL shader language, GLSL. Attributes are input parameters, varying are output parameters, and this void main is the actual vertex shader that performs all the operations. And as you can see, I'm just setting this fragment color to whatever the vertex color happens to be, pretty straightforward. And then with every vertex shader, there's a special variable that you set called gl underscore position. And this sets where on the actual rendering surface you want to draw that vertex. It then goes through the rasterizer and for every single pixel that comes out of that rasterizer state, you draw with a fragment shader, which looks like this. The varying are now the inputs for the fragment shader, and the only output from your fragment shader will ever be this gl underscore frag color. And right here, I'm taking the color that it was supposed to be and adding an alpha component. We're gonna do a little bit simpler of a vertex and a fragment shader to begin with, and then we're gonna work up to those. So to start out, we're not gonna do anything special with color. So what I'm gonna write that is I'm gonna write for vertex shader text equals, and I'm gonna use a little trick that I learned at an internship that I did last summer. And this is, I have an array, and inside the array, I'm gonna put the lines of the vertex shader each on their own actual line in the file. So the first one, precision, medium, p, flow. And this says I wanna use medium precision on the floating point variables. I'm not sure why I need that, but I guess I do. So this has something to do with the arithmetic, the comparisons, lower precision means less accuracy, but also faster. So medium, compromise, goldilocks, right? Okay, then the next line, I'm just gonna leave that blank at tribute. Vect two, vert position. And the types are going to be a lot different than they are in the C-based languages. You do have float, like you do in the C-based languages, but you also have these Vect two, Vect three, and Vect four. These represent pairs, triplets, and four sets of floats that go together. So a vector of two elements would be this one. This has an x and a y component, both floats. And then right now, I just wanna deal with position, so I'm not gonna add any variants or other attributes. Floyd main. Okay, and then here all I'm gonna do is I'm gonna say that the position equals Vect four, because gl position is a four-dimensional vector, but this is only a two-dimensional vector that we're giving in. I wanna say that the x and y come from vert position. The z is going to be zero, and the last one is always 1.0. You don't really need to know too much about why this is 1.0 right now, just know that it's 1.0. So what this is doing is it's saying if Vect four expects four numbers, this one has two, so we're gonna take the first two from there, and then the second one, or the third one and fourth one, we're gonna use just like that. So that's our vertex shader. Let's make sure that I didn't screw up the syntax on that, good, still compiles. Let's write our fragment shader. And in a later tutorial, I'm gonna show you how to actually load these from external files. Turns out it's not super easy, so yeah, there is that. Let's see, and this one isn't taking in any additional parameters right now. I just want to color it. Let's give it a straight color like red. Syntax is good, we're great. All right, so this is just gonna say that the fragment color is going to be one on red, so completely filled with red, no green, no blue, full on the alpha because it's not transparent. Great, so now what we need to do is we need to get OpenGL ready to use these vertex shader and fragment shader. So I'm gonna make two variables. First one is going to be our vertex shader. And the way we do this is we use OpenGL to create a new shader object, gl.create shader. And then here, we tell it what type of shader we want to use. In this case, it's gl.vertex shader. Now you can see a lot of these constants are using gl.something. That's just the way WebGL does it because the OpenGL API uses a ton of these constants just like this. This ends up being a number. I don't remember what number it is. Let's do the same for a fragment shader. Fragment shader, great. So now what I want to do is I want to tell, I want to compile the vertex shader from this code that we provide up here and the fragment shader from this code that I provide up here. So there's two steps to doing that. The first thing you have to do is you have to set the shader source and you do that through the gl.shader source. The first parameter is the shader that you want to set the source code for and the second parameter is the actual source code, shader source again. Great, and so now we've set the source code if we run it, everything's good so far, great. So now I need to actually compile it and that is a gl.compile shader call. Great, and it still seems to be working. Now, this is where things get a little bit trickier. It seems to be working, but it's very possible that it's not. So I'm going to introduce a syntax error here by removing a semicolon. As you can see, it still likes it. So the way we can check for compilation errors in our shaders is there's a function called gl.get shader parameter which when then we tell it which shader we're examining, vertex shader and then we want to say which parameter we want to be getting. We want the gl.compile status and if it returns false or wrong or whatever, we want to output an error to the console console.er er compiling vertex shader. And then we also want to give it additional information which we can get from gl.get shader info log vertex shader and this just gives us a log with information from the vertex shader. Now I'm going to return out of this function too right here so it doesn't keep trying to go with invalid shaders. So if I refresh it here, now you can see, boom, we have an error. Error compiling vertex shader online eight. One, two, three, four, five, six, seven, eight. So it's saying that the error is right here and that this is the syntax error. I know that I forgot a semicolon so I'm going to put that back and that'll be the error. So now if I compile it, seems fine. Let's do that same thing for a fragment shader. All right, so now if I run it, it looks like the fragment shader I did right the first time. Great, so now we have a vertex shader and a fragment shader that we're ready to use and those are the two programs we have to write for the graphics pipeline. Now the last thing that we do is we need to combine them. We need to tell OpenGL that these are the two programs that we want to use together and we do that through creating what's called a program in OpenGL, the terminology and a little bit weird programs, shaders. But a program is, you can think of as a graphics card, entire program, the entire graphics pipeline whereas a shader is just an individual component. So we do that, so save our program equals gl.create program. And then I want to attach both a vertex shader and our fragment shader. And you do that to the gl.attachader function which takes in which program you want to attach the shader to and then which shader you actually want to use. So I'm going to use both of these. Now, you're noticing that I'm not specifying which one's vertex and which one's fragment. That's because these objects already know and so the OpenGL API is able to take care of that by itself. Finally, now that we've attached both of our things we need to link the program together. A good way to remember that is just compile then link legacy program. Link program, I want to link that program. So now much like we checked for a compile errors right here we're also going to check for linker errors like this. If gl.get program parameter now and gl.link status, error linking program, program. gl.get program info log program. Hopefully everything is good so far. Great, everything is great. And then there's one final thing we can do and to be completely honest with you I have no idea what this step does but I know that it's usually a good idea in, well I have no idea specifically what this does. I do know that it catches additional errors that you might run into but you can do what's called validating the program. This is something you only want to do in testing because as far as I'm aware it's more expensive. I know in actual C++ game writing this is something you'd only do in your debug releases and not in your full releases because apparently it takes a longer time. And I know this will catch additional issues but I don't know exactly what issues those are. Error validating program gl.get program info log zoom that out just a little bit. So actually we'll do this. We'll do it this way so you can now see everything. Should've been doing that the whole time. So now if I run it, hopefully it still validates. It still validates. We're great. Cool. So now we have set up the graphics card program and it is ready to accept our vertices. Now this is where things get much hairier for which I apologize. But we need to set all the information that the graphics card is gonna be using. Whoops. So back to the triangle. This has three points and each point is going to have an x and y position. We're not dealing with color right now so that's just two points of data. And we know that from the fragment shader. We have one attribute and that attribute is a vector two. So we need to create a list of those x and y positions that's gonna define our triangle. After we do that, we need to attach that list to the graphics card, or yeah, to the vertex shader. So what we do that is first let's make our vertices triangle vertices equals. And I'm just gonna put a little comment up here saying x and y is the information we're expecting. So for the first one, let's go top counterclockwise. So the first one is gonna be right in the middle on the x-axis and it's gonna be half way up the screen or three quarters of the way up the screen I guess on the y-axis. Second point, let's do the one on the left to make sure that we keep going to counterclockwise order which I will get to why that's important in a later video. It's actually not important right now but I'm gonna do it anyways counterclockwise. So that one is gonna be 0.5 negative 0.5, negative 0.5 there. So those are the actual vertices we're gonna use. So this right now is sitting on our main computer RAM. So this is sitting on your CPU accessible memory. Your graphics card has no notion of what that is. Your vertex shader has no notion. What you use for graphics card programs is you use buffers and that is just a chunk of memory that's allocated for some purpose. So I'm gonna make our triangle vertex buffer object equals gl.createBuffer. Now this is a chunk of memory on the GPU that we're ready to use. So let's bind it and what this line, whoops, what this line right here is doing now is it is saying that the active buffer we're using it as an array buffer which you can think of that as just being variables that we're passing to the graphics card. Nothing too special, just vertex buffers do that. I don't know exactly why it's called an array buffer and we are binding this buffer that we just created to be the active buffer. After that we wanna specify the data on the active buffer, gl.arrayBuffer, angleVertices, gl.staticDraw. Now what these three parameters mean is that this is the type of buffer that we're talking about an array buffer. Notice that this actual variable is not called in the gl.buffer data. The reason for that is it's going to use whatever active buffer is there. Switch every one that we last bound which happens to be the triangle buffer, triangle vertex buffer object. The second parameter is actually wrong but we're specifying that we want to use these points. Now in C or C++ it would be fine just like this but JavaScript, these types are a little bit weird. The way JavaScript stores things is everything is a 64 bit floating point precision number but OpenGL is expecting it in 32 bits so JavaScript has provided us with a wonderful object called the float32array which specifies the type and makes sure that it is correct for the OpenGL calls. So this is how we're gonna pass it. And then this gl.staticDraw all this means is that we're sending the information from the CPU memory to the GPU memory once and that we're not gonna change it ever again. It's just going over and we're gonna be done with it. Which is correct, we're not gonna be changing that triangle at all in this demo at least. Great, so now we've sent the information over to the graphics card. We now need to inform the vertex shader that vert position, this attribute is these vertices or actually every pair of these vertices because right now on the graphics card all it's doing is it's sitting there as six points, zero, 0.5, negative 0.5, negative 0.5, and negative 0.5 but there's no rhyme or reason to that. So the way we do that is we need to get a handle to that attribute by doing this. For our position attribute location, location equals gl.getAttribLocation. We specify two parameters here, which program we're using and the program will then additionally specify which vertex shader we're using and then what the name of the attribute that we are using is and that is just vert position. So now this position attribute location is going to be some number. If I'm not mistaken it's gonna be zero because this is the first attribute that we're passing. So I could just use zero but I'm not going to because what if I wanted to add an attribute up here? Then we'd have to change all of those specific numbers. So it's better practice just to do something like this where you get the attribute location. We then need to specify the layout of that attribute which we can use a function call called vertexAttribPointer and this is going to take five parameters. This first parameter is the attribute location which is going to be positionAttribLocation. The second parameter is going to be the number of elements in each attribute. This one has two, it's a vet two. So we're gonna say two per attribute and then we need the type of each of those. These ones are going to be 32 bit floats. So gl.float type of elements. This first one is whether or not the data is normalized which for now is gonna be false. I can get into that later. It's beyond the scope of this video though. This fourth one is going to be the size of an individual vertex and this last one is going to be the offset from the beginning of a single vertex to this attribute. So in this one we're gonna have three vertexes and each one is going to have two elements, both floats in it and right from the beginning is gonna be the position. We don't have any additional information that comes before that. So for the size, it's gonna be two times the size of a single float which I know that to be four but because I hate using magic numbers like that I'm going to say float 32 array dot bytes per element. And this is just a constant that holds the number four which is how many bytes a single 32 bit float uses. And the reason we need all these number of bytes is just so that the graphics card can reinterpret this data once it gets over there. Because on our CPU we know what this information is but we don't know that on the graphics card. So gonna do that. Two times float 32 array dot bytes per element and then here it's gonna be just the number zero because we're not actually doing any offsets. All right, two more steps. We're almost done, I promise. We need to enable the vertex at rib array. This pretty much enables the attribute for use. That's it. And then here this is where we put the main render loop. Now in a game this would be maybe a while loop where you write like an update world, render world function, something like that. Or the more JavaScript way to do it would be something like far loop equals function, update world, render world. If running request animation frame loop. This would be the more JavaScript way of doing it. For now we're just gonna draw that triangle out of the screen because we're not doing any actual animation. So I need to specify which program I wanna use. I wanna use this one, it's the only one we have so far so really that's the only one that makes sense. I then want to draw arrays. So this function is gonna take three parameters and this is going to use whichever buffer happens to be actively bound. If you haven't bound any other buffer since here. So we're gonna use this one. The first parameter GL dot triangles is going to say we're going to draw in triangles. 99% of the time you draw something it's going to be GL dot triangles. The other options are like GL dot points, GL dot lines, line segments, triangle fans and those do individual things. You're gonna have to look up on your own how those ones work though. The second parameter is how many of these vertexes we're going to skip. I want to skip zero and then the third one is how many vertices we have to actually draw which is three. I'm drawing three to form a complete triangle. If I had more points in here, so maybe whoops I wanted to draw this twice. I would have to change that to be six. But for right now we're only doing three. So at this point that is everything we're going to need to draw a red triangle. Voila. If we go back to edge it should work just the same. Yep. And if you went over to Firefox it would still work just the same even though we're using experimental GL and same with Safari. But cool. So this is the basic drawing a triangle. I'm gonna finish off the video with adding just one more attribute for the color so that we can get completely what I had here. And the point of doing that is just to show how information is passed from the vertex shader to the fragment shader. So let's do that. Let's add another attribute. Effect three vert color. Put a semicolon there. So that's gonna be another input to our vertex shader and then we're gonna have an output from our vertex shader varying effect three frag color. And then we're gonna add that same input to our fragment shader. In fact I'm just gonna copy that exact same line. We're not doing anything additional, extra exciting so I'm just gonna say that the frag color equals the vert color. And then here I'm gonna replace the red with whatever vert color I want it to use. And you can see right here, frag color has, oops, that should be frag color, huh? Frag color has three colors to it, a red, green, and a blue. And so we're passing it the red, green, and blue and then we're just assigning the alpha to be one because we want it to be opaque right now. So now down here we don't have to change any of this code because we're still compiling the vertex shader the same way. The only thing we do have to change is now we want to add the colors to our vertices and there's gonna be three of them, one for red, green, and blue. So let's say one of them is gonna be 1.0, 1.0, 0.0. One of them may be 0.7, 0.0, 1.0. I'm just picking random arbitrary colors right here. 0.1, 1.0, 0.6 I guess, why not? Cool. Now we have to add the attributes descriptions down here so I'm gonna copy this line because we're adding another attribute. It's gonna be vert color. Now notice we don't have to do anything for the varying. That is because by the time we get to the varying properties we're already done with the vertex shader. We only have to specify the inputs to the vertex shader because the inputs to the fragment shader the CPU isn't involved in at all. Now another type that we do have that I haven't covered here is we might have like uniforms. So uniform flow screen width perhaps. In uniforms are constants that stay the same between the vertex shader and the fragment shader. We'll get to those in another video, the next video actually. Great, so now I'm getting the color attribute location. I need to enable the color attribute location and then I need to do this whole thing for the color attribute location as well. Now a couple of things have changed here. First of all, the size of an individual vertex has changed from two to five. So if I were to just try to draw this right now it would probably freak out, yeah. Did not like that. So I do need to say that each one takes up five. And let's see what it does now. Still shouldn't like that, yeah. Still should not like that. And that's because the last thing that we need to change oh actually this, I did forget about this for the color, instead of having two elements it has three, so we do need to say that. I'm looking to see it draw a black triangle. I'm not sure what else I'm missing. But whatever, I was gonna show you how to do it wrong so I could show you how to do it right, but whatever, don't need to do that. The last thing that we're going to need is this one actually is offset from the beginning of a single vertex. So this is one single vertex. The position doesn't have an offset, but the color does, you have to skip the X and Y position in order to get the red, green, and blue values. So that is going to be three values and each one is going to be the size of a float. Great. And now that should be everything to draw it. Nope, I did something wrong. Shield draw arrays attempt to access out of range vertices in attribute zero. Oh, that needs to be color attribute location. That apparently wasn't the only issue. All right, what am I doing wrong? Triangle vertices, this is formed correctly. This is formed correctly. That's correct. That's correct. Let me look at my notes and see what I'm doing wrong here. Position program, vert position, vert color. Position two, float, false, five, zero. Color three, float, false, five. Oh, this has an offset of two, not of three. Yep, okay, that was it. Silly me. Hopefully you were yelling at the screen, hopefully you caught that and were like, are you idiot, how could you possibly be doing that wrong? But great, so now you can see that one of the vertices, this one has like a yellowish color. This one has like a purplish and this one some sort of green. But anyway, so that is it for this tutorial. That is setting up WebGL. That is drawing the most basic shapes that you can do. All of this so far has been 2D and we've been giving it direct coordinates for which position on the viewport that we want to use. In the next video, what I'm gonna do is I'm going to expand this into three dimensions. I'm gonna explain all the math that has to be done in order to do that and then we're gonna make a little rotating cube. So I hope you enjoyed this one and stick around for the next one. Thanks.