1. GLSL Setup

Introduction to GLSL Setup

GLSL (OpenGL Shading Language) is a high-level shading language used to program the GPU for advanced graphical effects. Shaders allow you to customize how your graphics are rendered, providing immense flexibility and control over the rendering pipeline.

In this tutorial, we’ll dive into setting up GLSL shaders in OpenGL, covering the process of creating, compiling, and linking shaders. We’ll also introduce a reusable shader class to streamline shader management for future projects. By the end of this tutorial, you’ll understand how shaders fit into OpenGL and how to integrate them into your programs.

What are Shaders?

In traditional fixed-function OpenGL, the rendering process followed a predefined pipeline where you had limited control over rendering steps. With GLSL, you can replace parts of this pipeline with your own code in the form of shaders.

Shaders are small programs that run directly on the GPU. They are written in GLSL and come in different types:

  • Vertex Shaders: These process each vertex of your geometry. You can manipulate positions, normals, and texture coordinates here.
  • Fragment Shaders: These calculate the color of each pixel in your rendered objects, allowing effects like custom lighting, shadowing, and texture blending.
  • Geometry Shaders: These are optional and allow you to generate new geometry dynamically. For instance, you could create additional vertices for tessellation or procedural geometry.

How Shaders Work in OpenGL

Shaders are written in GLSL and must go through the following steps before they can be used:
1. Source Code Loading: Shader code is loaded into memory from text files.
2. Compilation: The source code is compiled into GPU-compatible bytecode.
3. Linking: Compiled shaders are linked together to form a shader program, which can be used by the GPU.
4. Binding: The program is activated in OpenGL, and all subsequent rendering commands use it.

Setting Up a Shader Class

To manage shaders effectively, we’ll create a reusable `Shader` class. This class handles loading, compiling, linking, and activating shaders, abstracting the complexity of shader setup. Let’s start by defining its interface in a header file:

#ifndef __SHADER_H
#define __SHADER_H

#if (defined(__MACH__) && defined(__APPLE__))
#include <cstdlib>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <OpenGL/glext.h>
#else
#include <cstdlib>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#endif

#include <cstring>

class Shader {
public:
    Shader();
    Shader(const char *vsFile, const char *fsFile);
    ~Shader();

    void init(const char *vsFile, const char *fsFile);
    void bind();
    void unbind();
    unsigned int id();

private:
    unsigned int shader_id;
    unsigned int shader_vp;
    unsigned int shader_fp;
};

#endif

Explanation:

  • Shader Constructor: Initializes a new shader object, allowing us to load vertex and fragment shaders.
  • init: Loads and compiles the shaders, then links them into a shader program.
  • bind and unbind: Activates or deactivates the shader program for rendering.

Implementing the Shader Class

Here’s the implementation of the `Shader` class, which handles the intricacies of loading, compiling, and linking shaders.

#include "shader.h"
#include <iostream>
#include <fstream>

Shader::Shader() {}

Shader::Shader(const char *vsFile, const char *fsFile) {
    init(vsFile, fsFile);
}

void Shader::init(const char *vsFile, const char *fsFile) {
    shader_vp = glCreateShader(GL_VERTEX_SHADER);
    shader_fp = glCreateShader(GL_FRAGMENT_SHADER);

    const char* vsText = textFileRead(vsFile);
    const char* fsText = textFileRead(fsFile);

    if (vsText == nullptr || fsText == nullptr) {
        std::cerr << "Shader files not found!" << std::endl;
        return;
    }

    glShaderSource(shader_vp, 1, &vsText, nullptr);
    glShaderSource(shader_fp, 1, &fsText, nullptr);

    glCompileShader(shader_vp);
    glCompileShader(shader_fp);

    shader_id = glCreateProgram();
    glAttachShader(shader_id, shader_fp);
    glAttachShader(shader_id, shader_vp);
    glLinkProgram(shader_id);
}

void Shader::bind() {
    glUseProgram(shader_id);
}

void Shader::unbind() {
    glUseProgram(0);
}

unsigned int Shader::id() {
    return shader_id;
}

Shader::~Shader() {
    glDetachShader(shader_id, shader_fp);
    glDetachShader(shader_id, shader_vp);
    glDeleteShader(shader_fp);
    glDeleteShader(shader_vp);
    glDeleteProgram(shader_id);
}

Explanation:

  • glShaderSource: Associates the shader source code with the shader object.
  • glCompileShader: Compiles the shader source code into a binary form understood by the GPU.
  • glAttachShader: Attaches the compiled shader to the program.
  • glLinkProgram: Links the shaders into a final executable shader program.

Main Program

The following program demonstrates how to use the `Shader` class to render a spinning teapot using GLSL.

#include "shader.h"

Shader shader;
GLfloat angle = 0.0f;

void init() {
    glEnable(GL_DEPTH_TEST);
    shader.init("shader.vert", "shader.frag");
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

    shader.bind();
    glRotatef(angle, 1.0f, 1.0f, 0.0f);
    glutWireTeapot(1.0);
    shader.unbind();

    glutSwapBuffers();
    angle += 1.0f;
}

void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, (GLfloat)w / h, 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("GLSL Setup Tutorial");

    glewInit();
    init();

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIdleFunc(display);

    glutMainLoop();
    return 0;
}

Shader Source Code

Vertex Shader (`shader.vert`):

void main() {
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader (`shader.frag`):

void main() {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

Download Links

If you have any questions, feel free to email me at swiftless@gmail.com.

  • March 25, 2010
  • 64