1. GLSL Setup

GLSL is the OpenGL Shader Language, and is the OpenGL version of HLSL for DirectX. It allows us to manipulate the scene on a per vertex and per fragment basis, giving us total control.

Jump To:

shader.h Source
shader.cpp Source
main.cpp Source
shader.vert Source
shader.frag Source
Download

To start off the OpenGL Shader Language tutorials, I am going to take our basic
Smooth Rotation OpenGL tutorial and add to it. Now for those of you that didn’t
read about GLSL on the tutorials page, up the top, it will just give you a brief outline on what shaders can do as well as how it compares to DirectX, and I recommend taking a look.

If you don’t know anything about GLSL already, a shader program is made up of either a Vertex Shader and a Fragment Shader, or a Vertex Shader, Geometry Shader and Fragment Shader, which is supported on graphics cards from the Nvidia 8xxx cards onwards.

Here is a quick rundown on the different types of shader programs:

A Vertex Shader allows us to manipulate vertices, and gives us access to vertex normals, our lighting pipeline, object materials, and other functions.

A Fragment Shader allows us to manipulate the outputting pixels that we see on our screen, here we can create our effects such as bloom, lighting, texturing, fog, etc.

A Geometry Shader (which I will introduce in later tutorials), allows us to actually create and remove vertices as we see fit. This is great for dynamic level of detail, allows us to create ‘fuzzy’ polygons, explode shapes, and do a bunch of nifty effects.

 

I’d like to point out now, that this tutorial is aimed for OpenGL 2.0 and up, where GLSL is made a part of OpenGL. All prior versions will need to use ARB extensions.

Shader.H File:

Being our first GLSL tutorial, this might be a little long as there is a bit to setting up your first application. It’s not really complicated, and the final code is quite short, but because there is such a range of things to do, this page is going to be a bit long.

Having used Java quite a bit recently thanks to my University, I am growing a fondness for Object Oriented programming. This tutorial is going to take an Object Oriented style approach at GLSL shaders, and we will end up with a class that we can reuse each time we want to create a shader program.

To start off with our class declaration, open up a new C++ header file called "shader.h". In this we want to start off with a new class, so type something along the lines of:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
    #ifndef __SHADER_H
#define __SHADER_H

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

#include <string>

class Shader {
public:
    Shader();
    ~Shader();

private:

};
#endif

This will be a nice clean skeleton on which we are going to add everything. You may notice the extra long code for our header files which includes an if statement. This code is explained on the new OpenGL Tips page, in short, it means you can compile on Mac as well.

Now, we want to add a few methods to the public: section of our class. We will give whoever uses this class the ability to create a shader using the constructor, or by using an init method. Both of which will take the file names for the vertex and fragment shader.

So we need to add the lines:

Shader(const char *vsFile, const char *fsFile);
void init(const char *vsFile, const char *fsFile);

Ok, so we now know how to create our shaders. But what about using them? Well all we need are three methods, bind and unbind to enable and disable our shader, and id to get the number associated with our shader, so that we can pass variable through to the shader:

void bind();
void unbind();
unsigned int id();

Nice, now all that is left is to do is to create three private variables. One for our shader program, one for our fragment shader and a final one for our vertex shader. I will be calling them:

unsigned int shader_id;
unsigned int shader_vp;
unsigned int shader_fp;

When we put all of this into shader.h, we get the following file, which is quite simple and does all that we need.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.

   

#ifndef __SHADER_H
#define __SHADER_H

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

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

Shader.CPP File:

Now lets move onto our shader.cpp file. This is where we are going to fill all of our methods outlined above, and introduce a new method.

So lets start from the beginning, with our constructors:

Shader::Shader() {  
}

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

Phew, now that that’s out of the way.. Lets move onto our init method.

The first two lines we are going to add, are going to tell OpenGL to create a vertex shader and a fragment shader, and assign the id’s of these to our shader_vp and shader_fp variables.

    shader_vp = glCreateShader(GL_VERTEX_SHADER);
    shader_fp = glCreateShader(GL_FRAGMENT_SHADER);
    
Now the following two lines, read in our vertex shader and fragment shader files. These are just standard text documents, with whatever extension you wish to add. The method textFileRead() can be any method that returns the contents of the shader files as a char*. This tutorial includes code for this, but I will not be explaining it, as I assume you have some knowledge of C++.

    const char* vsText = textFileRead(vsFile);
    const char* fsText = textFileRead(fsFile);    
    
Then I just do a little check to see if we were able to load both the files, if we have a problem reading them, we will just skip the method, and OpenGL should go on with everything like normal, just our shader won’t function.

    if (vsText == NULL || fsText == NULL) {
        cerr << “Either vertex shader or fragment shader file not found.” << endl;
        return;
    }

The next two lines pass our shader file contents to OpenGL to attach it to our shaders.
    
    glShaderSource(shader_vp, 1, &vsText, 0);
    glShaderSource(shader_fp, 1, &fsText, 0);

This next two lines, tells OpenGL to compile our shaders, which we have already bound the source code to.
    
    glCompileShader(shader_vp);
    glCompileShader(shader_fp);
    
And finally, we create our shader_id as a shader program. And then attach the vertex shader and the fragment shader to the shader program, and finally, link the program.

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

So now we can read in a shader file and compile it as a shader program. So lets take a look at the methods which allow us to use this:

This first method, id(), will return the shader_id program. This is used for when we bind our shader, or for when we want to pass variables through to our shader.
unsigned int Shader::id() {
    return shader_id;
}

This second method, bind(), will simply attach our shader, and anything drawn afterwards will use this shader, either until this shader is unbound or another shader is enabled.
void Shader::bind() {
    glUseProgram(shader_id);
}

The third of our usage methods, is unbind(), this simply binds the shader 0, which is reserved for OpenGL, and will disable our current shader.
void Shader::unbind() {
    glUseProgram(0);
}

So whats left now? We can create our shader, and we can use our shader. So why don’t we clean up our shader when we are done?

I will be doing this from within the destructor method for our shader, and this should only be called once at the end of our application, otherwise we will have to recreate our shader, which is an expensive process.

Our desctructor is made up of only 5 method calls to OpenGL. The first two, just detach our vertex shader and fragment shader from our shader project, and the final 3 just delete all of the shaders and then deletes the shader program.

    glDetachShader(shader_id, shader_fp);
    glDetachShader(shader_id, shader_vp);
    
    glDeleteShader(shader_fp);
    glDeleteShader(shader_vp);
    glDeleteProgram(shader_id);

And that’s all there is to creating a shader program. It’s not that tricky, and it all makes sense when sit back and think about what it is doing.

Now lets take a look at the main.cpp file below for this project.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
    #include “shader.h”
#include <string.h>
#include <iostream>
#include <stdlib.h>

using namespace std;

static char* textFileRead(const char *fileName) {
    char* text;
    
    if (fileName != NULL) {
        FILE *file = fopen(fileName, “rt”);
        
        if (file != NULL) {
            fseek(file, 0, SEEK_END);
            int count = ftell(file);
            rewind(file);
            
            if (count > 0) {
                text = (char*)malloc(sizeof(char) * (count + 1));
                count = fread(text, sizeof(char), count, file);
                text[count] = ‘\0′;
            }
            fclose(file);
        }
    }
    return text;
}

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 == NULL || fsText == NULL) {
        cerr << “Either vertex shader or fragment shader file not found.” << endl;
        return;
    }
    
    glShaderSource(shader_vp, 1, &vsText, 0);
    glShaderSource(shader_fp, 1, &fsText, 0);
    
    glCompileShader(shader_vp);
    glCompileShader(shader_fp);
    
    shader_id = glCreateProgram();
    glAttachShader(shader_id, shader_fp);
    glAttachShader(shader_id, shader_vp);
    glLinkProgram(shader_id);
}

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

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

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

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

Main.CPP File:

As most of the main.cpp file is based on the double buffered window tutorial, I am only going to explain the lines that are related to GLSL and implementing our new class. The first thing we are going to do here, is include our header file:

#include “shader.h”

Now that we have access to our shader class, we need to declare a Shader object. I am going to call it shader for lack of a better word in this tutorial:

Shader shader;

Now before we can do anything with our shaders, we need to call glewInit(), this sets up GLEW so that we can use the extensions required to use shaders. This code will go below our glutCreateWindow call in our main method:

glewInit();

Simple enough so far. Now we want to initialize our shader. Inside our void init(void) method, add the following line:

shader.init(“shader.vert”, “shader.frag”);

For this tutorial, our shader programs are going to be called shader.vert and shader.frag and will be located in the same directory as the the compiled application.

All that is left now is to use the shader. In our display method, we want the shader to be applied to our cube, so before we draw the cube we want to bind our shader, and afterwards we want to unbind it:

shader.bind();
cube();
shader.unbind();

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.

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

#include “shader.h”

Shader shader;

GLfloat angle = 0.0; //set the angle of rotation

void init(void) {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    
    shader.init(“shader.vert”, “shader.frag”);
}

void cube (void) {
    glRotatef(angle, 1.0, 0.0, 0.0); //rotate on the x axis
    glRotatef(angle, 0.0, 1.0, 0.0); //rotate on the y axis
    glRotatef(angle, 0.0, 0.0, 1.0); //rotate on the z axis
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glutWireCube(2);
}

void display (void) {
    glClearColor (0.0,0.0,0.0,1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity<