DEV Community

Cover image for OpenGL: Catching up: Week 1
NoticeableSmeh
NoticeableSmeh

Posted on

OpenGL: Catching up: Week 1

OpenGL: Catching up, Week 1

Hi my name is William de Try and I am a game dev student at Stockholm University, I have decided to learn OpenGL. My intention is to get comfortable with OpenGL enough to continue to learn Directx12. So I have actually already begun learning openGL and I have done a few pages on learnopengl.com. I will be covering the chapters I have done so far quickly and I will go more in depth next week with my chapter analysis. Anyways this is what I have learned so far.

Hello Window - Textures

This chapter was just about the basics of OpenGL, getting it to run and getting a simple window on the screen. OpenGL is just a specification which means you have to have something that points to all of OpenGL's functions and such, basically OpenGL knows what it can do and what it should do. Your computer just doesn’t have a way of communicating with it, That’s where GLAD comes in. It does just that, it communicates between OpenGL and your computer's GPU drivers.

GLFW is basically a helper library that does things like creating a window, handling input and managing events. OpenGL is more like an artist that knows how to paint, whilst GLFW is the canvas and or provides the means in which to paint.
So we initialize GLFW and then we tell it hey we want this version or whatever, and the tutorial said that the core profile is better so why not just pick that one.

Next is making the actual window as you can see here,

And making a resize callback so GLFW knows to resize its window.

Then we have to load the OpenGL functions with GLAD

Then we have our shader programs, now a shader is basically just a program but it runs on your GPU. These are just textfiles with changed filetype to .vs and .fs, vs stands for vertex shader and .fs stands for fragment shader. Vertex shader includes the information of your vertices, if you were to draw a triangle you would need 3 different corners, those corners are vertices essentially. Now a vertex shader stores information needed for a program to know where to draw its vertices. A fragment shader just determines what colour each individual pixel inside of the object's vertices should be.

Both fragment shaders and vertex shaders are written in GLSL and look something like this.

Now lets talk about Vertex vs Fragment shaders
The vertex shader always runs first. It handles the shape of your object, where the vertices go on the screen.
The fragment shader runs second. It handles the look of your object, what color each pixel inside those corners should be.
The two shaders can pass information to each other using in and out.
Inside of the vertex shader file you can write something to an “out” variable that variable now becomes an “in” variable inside the fragment shader, and when the fragment shader is drawing its object it can now reference that variable to draw whatever it is it wants to draw.

The next thing I want to cover here is uniforms, uniforms are a way to be able to simply change something about a shader inside of your normal c++ code, as for example inside your main method. Uniforms can hold pretty much any type of data you would want to send from your main c++ program. I am using uniforms here as you can see inside the fragment shader to store texture information from files.

We will return to the shader files later on but for now this is all you need to know.

Now I have talked a lot about triangles already on this page but I’m actually just drawing a rectangle right now. Here we are storing an Array that keeps information of our vertices. A vertex can store more than just its position, it can also store its colour values and its texture coordinates. The position floats obviously follow X,Y and Z. The color floats cover R G B (Red-green-Blue). Texture coordinates cover U and V, which basically mean X and Y. The X length of an image for example and the Y height of an image. U and V stuff I find really stupid I just think of it as X and Y, but maybe I am just the one who's stupid.

Anyways this all should seem pretty obvious but it's important to note that OpenGL draws and interprets coordinates from the bottom left corner of the screen as 0, 0 and the top right of the screen as 1.0, 1.0. It makes sense then that 0.5, 0.5 would be directly in the middle of the screen then yes? Because well it is directly in the middle of the screen.

Now next we have here is our indices,
Indices tell openGL what vertices to draw and in what order. So in essence it helps us optimize our code by not drawing two vertices at the same location twice.

So here we have 2 triangles, each consisting of three vertices (as seen in red). Lets smoosh these two together to create a rectangle

Oh no now we have two locations where vertices are drawn twice (as seen in red and purple there). So this fixes that. Although my brain sort of short circuits when trying to picture this in my head while looking at the code, whelp.

Here lets define these variables
VBO = Vertex Buffer Object: Stores the vertices data (its positions, colors and all that nonsense) on the GPU. This is where you actually apply your vertices array.

EBO = Element Buffer Object: Stores indices that tell OpenGL which vertices to use when drawing triangles. This is where you actually apply your indices array.

VAO = Vertex Array Object: Like how to interpret the VBO data and which EBO is linked. When you bind the VAO. OpenGL knows how to draw your shape. This one is magic essentially.

Those glGen… functions essentially assign those variables accordingly. Buffers store stuff on the GPU. A bunch of names for things that just store stuff, it just stores stuff guys, it just stores stuff. Now the vertex array stores the buffers and how to interpret it, it’s like matryoshka dolls. The vertex array is like the biggest doll containing a bunch of children (buffers) and it dictates it around like a dictator. I'm tired of this.

Now the bind functions essentially say like “dude program I want you to focus on this thing right now”. Then in bufferdata you pass in your arguments the data you want to assign to your buffer. Then you do that again for each thing you want to add.

Here we go, these functions just make sense of what we have defined in our program already.

Lets explain the top two lines of code and you can apply this information to understand the others.

First the 0, that's the location of this vertexAttribArray, so when we call EnableVertexArray and all that we reference it through that ID.

The 3 means how many values belong there, so 3 floats for x,y and z

GL_Float, we are storing floats here people!

GL_False, honestly not sure. I think it has something to do with normalization, lets just keep that false for default application for now.

Now remember our vertices array? So our vertices array contained for each vertex 8 floats. 3 for positions, 3 for color and 2 for texture coordinates. So how many attributes do we have to examine before hopping onto the next vertex ? 8!, thats what that 8 *sizeof nonsense is about.

Now that (void*)0 is just an offset to determine it should begin reading in the vertices array. First we want to read the first 3 for our positions, then we want to read the colours in the second function there and then we want to read the last of the array to figure out our texture coordinates.

Now heres were actually applying textures and importing them and all that.
So first we declared some int variables that store our texture ids. Then when we load an image we need to know three things, its width, height and how many channels it has. RGB has 3 and RGBA has 4 y’know whatever. Then we flip the image, images are stored typically in their top-left corner, but as we noted previously OpenGL expects the bottom to be 0,0 so ye flip it.

Then glGenTexture we generate that texture and we snatch its ID. Then the bind texture function makes that current texture our focus so we can modify it, like we did previously.
Then we define the textures behaviors with glTexParameteri, wrap the texture along the x and y axis and if the x and y coordinates. We also don’t want the image to repeat so we say “No!” and clamp it. We use the nearest neighbor resize option because I am lazy and also we are just learning here, not trying to get fancy.

Then here with the stbi_load we load the image and all that. Then we upload the very sexy texture we have to our GPU. GLTexImage2D takes a few parameters, let's break those down here.

Gl_Texture_2D, its a 2d mage.
0 = this is its mipmap level, or its base level, (what are mipmaps?, relax ill get to it)
GL__RGB = this is the format we want to store it on the GPU
Width, height = Pretty self explanatory right
0 = A little secret between you and openGL, most likely your confession about a murder
GL_RGb = AGAIN! WHAT? No this is the format for the input data
GL_UNSIGNED_Byte = this is the type of the input data
Data = this what we loaded from the image before, we have to put that somewhere you know?. It's essentially just pixel data we are storing here and passing into here.

Now as for mipmaps. Y’know how something is super far away and you can’t really see it? Well it would be stupid for the computer to render that object as if you were really close to it, that would be an unnecessary amount of detail to render, so the computer cheats and creates a lower quality version it displays when far away, you do not notice the change in detail and it runs fast. It is a win win. That's what mipmapping is essentially.

Now it's finally time to explain this while loop here.

When we want the program to run, we do not want it to close right? Well thats what that glfWindowShouldCLose function is for.

Then we processInput, more on this later.

Then here we go, glClearColor, here we define when we clear our screen what color the screen should be cleared to. Then we, y'know, clear the screen. Every frame should be unique; we do not want frames stacking on top of one another.

ourShader.use(); Tell OpenGL, we want to use this shader program for rendering!!

glBindVertexArray, we want to use our VAO (because it has our vertices data) so openGL knows how to draw.

the glActiveTexture and bindTexture these select the texture then bind our selected texture to that slot so we can use both!

Then we actually draw the rectangle(2 triangles!) and declare that with GL_Triangles, two triangles take 3 indices each so we pass in 6 indices in total. So the 6 here means how many indices were pulling from the EBO. Gl_Unsigned_int is the data type of the indices in the EBO. Then lastly we have an offset that shows where it should start reading, right now it should start at 0.

Then finally we swapBuffers, we swap the front buffer and the back buffer to show everything on the screen.
So essentially the front buffer is what we see and the back buffer is what we will see in the next frame, it is constantly swapping these back and forth.
And then finally pollevents just checks for inputs and such.

Here's 2 helper functions we have been using, processInput and framebuffer size callback.

Not a lot going on here, processInput just checks if the escape key is pressed, if its pressed = close the program.

Framebuffer_size_callback changes the rendering areas size accordingly to fit inside the window, it does this every frame.

Conclusion:
That's it, that's what I've done so far in my journey in learning OpenGl. I am doing this devlog if you can call it that to help myself learn OpenGL better, and hopefully you had fun reading through it as well and that it proved useful. I am looking forward to documenting my journey learning this magnificent api!
Now finally this is the result on our screen right now!

Top comments (0)