Learning Modern OpenGL with C++ (in Computer Graphics)-Part 3

Learning Modern OpenGL with C++ (in Computer Graphics)-Part 3

In Previous Part we setup GLEW/GLFW on windows and Introduce with basic code. rajkumarpatil.hashnode.dev/learning-modern-..

In this part we are going to study Shaders and the Rendering Pipeline

What is the Rendering Pipeline?

  • The Rendering Pipeline is a series of step in order to render an image on display monitor or screen.
  • Four step are programmable via Shaders.
  • Shaders are pieces of code written in GLSL (OpenGL Shading Language), or HLSL (High-Level Shading Language) if you're using Direct3D.
  • GLSL is based on C.

    The Rendering Pipeline Step

    1. Vertex Specification
      • Vertex ~= Vertices
      • Vertex Specification is setting up the data of vertices to be display or render on the screen.
    2. Vertex Shader (programmable)

      • Uses VAOs (Vertex Array Objects) and VBOs (Vertex Buffer Objects).
      • VAO defines WHAT data a vertex has (position, colour, texture, normals, etc).
      • VBO defines the data itself.
      • Vertex Specification: Creating VAO/VBO
        1. Generate a VAO ID.
        2. Bind the VAO with that ID.
        3. Generate a VBO ID.
        4. Bind the VBO with that ID (now you're working on the chosen VBO attached to the chosen VAO).
        5. Attach the vertex data to that VBO.
        6. Define the Attribute Pointer formatting
        7. Enable the Attribute Pointer
        8. Unbind the VAO and VBO, ready for the next object to be bound.
      • Vertex Shader Simple Code
           #version 330 
           layout(location =0) in vec3 pos; 
           void main() 
           { 
               gl_Position = vec4(0.4*pos.x,0.4*pos.y,pos.z,1.0); 
           }";
        
    3. Tessellation (programmable)

      • Allow us to divide the data in small primitives.
    4. Geometry Shader (programmable)
      • Geometry shaders handle geometry primitives(Group of vertices)
    5. Vertex Post-Processing
      • Transformation and Clipping(We will study in future Parts of this series)
    6. Primitive Assembly
      • Vertices are converted into series of primitives
    7. Rasterization
      • Convert Primitives into fragment.
      • Fragment is a data for each pixel. (Colour etc)
    8. Fragment Shader (programmable)

      • Handel data for each fragment.
      • Fragment Shader :Simple Code
        #version 330 
        out vec4 colour ; 
        void main() 
        { 
          colour = vec4(1.0,0.0,0.0,1.0); //Red Colour(RGB)
        }";
        
    9. Per-Sample Operations

      • Series of test run to see if fragment to be drawn.
      • Most important test: Depth test. Determines if something is in front of the point being drawn.
      • Colour Blending: Using defined operations, fragment colours are "blended" together with overlapping fragments. Usually used to handle transparent objects.
      • Fragment data written to currently bound Frame-buffer.
      • Lastly, in the application code the user usually defines a buffer swap here, putting the newly updated Frame-buffer to the front.
      • The pipeline is complete!

Creating a Shader Program.

 1. Create empty program. 
 2. Create empty shaders. 
 3. Attach shader source code to shaders. 
 4. Compile shaders. 
 5. Attach shaders to program. 
 6. Link program (creates executables from shaders and links them together). 
 7. Validate program (optional but highly advised because debugging shaders is a pain). 

CODE: First Triangle with shader

#include<iostream>
#include<conio.h>
using namespace std;
#include<string.h>
#include<GL/glew.h>
#include<GLFW/glfw3.h>


const GLint WIDTH = 800, HEIGHT= 600 ;
GLuint VAO, VBO, shaders;

// Vertext Shader
static const char* vShader = "\n\
#version 330 \n\
layout(location =0) in vec3 pos; \n\
void main() \n\
{ \n\
    gl_Position = vec4(0.4*pos.x,0.4*pos.y,pos.z,1.0); \n\
}"; //0.4 is multiply to make distance less at all postion 1*0.4 = 0.4 < 1

// Fragment Shader
static const char* fShader = "\n\
#version 330 \n\
out vec4 colour ; \n\
void main() \n\
{ \n\
    colour = vec4(1.0,0.0,0.0,1.0); \n\
}";

void CreateTri() {
    GLfloat vertices[] = {   // Value as x, y, z axis 
     -1.0f,-1.0f,0.0f,       // -1,-1 A point of tringle
     1.0f,-1.0f,0.0f,        // 1,-1 B point of tringle
     0.0f,1.0f,0.0f          // 0,-1 C point of tringle
    };

    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
        glGenBuffers(1, &VBO);
        glBindBuffer(GL_ARRAY_BUFFER ,VBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
            glEnableVertexAttribArray(0);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void ShaderAdd(GLuint program, const char* codeofShader, GLenum TypeOfShader) {

    GLuint theshader = glCreateShader(TypeOfShader);
    const GLchar* theCode[1];
    theCode[0] = codeofShader;

    GLint codelenght[1];
    codelenght[0] = strlen(codeofShader);

    glShaderSource(theshader, 1, theCode, codelenght);
    glCompileShader(theshader);

    GLint result = 0;
    GLchar elog[1024] = { 0 };   //error Log


    glGetShaderiv(theshader, GL_COMPILE_STATUS, &result); 
    if (!result) {                                     // compling error checking
        glGetShaderInfoLog(theshader, sizeof(&elog), NULL, elog);
        cout << "Error is 2" << elog;
        return;
    }
    glAttachShader(program, theshader);
    return ;
}

void ComplieShader() {

    shaders = glCreateProgram();
    if (!shaders) {
        cout << "Error in complie shader";
        return;
    }
    ShaderAdd(shaders, vShader, GL_VERTEX_SHADER);
    ShaderAdd(shaders, fShader, GL_FRAGMENT_SHADER);

    GLint result = 0;
    GLchar elog[1024] = { 0 };   //error Log

    glLinkProgram(shaders);   // Linking Program
    glGetProgramiv(shaders, GL_LINK_STATUS, &result);
    if (!result) {
        glGetProgramInfoLog(shaders, sizeof(&elog), NULL, elog);
        cout << "Error is 1" << elog;
        return;
    }

    glValidateProgram(shaders);  // Validating Program
    glGetProgramiv(shaders, GL_VALIDATE_STATUS, &result);
    if (!result) {
        glGetProgramInfoLog(shaders, sizeof(&elog), NULL, elog);
        cout << "Error in validating is " << elog;
        return;
    }

}
int main() {
    //Initialization of GLFW
    if (!glfwInit()) {
        cout << "Error in Initialization of glfwInit()";
        glfwTerminate();     // Terminate if error one created
        return 1;
    }
    // Setup GLFW Windows Properties -- openGL version
    glfwInitHint(GLFW_CONTEXT_VERSION_MAJOR, 3);    // We are using version 3.3 3(Major).3(Minor)
    glfwInitHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // Core Profile No backward compatiblity
    glfwInitHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // Allow forward compatiblity
    glfwInitHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);


    GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "Mordern World openGL Window", NULL, NULL);
    if (!mainWindow) {
        cout << "GLFW Window is not Created There is some Error";
        glfwTerminate();
        return 1;
    }

    //Get Buffer Size Information
    int bWidth, bHeight;
    glfwGetFramebufferSize(mainWindow, &bWidth, &bHeight);

    //Seting Contex for GLEW to use
    glfwMakeContextCurrent(mainWindow);

    // Add or Allow Mordern Extension
    glewExperimental = GL_TRUE;

    //Initialization of GLEW
    if (glewInit() != GLEW_OK) { //glewInit() Function retrun GLEW_OK in success 
        cout << "Error in GLEW Initialization ";
        glfwDestroyWindow(mainWindow);
        glfwTerminate();
        return 1;
    }

    //Setup Viewport
    glViewport(0, 0, bWidth, bHeight);
    CreateTri();
    ComplieShader();

    //loop for window until we close
    while (!glfwWindowShouldClose(mainWindow)) {

        // Handle user input event
        glfwPollEvents();

        //clear Window
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Window will display (R=0, G=0, B=0) Black  
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shaders);
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
        glUseProgram(0);
        glfwSwapBuffers(mainWindow);
    }
    return 0;
}

OUTPUT

  • There are two output by some changes in code

    1. With vertex shader - gl_Position = vec4(0.4*pos.x,0.4*pos.y,pos.z,1.0); 0.4 is multiply in x and y
    2. With vertex shader - gl_Position = vec4(pos.x,pos.y,pos.z,1.0); you will get know where are coordinates(-1,-1; 1,-1; 0,1)(x,y)
  • 6.png

  • 7.png

Summary

  • Rendering Pipeline consists of several steup.
  • Four steup are programmable via shaders (Vertex, Tessellation, Geometry, Fragment).
  • Vertex Shader is mandatory.
  • Vertices: User-defined points in space.
  • Primitives: Groups of vertices that make a simple shape (usually a triangle).
  • Fragments: Per-pixel data created from primitives.
  • Vertex Array Object (VAO): WHAT data a vertex has.
  • Vertex Buffer Object (VBO): The vertex data itself.
  • Shader programs are created with at least a Vertex Shader and then activated before use.
  • We had code the triangle with shader.