Lazy Foo' Productions

Hello GLSL

Last Updated 8/09/12

Up until now we've been using the old Fixed Function Pipeline which did all our vertex operations (glTranslate(), glVertex(), glTexCoord(), etc) and fragment operations
(I'll show you those in a little bit) for us. This was nice when you're a beginner and you're not doing anything complicated, but when you need power and flexibility
the fixed function pipeline is constraining.

Enter GLSL (OpenGLShading Language) and the Programmable Pipeline. With GLSL, you can give your OpenGL program executable shader programs which
control how your GPU handles the data you send it. The GLSL Programmable Pipeline can do everything the Fixed Function Pipeline can do and more which made the FFP obsolete.
With the release of OpenGL 3.0+, the fixed function pipeline is deprecated and if you want to do any rendering you need to tell the GPU how to handle your data with
GLSL.

Why didn't the tutorial set start off with the Programmable Pipeline? Because as you're about to see it takes significantly more work to get a GLSL shader program going.
This tutorial will get you off the ground by creating your first GLSL shader program.

In this tutorial and future ones we'll loading shader programs. Shader programs control how our OpenGL program operates, so they could be loaded in the initGL() function. In
future tutorials we'll be loading text shader files so they could be loaded in the loadMedia() function.

Because graphics programs are so unique, they'll get their own loading function loadGP().

Here's the overview of the LShaderProgram class which will serve as the base class for all of our shader programs.

Don't obsess with the details of the class too much for now, but take notice of the "mProgramID" member variable. Just like we bind texture IDs and VBO IDs to use them, we'll be
binding shader program IDs to use them.

The constructor for LShaderProgram just initializes the ID to 0. The destructor just calls freeProgram() which just calls glDeleteProgram() to delete the program much in the same
way we would call glDeleteTextures() to delete a texture.

To bind a shader program for use, we call glUseProgram() on the program ID. To make sure the shader program bound successfully, we check if there were any errors using
glGetError(). If there were errors, we report them to the console. If there were no errors we return true.

To unbind the current shader program, we just bind a null ID. On OpenGL 2.1, this will cause the old fixed function pipeline to be used. In the post OpenGL 3.0 world, binding a
NULL shader will cause nothing to be rendered because there's no fixed function pipeline.

Now getting a GLSL shader program working requires quite a bit of communication with the GPU and we need to be able to print the GLSL shader program logs to know if something goes
wrong. This information is vital when trying to debug your GLSL shader program.

First we want to check if the ID we gave it was even a shader program using glIsProgram(). If it is, we check to find out how long the info log is in characters using glGetProgramiv().
Then we allocate the needed character string and get the actual program info log using glGetProgramInfoLog(). If the info log is longer than 0 characters (which means one actually
exists) we print it out to the console. After we'll done with the program log, we deallocate the string.

Now if the ID was not even a program, we print an error to the console.

Here we have printShaderLog() which prints out the log for a shader pretty much in the same way printProgramLog() prints out the info for a program.

You're probably wondering what's the difference between a shader and a program. A shader controls part of your graphics pipeline. A vertex shader controls how to process vertex
data and a fragment shader controls fragment operations. The program has a vertex shader and a fragment shader attached to it (and maybe other shaders like a geometry shader).
With the shaders attached to it, the shader program controls how data is rendered.

At the top of the loadProgram() function we allocate a shader program ID using glCreateProgram(). A shader program isn't very useful without some vertex or fragment operation
attached to it. So let's start attaching some shaders.

Ok here we allocate a vertex shader ID using glCreateShader() with the GL_VERTEX_SHADER argument. Then we have some GLSL source code put directly into an array of strings named
"vertexShaderSource". We then set the source code for the vertex shader using glShaderSource().

The first argument is the vertex shader ID. The second argument is how many source strings you're using. Caution: the GLSL compiler expects one long string per source file. Much
like in C++, you can have more than one source file per shader. However, it will treat each string in the array as a source file.

The third argument is the pointer to the array of shader source strings. The last argument is the array of string lengths for each of the shader source strings. If this is null,
the GLSL source compiler assumes each string is null terminated.

Here we compile and attach the fragment shader much in the same way we did with the vertex shader. Do make a note of the fact that the GLSL shader code is different for the
fragment shader. This is obviously because vertices and fragments are two different things that require different operations.

With both the vertex and fragment shaders attached to the shader program, we link the program. Vertex and fragment shaders typically have to have data sent between them so the
linking process makes sure the shaders play nice with each other.

Like with compilation, we use glGetProgramiv() to make sure the program linked properly. If it did, it means our program is ready to be used.

In our render function, we have the code to render a cyan quad in the center of the screen. Yet for some reason, when this demo program compiles and runs we get this:

Why? Let's look at the shader source code we gave the shaders.

From Vertex Shader Source

void main()
{
gl_Position = gl_Vertex;
}

Here is the GLSL code for the vertex shader. Even if you've never seen the GLSL documentation, its C like syntax should be easy to pick up on the fly.

Notice how the final position of the vertex is the same as the vertex we took in. We never multiplied against the projection or modelview matrices so glOrtho(), glTranslate(),
and our other OpenGL matrix calls have no effect and the quad we rendered uses untransformed matrix coordinates. This is what we mean by programmable pipeline because you program
how the GPU pipeline processes the data.

So because the vertices are untransformed, the quad will consume the entire screen as opposed to being 100 pixels wide in the center.

From Fragment Shader Source

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

Here's the fragment shader and the reason the quad is red as opposed to cyan. In the fragment shader we don't take into account the color attribute and just set the output fragment
to be red 1, green 0, blue 0, and alpha 1. While it appears strange how the OpenGL program behaved, it was only doing what we told it to do.

This is the power of shaders. By giving control to the programmer how the graphics data is processed you can achieve the powerful effects you see in modern games, or something
completely pointless like untransformed red geometry =).