Triangle

This is hopefully the first in what will be a weekly series of OpenGL tutorials. While I have not decided on a direction of where this is going, hopefully you can come back next week and learn some new things about how to work with OpenGL.

The goal of this first tutorial is to get our feet wet and begin working with OpenGL through rendering a triangle. To accomplish this task we will be using GLFW and GLEW, so you will need to download both of those libraries if you are going to follow along with the tutorial. Also, if you would like to download the source code for this tutorial, it is available here on GitHub.

We are going to get quite a few things done in this tutorial, so we can move onto more interesting things sooner in further tutorials. I hope to cover and explain enough so you can come away with a food enough understanding of what is required to render a scene with OpenGL and be able to work on more involved tasks in the future. I apologize in advance for anything I may glaze over or neglect to mention. If there is something you are not quite clear about please feel free to ask in the comments.

Program/ GLFW and Glew Initialization
The first thing we need to get done is initialize the components making it possible for us to draw to a window with an OpenGL application.

The first thing done in the initialize function is the initialization of GLFW with glfwInit. GLFW is a open source multi-platform library for creating windows for OpenGL contexts and handling input. In other, this makes it possible for us to quickly and easily create a window to render to. If GLFW is initialized without problem, we then create a new window to render to with glfwCreateWindow which specifies the screen width, screen height, title of the window. The final two variables are of no consequence for now, so we will set them to NULL. The final thing we must do with GLFW is make the window we just created, the current context which we will be rendering to. Without this call OpenGL will not know where to render.

After initializing GLFW, we initialize GLEW. GLEW helps determine what OpenGL extensions are supported on the platform running the application and makes them accessible. Accompanying this first block of code is also the function error_callback which we initialize pass to glfwSetErrorCallback at the beginning of Init(). error_callback tells glfw where to print error messages if one were to occur.

Initializing OpenGL
While in future tutorials this function will be forgone for a class to handle GLSL programs and their functions, for the time being because of the simplicity of the project, all program setup for rendering is accomplished within the function. There are two important components that must be created for us to accomplish our goal.

First thing that needs to be done is the creation of an empty program object that we will attach our shaders to. In OpenGL, a program is essentially the adhesive that pulls together the different shader stages (discussed in the next section) for a single rendering task. We set our global GLuint variable ProgramID to this empty object with a call to glCreateProgram.

Now we must initialize shaders and attach each shader to the program. But first what is a shader?

Simply enough, shaders are programs made for a specific part in the rendering pipeline that help specify the appearance of a scene. In GLSL (OpenGL Shading Language), a shader is made to execute one of the programmable stages of the rendering pipeline. Currently with OpenGL there are 6 types of shaders, vertex (GL_VERTEX_SHADER), tessellation control (GL_TESS_CONTROL_SHADER), tessellation evaluation (GL_TESS_EVALUATION_SHADER), geometry (GL_GEOMETRY_SHADER), fragment (GL_FRAGMENT_SHADER), compute (GL_COMPUTE_SHADER) shaders.

The GLSL program used for this project only uses a vertex shader and fragment shader. Both of these types are required for any GLSL program, aside from programs that are made with the compute shader.

We initialize the shaders with the LoadShader function (discussed below) and after the shader is compiled, it is attached to the program that will be used to render the scene. After both shaders have been initialized and compiled, we link the program, where if the link is successful, makes it possible to make the program part of the current rendering state. This in turn will make it possible to use our shaders to render the triangle.

If the link is successful the execution continues. If not we print a log of the reasons for the unsuccessful link. Next we find the location in the program for the attribute VertexPosition using glGetAttribLocation. Attributes are values that are provided to the shader for each vertex processed. In this case it is just the location where the vertex is located. Other values commonly are but are not limited to the normal, color, and texture coordinates. If VertexPosition is found we continue to where we set the color to blue for the screen when it is cleared after a buffer swap.

The Vertex Shader
Both the vertex and fragment shaders used here are rather simple. Their sole goal is to place the vertices in clip space (covered by the vertex shader) and then color the triangle created by the vertices white (performed by the fragment shader).
The vertex shader performs operations on vertex attributes passed to the shader. With the vertex shader used in the application, a vertex position is taken as input and passed to the predefined output variable, gl_Position, which specifies the vertex’s clip-space output position. This is done through first specifying the vec2 input variable VertexPosition. Then in the vertex shader’s main function VertexPosition is passed as well as the z, and w values for the variable gl_Position.

The Fragment Shader
The fragment shader processes the fragments and handles the way by which these fragments will be rasterized. The type of the fragments that are process by the fragment shader are specified in the glDrawArrays and glDrawElements functions as either points, lines, or triangles. The shader created for this application takes the triangle fragment created from the vertices and rasterizes the fragment as a white triangle. To do this we specify the output variable vec4 FragColor. In the fragment shader’s main function we pass a vec4 of (1.0, 1.0, 1.0, 1.0) which is the color white. A vec4 can represent a color as well as a position. So in the case of the vec4 passed to FragColor, there is an r (red) value of 1.0, g (green) value of 1.0, b (blue) value of 1.0, and a (alpha) value of 1.0 resulting in an opaque white color.

Using Vertex Buffer Objects and Index Buffer Objects
The first thing we must do is specify where the three vertices for the triangle are located in the scene. This is done by creating the array of GLfloat values, vertices:

The points are specified in pairs. The first being the x value and the second being the y value, 2 values, an important point to remember when setting up the vertex buffer object. After creating the vertices, we create vertex buffer object VBO with glGenBuffers and bind the buffer to the GL_ARRAY_BUFFER with glBindBufferData. This specifies that any data associated bound to a GL_ARRAY_BUFFER at that point will be bound to VBO. We then attach the vertices to the vertex buffer object with the glBufferData function. The second parameter passed to glBufferData, 2 * 3 *sizeof(GLfloat) specifies the size of the data being passed to the vertex buffer object.

As a little added flair, while not necessary in this case, we then set up an array of indices specifying the order in which the vertices should be passed and processed by the shader. This would be directly handled in this case by OpenGL just by the vertices being read in order from the VBO. The array of indices is then passed to the GL_ELEMENT_ARRAY_BUFFER and bound to the index buffer object IBO much like how the vertices were passed to the GL_ARRAY_BUFFER to be bound to the VBO.

Drawing the Triangle
With the DrawScene function the first thing we must do is clear the color buffer with glClear(GL_COLOR_BUFFER_BIT). This function clears the buffer to whatever color has been specified for the clear color, which we set to blue in the initialization function. We then state that we want to use the program, ProgramID that we set up previously in InitGL, with glUseProgram(ProgramID). We then enable the attribute that we set up previously for VertexPosition and bind the vertex buffer object as well as the index buffer object. After binding our buffers, we call glDrawElements. With this function, we specify what type of primitive we are drawing in this case just a triangle so we state GL_TRIANGLES. This can also be points, lines, triangle strip, and triangle fan. We also specify the number of indices we are processing (3), the data type of the indices (GL_UNSIGNED_INT), and the location of the vertices in the array (NULL for location 0 in the array). This draws the geometry to the back buffer.

To show what we have drawn to the back buffer, in the loop in the main function we call glfwSwapBuffers to swap the front and back buffer.

Shutdown
Finally, with the application we have the shutdown function which essentially cleans up everything created for the application. I believe the implementation of this function speaks for itself.

The Main Function
Woah! We almost forgot about our main function there, but there isn’t all that much to discuss about it. All of the functions previously discussed do the heavy lifting now. We just need to make sure that we call them properly. As long as everything initializes properly, the triangle will enter a loop where it will continue to render the triangle until it is told to close (yes I know we could do draw to the window once and call it a day). In the loop we draw the triangle to the back buffer and then swap buffers. After the loop is exited, the shutdown function is called and we return a EXIT_SUCCESS value to show that the program has run and closed properly.