Clockworkcoders Tutorials

 




Loading, Compiling, Linking, and Using GLSL Programs

tutorial image

A Simple Vertex Shader

This vertex shader scales all vertices in x and y direction. This doesn't really make much sense, but it is a good example to get started with. All vertices of our primitive (or object or scene) will go through this program. gl_Vertex is the current vertex which is being processed by this program. The vertex has the same (untransformed) values like you specify in glVertex3f(x,y,z) in your C++ program. The gl_ModelViewProjectionMatrix is the concatenated modelview and projection matrix. I assume you know what the modelview and projection matrices are, otherwise you can look it up in the OpenGL SDK.

void main(void)
{
   vec4 a = gl_Vertex;
   a.x = a.x * 0.5;
   a.y = a.y * 0.5;


   gl_Position = gl_ModelViewProjectionMatrix * a;

}       
Vertex Shader Source Code

A Simple Fragment Shader

A fragment is basically a pixel before it is rasterized. If you render a fully visible triangle then all pixels between the three vertices must be drawn. Before those pixels are drawn they go through the fragment processor and for each possible pixel the fragment program is exectuted.

In this simple example every fragment is set to green. gl_FragColor holds the output color.

void main (void)  
{     
   gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);  
}        
Fragment Shader Source Code

Loading Shader

Now we have two simple shaders, a vertex and a fragment shader. If the shaders are located in text files they must be loaded into memory first. This part has nothing to do with OpenGL, it is a simple ASCII file loader. If you write your shaders in Unicode (for the comments), you have to write your own loader. The actual program in memory should be in ASCII. You could also embed your shaders into your C++ code using a static char array. In other words: it doesn't matter how you get your shaders into memory. I recommend using ASCII files. This way you can change your shader code without recompiling your application. The source here simply loads an ASCII Shader File:

unsigned long getFileLength(ifstream& file)
{
if(!file.good()) return 0;

unsigned long pos=file.tellg();
file.seekg(0,ios::end);
unsigned long len = file.tellg();
file.seekg(ios::beg);

return len;
} int loadshader(char* filename, GLchar** ShaderSource, unsigned long* len)
{
ifstream file;
file.open(filename, ios::in); // opens as ASCII!
if(!file) return -1;

len = getFileLength(file);

if (len==0) return -2; // Error: Empty File

*ShaderSource = (GLubyte*) new char[len+1];
if (*ShaderSource == 0) return -3; // can't reserve memory
// len isn't always strlen cause some characters are stripped in ascii read... // it is important to 0-terminate the real length later, len is just max possible value...
*ShaderSource[len] = 0;

unsigned int i=0;
while (file.good())
{
*ShaderSource[i] = file.get(); // get character from file.
if (!file.eof())
i++;
}

*ShaderSource[i] = 0; // 0-terminate it at the correct position

file.close();

return 0; // No Error
} int unloadshader(GLubyte** ShaderSource) { if (*ShaderSource != 0) delete[] *ShaderSource; *ShaderSource = 0; }
C++ Source Code

 

Compiling Shader

First you have to create an OpenGL "Shader Object" specifying what kind of shader it is (e.g. Vertex Shader, Geometry Shader, or Fragment Shader) . A shader object can be created using the OpenGL function glCreateShader with the arguments GL_VERTEX_SHADER or GL_FRAGMENT_SHADER (or GL_GEOMETRY_SHADER_EXT).

GLuint vertexShader, fragmentShader;

vertexShaderObject = glCreateShader(GL_VERTEX_SHADER); fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
C++ Source Code


Once you created the shader objects you can attach (or replace) the shader source code to that object using the OpenGL function glShaderSource:

glShaderSourceARB(vertexShaderObject, 1, &VertexShaderSource, &vlength);
glShaderSourceARB(fragmentShaderObject, 1, &FragmentShaderSource, &flength);
C++ Source Code

Now it is time to compile! This can be done using the OpenGL function glCompileShader:

glCompileShaderARB(vertexShaderObject);

glCompileShaderARB(fragmentShaderObject);
C++ Source Code

Now the shaders are probably compiled. But what if you made some typo when entering the shader source code ? To check if the compile was successful or not you can use glGetObjectParameteriv with "GL_COMPILE_STATUS" argument.

GLint compiled;

glGetObjectParameteriv(ShaderObject, GL_COMPILE_STATUS, &compiled);
if (compiled)
{
   ... // yes it compiled!
}        
C++ Source Code.

If compilation failed, the exact cause can be checked using glGetShaderInfoLog. This usually returns a log message with the error description. The contents of the returned log is depends on the implementation/driver.

GLint blen = 0;	
GLsizei slen = 0; glGetShaderiv(ShaderObject, GL_INFO_LOG_LENGTH , &blen);
if (blen > 1)
{
 GLchar* compiler_log = (GLchar*)malloc(blen);
 glGetInfoLogARB(ShaderObject, blen, &slen, compiler_log);
 cout << "compiler_log:\n", compiler_log);
 free (compiler_log);
}
C++ Source Code

Linking

A vertex shader and fragment shader (and geometry shader) must be put together to a unit before it is possible to link. This unit is called "Program Object". The Program Object is created using glCreateProgram.

The OpenGL function glAttachShader can attach a Shader Objects to a Program Object.

(When using geometry shaders, you have to specify input primitive Type, output primitive type and maximal number of vertices before linking. But for now forget about geometry shaders.)


ProgramObject = glCreateProgram();

glAttachShader(ProgramObject, vertexShaderObject);
glAttachShader(ProgramObject, fragmentShaderObject);

glLinkProgram(ProgramObject);
C++ Source Code

 

To check if linking was successful, glGetObjectParameteriv can be used again, similar to compiling.

GLint linked;
glGetProgramiv(ProgramObject, GL_LINK_STATUS, &linked);
if (linked)
{
// ok! }
C++ Source Code


It is also possible to get a linker log, similar to the compiler log. Instead of using the ShaderObject, use the ProgramObject.

Using Shaders

Once you have a linked Program Object, it is very easy to use that shader with the OpenGL function glUseProgram with the program object as argument. To stop using the program you can call glUseProgram(0).

Example Project

As you see it is pretty easy to load, compile, link and use shaders. On the other side it is pretty complicated if you use many different shaders in your code. I created some C++ classes which simplify the whole process.

the class cwc::glShaderManager loads, compiles and links a GLSL program (from file or memory). It returns a cwc::glShader, which holds/represents the "Program Object". There are many other useful things in that class. It is the "libglsl" I created a while back.

#include "glsl.h"
cwc::glShaderManager SM;
cwc::glShader *shader; shader = SM.loadfromFile("vertexshader.txt","fragmentshader.txt"); // load (and compile, link) from file
if (shader==0)
std::cout << "Error Loading, compiling or linking shader\n"; ... shader->begin(); drawPrimitive shader->end();
C++ Source Code

In future tutorials, this way is used to load/compile/link shaders to reduce code overhead.

Download: GLSL_Loading.zip (Visual Studio 8 Project)
(If you create a project/makefile for a different platform/compiler, please send it to: christen(at)clockworkcoders.com and I will put it here.)

 

Return to Index

Next: Uniform Variables