Rendering a Triangle using OpenGL(using Shaders)

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 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

  1. Download the latest glew and glut header, library and dll files.
  2. In Visual Studio create a Win 32 console application project.
  3. Go to your project properties.
  4. Import and include header and library for all configuration.
  5. Put the dll files where your source files are.

The complete Program(Explanation follows after the program)

filter_none

edit
close

play_arrow

link
brightness_4
code

// CPP program to render a triangle using Shaders
#include <GL\freeglut.h>
#include <GL\glew.h>
#include <iostream>
#include <string>
  
std::string vertexShader = "#version 430\n"
                           "in vec3 pos;"
                           "void main() {"
                           "gl_Position = vec4(pos, 1);"
                           "}";
  
std::string fragmentShader = "#version 430\n"
                             "void main() {"
                             "gl_FragColor = vec4(1, 0, 0, 1);"
                             "}";
  
// Compile and create shader object and returns its id
GLuint compileShaders(std::string shader, GLenum type)
{
  
    const char* shaderCode = shader.c_str();
    GLuint shaderId = glCreateShader(type);
  
    if (shaderId == 0) { // Error: Cannot create shader object
        std::cout << "Error creating shaders";
        return 0;
    }
  
    // Attach source code to this object
    glShaderSource(shaderId, 1, &shaderCode, NULL);
    glCompileShader(shaderId); // compile the shader object
  
    GLint compileStatus;
  
    // check for compilation status
    glGetShaderiv(shaderId, GL_COMPILE_STATUS, &compileStatus);
  
    if (!compileStatus) { // If compilation was not successfull
        int length;
        glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
        char* cMessage = new char[length];
  
        // Get additional information
        glGetShaderInfoLog(shaderId, length, &length, cMessage);
        std::cout << "Cannot Compile Shader: " << cMessage;
        delete[] cMessage;
        glDeleteShader(shaderId);
        return 0;
    }
  
    return shaderId;
}
  
// Creates a program containg vertex and fragment shader
// links it and returns its ID
GLuint linkProgram(GLuint vertexShaderId, GLuint fragmentShaderId)
{
    GLuint programId = glCreateProgram(); // crate a program
  
    if (programId == 0) {
        std::cout << "Error Creating Shader Program";
        return 0;
    }
  
    // Attach both the shaders to it
    glAttachShader(programId, vertexShaderId);
    glAttachShader(programId, fragmentShaderId);
  
    // Create executable of this program
    glLinkProgram(programId);
  
    GLint linkStatus;
  
    // Get the link status for this program
    glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
  
    if (!linkStatus) { // If the linking failed
        std::cout << "Error Linking program";
        glDetachShader(programId, vertexShaderId);
        glDetachShader(programId, fragmentShaderId);
        glDeleteProgram(programId);
  
        return 0;
    }
  
    return programId;
}
  
// Load data in VBO and return the vbo's id
GLuint loadDataInBuffers()
{
    GLfloat vertices[] = { // vertex coordinates
                           -0.7, -0.7, 0,
                           0.7, -0.7, 0,
                           0, 0.7, 0
    };
  
    GLuint vboId;
  
    // allocate buffer sapce and pass data to it
    glGenBuffers(1, &vboId);
    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  
    // unbind the active buffer
    glBindBuffer(GL_ARRAY_BUFFER, 0);
  
    return vboId;
}
  
// Initialize and put everything together
void init()
{
    // clear the framebuffer each frame with black color
    glClearColor(0, 0, 0, 0);
  
    GLuint vboId = loadDataInBuffers();
  
    GLuint vShaderId = compileShaders(vertexShader, GL_VERTEX_SHADER);
    GLuint fShaderId = compileShaders(fragmentShader, GL_FRAGMENT_SHADER);
  
    GLuint programId = linkProgram(vShaderId, fShaderId);
  
    // Get the 'pos' variable location inside this program
    GLuint posAttributePosition = glGetAttribLocation(programId, "pos");
  
    GLuint vaoId;
    glGenVertexArrays(1, &vaoId); // Generate VAO
  
    // Bind it so that rest of vao operations affect this vao
    glBindVertexArray(vaoId);
  
    // buffer from which 'pos' will recive its data and the format of that data
    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glVertexAttribPointer(posAttributePosition, 3, GL_FLOAT, false, 0, 0);
  
    // Enable this attribute array linked to 'pos'
    glEnableVertexAttribArray(posAttributePosition);
  
    // Use this program for rendering.
    glUseProgram(programId);
}
  
// Function that does the drawing
// glut calls this function whenever it needs to redraw
void display()
{
    // clear the color buffer before each drawing
    glClear(GL_COLOR_BUFFER_BIT);
  
    // draw triangles starting from index 0 and
    // using 3 indices
    glDrawArrays(GL_TRIANGLES, 0, 3);
  
    // swap the buffers and hence show the buffers 
    // content to the screen
    glutSwapBuffers();
}
  
// main function
// sets up window to which we'll draw
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 50);
    glutCreateWindow("Triangle Using OpenGL");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

chevron_right


Output
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
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.

loadDataInBuffers() Function
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.

  1. Generate a unsigned integer identifier for your buffer object.
  2. Bind that buffer object to a target.
  3. 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).

Shaders
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

  1. 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.
  2. 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.

compileShaders() Function

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

linkProgram() Function
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.

init() Function

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.

display() Function
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.



My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.



Improved By : nidhi_biet



Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.