6/21

2.1. Shader program

What is a shader

A shader is a user defined program that is executed on the GPU (in parallel). Shaders have been introduced to allow the user to modify the basic rendering pipeline to fits its expectations. Initially, shaders were typically used to modify the appearance of the material with respect to the light - called shading - The name shader remains, but may be used for much larger purposes.

OpenGL shaders are written in GLSL (OpenGL Shading Language) language, which looks like simplified C++ specialized for 3D vector and matrix operations. GLSL code followed the evolution of OpenGL, and is thus links to different version. In our case, we will use GLSL 4.30 (similar to OpenGL 4.3)

To be executed by the graphics card, human readable GLSL code has to be compiled and linked into a binary shader program. This compilation is fast and is realized on the fly every time the main executable is run. Once compiled, the shader program that has been set is executed every time the user call for a data display.

A shader program should be made of, at least, two different type of shaders [1] * A vertex shader * A fragment shader

Vertex shader are programs executed in parallel on each vertex of the data. The typical role of vertex shader is to compute the projection of vertices in applying the projection matrix to vertex position. More generally, a vertex shader receives as input a vertex and outputs a modified one.

Fragment shader are programs executed in parallel on each visible fragments. A fragment corresponds to a rasterized element of a primitive (can be viewed as a pixel belonging to a visible primitive). Typical role of a fragment shader is to compute the color of the current fragment. More generally, a fragment shader receives as input the linearly interpolated values of the vertices from a primitive and outputs a color.

Application

In our application we will consider a minimal shader displaying a uniformly red triangle.

The vertex shader is the following

#version 430 core
layout (location = 0) in vec4 position;
void main()
{
    gl_Position = position;
}
  • #version 430 core indicates the use of GLSL 4.30

  • layout (location = 0) in vec4 position indicates that the first input parameter associated to a vertex (indexed at location 0) will be a variable named position. The variable will be a 4D vector \((x,y,z,w)\). In this case, this is the only per-vertex parameter that we will pass to the GPU.

  • Once the global variables are declared (here position) GLSL code always contains a main function with the actual code to execute.

  • One of the objective of the vertex shader is to fill the global output variable gl_Position. This variable contains the projected position of the input vertex position. In this simple case, gl_Position is filled with the coordinates of the input 3D vertex position, therefore no perspective projection is applied.

The fragment shader is the following

#version 430 core
out vec4 FragColor;
void main()
{
    FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
  • In this case, we have a single output variable FragColor as a 4-components vector. This output variable will be interpreted as the color of the current fragment. As we aim at displaying a uniform red triangle, we set this output variable as the constant value vec4(1.0, 0.0, 0.0, 1.0). Note that the last - alpha - parameter is not used in this case.

Note that in this application, shaders are hard-coded inline within the code as strings.

The next step is to call the compilation of each shader from their code, and link them as a shader program. The corresponding code (shader definition and compilation) is the following

    // ************************************************* //
    //             1 - Setup Shaders                     //
    // ************************************************* //

    std::cout<<"*** Setup Shader ***"<<std::endl;

    // ******************************** //
    // 1.1 Define vertex and fragment shader
    // ******************************** //
    //   Here GLSL code is hard-coded inline as strings
    const char* vertex_shader_txt = "                                      \n \
            #version 430 core                                              \n \
            layout (location = 0) in vec4 position;                        \n \
            void main()                                                    \n \
            {                                                              \n \
                gl_Position = position;                                    \n \
            }";
    const char* fragment_shader_txt = "                                    \n \
            #version 430 core                                              \n \
            out vec4 FragColor;                                            \n \
            void main()                                                    \n \
            {                                                              \n \
                FragColor = vec4(1.0, 0.0, 0.0, 1.0);                      \n \
            }";



    // ******************************** //
    // 1.2 Create shader program
    // ******************************** //

    // (Warning: the following code doesn't perform error checking)

    //  A. Compile each shader separately
    // ******************************************* //

    // Create identifiant for vertex and fragment shaders
    const GLuint vertex_shader   = glCreateShader(GL_VERTEX_SHADER);
    const GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);

    // Set source from the shader
    glShaderSource(vertex_shader, 1, &vertex_shader_txt, nullptr);
    // Compile the current shader (here vertex shader)
    glCompileShader(vertex_shader);

    // Set source from the shader
    glShaderSource(fragment_shader, 1, &fragment_shader_txt, nullptr);
    // Compile the current shader (here fragment shader)
    glCompileShader(fragment_shader);

    //  B. Link shaders into shader program
    // ******************************************* //

    // Create identifiant for shader program
    const GLuint shader = glCreateProgram();

    // Attach vertex and fragment shader before linking
    glAttachShader(shader, vertex_shader);
    glAttachShader(shader, fragment_shader);
    // Perform the link of the two shaders into a program
    glLinkProgram(shader);

    // Compiled shaders can be safely deleted
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);

Note the following documentation for further information


1. You may also encounter Geometry, Tessellation and Compute shaders