In this article we’ll see how to render a triangle using OpenGL. A triangle is probably the simplest shapes you can draw in OpenGL after points and lines and any complicated geometry that you make will me made up of number of triangles joined together.
We’ll be using the programmable pipeline, so we’ll be writing simple shader programs as well and compiling them and using them later for rendering. Now this will make our program a bit lengthy but the cool thing is we have do do this only once and then reuse the code we’ve already written. This is actually true for most of the OpenGL code and hence we tend to write most of OpenGL codes in different functions and reuse them multiple times later.
The libraries that we’ll be using for this is Glew and Glut and Ç++ as our programming language.
Setting up the Environment
- Download the latest glew and glut header, library and dll files.
- In Visual Studio create a Win 32 console application project.
- Go to your project properties.
- Import and include header and library for all configuration.
- Put the dll files where your source files are.
The complete Program(Explanation follows after the program)
Now when you run the program you should have a red triangle on the screen that should look something like the image below.
Main function sets up our window. We initialize glut, glew, specify windows size, its position on the screen, specify display mode(which just tells what color space will be used by the window and will it use single or double buffer), set windows title, set the callback method that glut will use to draw stuff on this window and finally make the window visible.
A triangle is made up of three coordinate points. These points(stored as floating point array) needs to be sent to graphics processor memory.
The way we refer to a location in system memory is relatively easy, just get a pointer to the memory location but using your GPU’s memory is not that straightforward.
We do this by something known as buffer objects.
Buffer objects are the way you upload your data to the V-RAM and refer to it later.
There are usually three steps that you need to follow to get your data into the video memeory.
- Generate a unsigned integer identifier for your buffer object.
- Bind that buffer object to a target.
- Specify the size of the buffer and optionally load data into it.
Remember that OpenGL expects vertex coordinates in the range of [-1, 1]. Anything outside this range is clipped. However, we are free to choose our own coordinate system in-fact, it is a very common practice to define our own coordinate system and define our objects according to this coordinate system and later change it to the OpengGL coordinate system.
But in this post we’ll travel down the easier road and specify our triangle’s coordinates in [-1, 1] range. FYI coordinates in these range are known as normalized device coordinate(NDC).
Throughout the rendering pipeline the input data(here vertex coordinates) go through various stages. We control these stages using something called Shaders
Shaders are programs that execute on GPU. Shaders in OpenGL are written in a special language commonly known as GLSL(OpenGL shading language) which you will come to notice is very similar to C and C++. Shaders give you direct control over the graphics pipeline. GLSL was formally included into the OpenGL 2.0 core in 2004 by the OpenGL ARB.
The two shaders that you will need all the time are
- Vertex Shader: These shaders are executed once for every vertex(in case of a triangle it will execute 3 times) that the GPU processes. So if your scene consist of one million vertices, the vertex shader will execute one million times once for each vertex. The main job of a vertex shader is to calculate the final positions of the vertices in the scene. It does so by writing a vector(this vector has 4 component) to a special output variable known as gl_Position(only available in vertex shader). The input to the vertex shader is specified using an in type qualifier and output with an out qualifier before the variable. The input and output variable must have global scope.
- Fragment Shader: After your geometry has passes through other intermediate stages if finally reaches to the rasterizer. Rasterizer divides your geometry into fragments. Then the fragment shader runs for each fragment in your geometry(or triangle). The job of the fragment shader is to determine the final color for each fragment. It writes the final color to a special output variable gl_FragColor(only available to fragment shader). The value passed to the gl_FragColor is a vector containing rgba values for that fragment.
The shader code is stored as strings global variable. We’ll link this variable to the data in our openGL program later. The vertex position are passed to gl_Position without modification.
Note: It is a common practice to put your shader codes in different files and later store that files contents in string variables.
After you done writing your shader you need to create shader objects, attach their corresponding source code, compile them and check for errors if there are.
This function compiles the shader code, checks for error and creates shader object and returns its id.
We’ll call this function once for vertex and fragment shader
Shaders that you write for rendering needs to clubbed together into a shader program.
This function clubs these shader objects into a shader program, links them(that sort of creates an executable file for that program) and check for errors that may occur in this process.
It uses all the above functions and puts everything together.
We use what is known as VAO(Vertex Array Objects).
VAO: A Vertex Array Object (VAO) is an OpenGL Object that stores all of the state needed to supply vertex data. It stores the format of the vertex data as well as the Buffer Objects providing the vertex data arrays. Note that VAOs do not copy, freeze or store the contents of the referenced buffers, if you change any of the data in the buffers referenced by an existing VAO, those changes will be seen by users of the VAO.
A shader program assigns to each of the input variable of vertex shader(aka vertex attributes) and uniform variables(variables whose values do not change for the primitive we are rendering) a position that we need to know if we want to link them to their data source.
Remember we haven’t yet told GPU to start rendering. display Function is what gives this command to the GPU.
Glut calls this callback function whenever it needs to draw stuff to the screen.