skip to content

Learning OpenGL and GLSL

/ 9 min read

Introduction

I have recently developed interest in graphics and game develpoment, learning these new things is always fun. I have been learning OpenGL and GLSL for a while now, and I have found some great resources to learn from. I will be sharing them here, so that you can also learn from them.

Challenge

Firstly I faced a challenge in setting up OpenGL and GLSL in clion, which i primarily use for C/C++ development. I could not find any good resources and still face this challenge. I will update this log if I find a solution.

Vscode

Facing challenge in clion, I decided to use VSCode for OpenGL and GLSL development. I found a great video on youtube by Codeus Channel, which explains how to setup OpenGL and GLSL in VSCode. I have linked the video below.

Youtube video by Codeus Channel:

C++ OpenGL setup for VSCode in 2min

Workaround to run multiple files

Here the task.json file shared in the tutorial works only for one file which is main.cpp but I wanted to run any file I wanted just by selecting run on that particular file. Unfortunately I did not find any solution for this, but I found a workaround. I just edit the task.json file and change the file name to the file I want to run.

{
	"version": "2.0.0",
	"tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++.exe build active file",
            "command": "C:/mingw64/bin/g++.exe",
            "args": [
                "-g",
                "-std=c++17",
                "-I${workspaceFolder}/include",
                "-L${workspaceFolder}/lib",
                "${workspaceFolder}/src/matrixplanet.cpp", //change this
                "${workspaceFolder}/src/glad.c",
                "-lglfw3dll",
                "-o",
                "${workspaceFolder}/cutable.exe"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: C:/mingw64/bin/g++.exe"
        }
    ]
}

How to run multiple files

Here "${workspaceFolder}/src/matrixplanet.cpp", is the line which runs the particular file. instead of matrixplanet.cpp I can run any file I want, just by changing the file name.

Where to put the files

I put all my files in the src folder, and I can run any file I want just by changing it in the task.json file.

Learning

Here are some things I have learned so far.

1. Basic skeleton in OpenGL

Basic skeleton
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

using namespace std; 



int main()
{
    GLFWwindow* window;
    if (!glfwInit())
    {
        cout << "Failed to initialize GLFW" << endl;
        return -1;
    }

    window = glfwCreateWindow(640, 480, "Hello World", 0, 0);
    if (!window)
    {
        cout << "Failed to create window" << endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        cout << "Failed to initialize GLAD" << endl;
        return -1;
    }


    // render loop
    while (!glfwWindowShouldClose(window))
    {
     
        glClearColor(1.0,0,0,0);
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
   
}

The tutorials I followed, some of them did not use glad and even without using it their code ran. But I got errors if I did not use glad so just by adding glad I was able to run the code.

Note

Also if I had to render anything that should go after the glad load function, otherwise it would not render.

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        cout << "Failed to initialize GLAD" << endl;
        return -1;
    }

    // render

2. Toggle color of the window each frame.

😋 this was a fun thing to do, basic though as compared to other things I did.

bool toggle = false;
    // render loop
    while (!glfwWindowShouldClose(window))
    {
        if(toggle)
        {
            glClearColor(1.0,0,0,0);
        }
        else
        {
            glClearColor(0,1.0,0,0);
        }
        toggle = !toggle;
        glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

This just made the screen go crazy with red and green color change each frame.

3. Drawing a Triangle

Triangle
 glBegin(GL_TRIANGLES);
        glColor3f(1.f,0.f,0.f);
        glVertex3f(-0.6f,-0.4f,0.0f);
        glColor3f(0.f, 1.f, 0.f);
        glVertex3f(0.6f,-0.4f,0.0f);
        glColor3f(0.f, 0.f, 1.f);
        glVertex3f(0.f,0.6f,0.0f);
        glEnd();

With this I learned how to draw a triangle, and how to set the color of the triangle. I also learned that the color of the triangle is set by the last glColor3f function. So if I want to set the color of the triangle I should set it before the glVertex3f function. I also learned we can created circles with triangles too! 😋 In the next learning I will be drawing a circle with triangles.

4. Drawing a Circle with steps

Circle

Outside of the main function, I set the steps and step angle.

If we have less steps the circle will not be smooth, and if we have more steps the circle will be smooth but it will take more time to render. You can set less steps and see how the triangle form a complete circle.

const int steps = 50;
const float stepAngle = 3.1415926f * 2.f / steps;
float xPos = 0; float yPos = 0; float radius = 1.0f;
    // render loop
    while (!glfwWindowShouldClose(window))
    {
     
        glClearColor(1,1,1,0);
        glClear(GL_COLOR_BUFFER_BIT);

        float xCenter = 0.0f;
        float yCenter = 0.0f;
        float radius = 1.f;
        
        float rx = xCenter;
        float ry = yCenter-radius;
        
        for (int i=0;i<=steps;i++) {
            float angle = stepAngle*i;
            float newRx = radius*sinf(angle);
            float newRy = -radius*cosf(angle);
            glBegin(GL_TRIANGLES);
            glColor3f(0.f,0.75f,0.f);
            glVertex3f(0.f,0.f,0.f);
            glVertex3f(rx,ry,0.f);
            glVertex3f(newRx,newRy,0.f);
            glEnd();
            rx = newRx;
            ry = newRy;
        }
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

Here I have used the sinf and cosf functions to calculate the x and y position of the triangle. I have used the glBegin and glEnd functions to draw the triangle. I have also used the glVertex3f function to set the position of the triangle. I have also used the glColor3f function to set the color of the triangle.

Note

I forgot to <= in the for loop

for (int i=0;i<=steps;i++)

instead i did was i < steps which caused the circle to not complete. I was stuck on this for a while, but then I figured out the problem.

5. Drawing solar system

solar system

Made a reusable function to draw a circle.

const int steps = 100;
void drawCircle(float red, float green, float blue){
    float radius = 1.;
    const float angle = 2.0f * 3.1416f / steps;
    float oldX = 0; float oldY = 0;
    for (int i = 0; i <= steps; i++) {
        float newX = radius * sin(i * angle);
        float newY = radius * cos(i * angle);
        glColor3f(red, green, blue);
        glBegin(GL_TRIANGLES);
        glVertex3f(0.f, 0.f, 0.f);
        glVertex3f(oldX, oldY, 0.f);
        glVertex3f(newX, newY, 0.f);
        glEnd();

        oldX = newX;
        oldY = newY;
    }
}

And used GL_MODELVIEW to draw the planets.

the glPushMatrix and glPopMatrix functions are used to save the current state of the matrix and restore it later. This is used to draw the moon around the earth.

If I did not use the glPushMatrix and glPopMatrix the earth would go 5 points up in a loop hence we could not see the earth and moon rotating.

The {} are used to create a scope, so that the glPushMatrix and glPopMatrix functions are used only for the earth and moon.

I scaled down the moon and earth to make it look like a solar system. Also created a separate variable for the angle of the moon, so that it rotates much faster around the earth.

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glScalef(0.1, 0.1, 1);
    float angle = 0;
    float angleMoon = 0;
    // render loop
    while (!glfwWindowShouldClose(window))
    {
        angle += 1;
        glClearColor(0,0,0,0);
        glClear(GL_COLOR_BUFFER_BIT);
        // sun
        drawCircle(0, 1, 0);
        {
            // earth
            glPushMatrix();
            glRotatef(angle, 0, 0, 1);
            glTranslatef(0,5,0);
            glScalef(0.5, 0.5, 1);
            drawCircle(0, 0.3, 1);
            {
                // moon
                glPushMatrix();
                glRotatef(angleMoon, 0, 0, 1);
                glTranslatef(0, 2, 0);
                glScalef(0.3, 0.3, 1);
                drawCircle(0.5, 0.5, 0.5);
                glPopMatrix();
                angleMoon += 5;
            
            }
            glPopMatrix();
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

6. Trying shaders

I learned how sharders are set for the vertex and fragment. I also learned how to set the color of the triangle in the fragment shader.

I tried creating a cube with two triangles giving both different colors.

7. Drawing a texture rectangle sprite

texture
// vertex shader source

const GLchar* vertex120 = R"END(
#version 120
attribute vec3 inPosition;
attribute vec2 inUvs;
varying vec2 outUvs;
uniform mat4 matrix;
void main()
{
    outUvs = inUvs;
    gl_Position = matrix * vec4(inPosition,1.f);
}
)END";

// fragment shader source

const GLchar* raster120 = R"END(
#version 120
uniform vec2 res;
uniform float time;
varying vec2 outUvs;
uniform sampler2D tex; // 1st texture slot by default
void main()
{
    gl_FragColor = texture2D(tex, outUvs);
}
)END";

I assigned the raster120 framgment shader to the shader fragrment and also the vertex120 vertex shader to the shader vertex.

and then created a shader created a shader program to attach both of them to the shader program.

// ------------- VERTEX SHADER
    
    source = vertex120;
    
    GLuint shaderVertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(shaderVertex,1,&source,0);
    glCompileShader(shaderVertex);
    
    glGetShaderiv(shaderVertex, GL_COMPILE_STATUS, &compilationStatus);
    if (compilationStatus == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderVertex, sizeof(messages), 0, &messages[0]); std::cout << messages;
        exit(1);
    }
    
    // ---------- FRAGMENT SHADER
    
    source = raster120;
    
    GLuint shaderFragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(shaderFragment,1,&source,0);
    glCompileShader(shaderFragment);
    
    glGetShaderiv(shaderFragment, GL_COMPILE_STATUS, &compilationStatus);
    if (compilationStatus == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderFragment, sizeof(messages), 0, &messages[0]); std::cout << messages;
        exit(1);
    }
    
    // ------------- SHADER PROGRAM
    
    GLint linkStatus;
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram,shaderVertex);
    glAttachShader(shaderProgram,shaderFragment);
    glLinkProgram(shaderProgram);
    
    glGetProgramiv(shaderProgram,GL_LINK_STATUS,&linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(shaderProgram,sizeof(messages),0,&messages[0]);
        std::cout << messages;
        exit(1);
    }
    
    glUseProgram(shaderProgram);

I then created vbo’s and uvs, added pixel texture and ran the shader program.

8. Loading a bmp texture

I tried adding a bmp texture to a simple rectangle to just show the entire texture. I learned how to load a bmp texture and how to set it to the rectangle.

Error I faced

I faced an error while loading the texture, the texture was not loading and the application did not run. I am still stuck on this maybe because opengl is sensitive to the bitmap format. I requires 32 bit bmp format. I will update this log if I find a solution.

9. Cube with shared and Unique vertices.

Shared Vertex Cube (8 vertices):

I learned how to create a cube with shared vertices and unique vertices. I also learned how to set the color of the cube in the fragment shader.

Shared Vertex Cube

The cube is made up of 8 vertices, and each vertex is shared by 3 faces. The cube is made up of 6 faces, and each face is made up of 2 triangles. The cube is made up of 12 triangles.

    3-------2
   /|      /|
  / |     / |
7-------6  |
|  |    |  |
|  0----|--1
| /     | /
4-------5
  • Vertices are shared among faces.
  • Vertex 2 is part of the front, right, and top faces.
  • Coloring Vertex 2 one color will affect all three faces.
  • Colors will be interpolated between shared vertices, mixing at edges.

Unique Vertex Cube (24 vertices):

Front Face Vertices (4 unique):

    3'------2'
   /       /
  /       /
4'------1'

Right Face Vertices (4 unique):

         2''-----6'
        /       /
       /       /
    1''------5'

Top Face Vertices (4 unique):

        6''-----2'''
       /       /
      /       /
    5''------1'''
  • Each face has 4 unique vertices, not shared with other faces.
  • Vertex 2 is replicated three times (2’, 2”, 2''') for the front, right, and top faces.
  • Each instance of Vertex 2 can have a different color, giving each face a unique color.
  • No color interpolation between faces; each face is a solid color.

Github Repo

I have created a github repo for this project, you can check it out here. Github