GLSL: An Introduction

What Is GLSL?

GLSL (GLslang) is a short term for the official OpenGL Shading Language. GLSL is a C/C++ similar high level programming language for several parts of the graphic card. With GLSL you can code (right up to) short programs, called shaders, which are executed on the GPU.

Why Shaders?

Until DirectX 8 hardware (GeForce 2 and lower, Radoen 7000 and lower) the graphic pipeline could only be configured, but not be programmed. For example there is the OpenGL lighting model with ambient, diffuse, specular and emissive lighting. This model is mainly used but there are many other models for lighting. In fixed-function OpenGL only this lighting model could be used, no other. With Shaders you are able to write your own lighting model. But that’s only one feature of shaders. There are thousands of other really nice possibilities: Shadows, Environment Mapping, Per-Pixel Lighting, Bump Mapping, Parallax Bump Mapping, HDR, and much more!

Why GLSL?

Shaders are available in OpenGL till 2002 through ARB_vertex_program and ARB_fragment_program extension. But with those extensions you are only able to use assembly shaders. Because of the growing complexity of lighting and shading models assembly shaders are hard to use. GLSL is a high-level shading language, which means that you can write your shader in C/C++ style. This makes shader development much easier!

What Is The Difference Between Fixed Function Pipeline And GLSL?

There are two types of shaders in GLSL: vertex shaders and fragment shaders.

Vertex Shader

A vertex shader operates on every vertex. So if you call glVertex* (or glDrawArrays, …) the vertex shader is executed for each vertex. If you use a vertex shader you have nearly full control over what is happening with each vertex. But if you use a vertex shader ALL Per-Vertex operations of the fixed function OpenGL pipeline are replaced (see Figure 1):

      Vertex Transformation

 

      Normal Transformation, Normalization and Rescaling

 

      Lighting

 

      Texture Coordinate Generation and Transformation

 

For a full overview what a vertex shader replaces and what it does not replace please see reference [1], page 41. So if you want to use a vertex shader you HAVE to do all these things above on your own (of course, only if you need it :-)

Fragment Shader

A fragment shader operates on every fragment which is produced by rasterization. With fragment shader you have nearly full control over what is happening with each fragment. But just like a vertex shader, a fragment shader replaces ALL Per-Fragment operations of the fixed function OpenGL pipeline (see Figure 1):

      Texture access and application (Texture environments)

 

      Fog

 

For a full overview what a fragment shader replaces and what it does not replace please see reference [1], page 43. Using a fragment shader is just like using a vertex shader, because you HAVE to do all these things above on your own (of course, only if you need it).


Figure 1, OpenGL 1.5 fixed function pipeline [1]

What Does GLSL Look Like?

As mentioned above there are 2 types of shaders, a vertex shader and a fragment shader. Each shader type has other inputs and outputs.

Data Types In GLSL

There are four main types: float, int, bool and sampler. For the first three types, vector types are available:

vec2, vec3, vec4			2D, 3D and 4D floating point vector
ivec2, ivec3, ivec4			2D, 3D and 4D integer vector
bvec2, bvec3, bvec4			2D, 3D and 4D boolean vectors

For floats here are also matrix types:

mat2, mat3, mat4			2x2, 3x3, 4x4 floating point matrix

Samplers are types representing textures. They are used for texture sampling. Sampler types have to be uniform. They are not allowed to be declared as a non-uniform type. Here are the different sampler types:

sampler1D, sampler2D, sampler3D		1D, 2D and 3D texture
samplerCube				Cube Map texture
sampler1Dshadow, sampler2Dshadow	1D and 2D depth-component texture

About Attributes, Uniforms And Varyings

There are three types of inputs and outputs in a shader: uniforms, attributes and varyings.

Uniforms are values which do not change during a rendering, for example the light position or the light color. Uniforms are available in vertex and fragment shaders. Uniforms are read-only.

Attributes are only available in vertex shader and they are input values which change every vertex, for example the vertex position or normals. Attributes are read-only.

Varyings are used for passing data from a vertex shader to a fragment shader. Varyings are (perspective correct) interpolated across the primitive. Varyings are read-only in fragment shader but are read- and writeable in vertex shader (but be careful, reading a varying type before writing to it will return an undefined value). If you want to use varyings you have to declare the same varying in your vertex shader and in your fragment shader.

All uniform, attribute and varying types HAVE to be global. You are not allowed to specify a uniform/attribute/varying type in a function or a void.

Built-In Types

GLSL has some built-in attributes in a vertex shader:

gl_Vertex				4D vector representing the vertex position
gl_Normal				3D vector representing the vertex normal
gl_Color				4D vector representing the vertex color
gl_MultiTexCoordX			4D vector representing the texture coordinate of texture unit X

There are some other built-in attributes, see reference [2], page 41 for a full list.

GLSL also has some built-in uniforms:

gl_ModelViewMatrix			4x4 Matrix representing the model-view matrix.
gl_ModelViewProjectionMatrix		4x4 Matrix representing the model-view-projection matrix.
gl_NormalMatrix				3x3 Matrix representing the inverse transpose model-view matrix.
					This matrix is used for normal transformation.

There are some other built-in uniforms, like lighting states. See reference [2], page 42 for a full list.

GLSL Built-In Varyings:

gl_FrontColor				4D vector representing the primitives front color
gl_BackColor				4D vector representing the primitives back color
gl_TexCoord[X]				4D vector representing the Xth texture coordinate

There are some other built-in varyings. See reference [2], page 44 for a full list.

And last but not least there are some built-in types which are used for shader output:

gl_Position				4D vector representing the final processed vertex position. Only 
					available in vertex shader.
gl_FragColor				4D vector representing the final color which is written in the frame 
					buffer. Only available in fragment shader.
gl_FragDepth				float representing the depth which is written in the depth buffer.
					Only available in fragment shader.

The importance of built-in types is that they are mapped to the OpenGL states. For example if you call glLightfv(GL_LIGHT0, GL_POSITION, my_light_position) this value is available as a uniform using gl_LightSource[0].position in a vertex and/or fragment shader.

Generic Types

You are also able to specify your own attributes, uniforms and varyings. For example if you want to pass a 3D tangent vector for each vertex from your application to the vertex shader you can specify a “Tangent” attribute:

attribute vec3 Tangent;

Here are some other examples:

uniform sampler2D my_color_texture;
uniform mat4 my_texture_matrix;

varying vec3 vertex_to_light_vector;
varying vec3 vertex_to_eye_vector;

attribute vec3 tangent;
attribute vec3 binormal;

Language Details

GLSL is quite similar to C/C++ but there are some minor differences. See reference [1], page 57 for ANSI C features which are not supported in GLSL.

Additionally GLSL has the following features/restrictions:

GLSL is 100% type safe. You are not allowed to assign an integer to a float without casting (by constructor):

float my_float = 1;			// Won’t Work! “1” Is An Integer!
float my_new_float = 1.0;		// Will Work!

Casts have to be done using constructors. For example this won’t work:

vec2 my_vec;
ivec2 my_int_vec;
my_vec2 = (vec2)my_int_vec;		// Won’t Work Because No Constructor Is Used!
my_vec2 = vec2(my_int_vec);		// Will Work!

Vectors and matrices can be only be filled with user-data using constructors:

vec3 my_vec = vec3(1.0, 1.0, 1.0);
mat3 my_mat = mat3(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);

Vector multiplication is component-wise:

vec3 my_vec1 = vec3(5.0, 1.0, 0.0);
vec3 my_vec2 = vec3(1.0, 3.0, 4.0);
vec3 product = my_vec1 * my_vec2;	// Will Return This Vector: (5.0, 3.0, 0.0)

Vector with matrix multiplication is also available.
Matrix * Vector will threat the vector as a column-vector (OpenGL standard)
Vector * Matrix will threat the vector as a row-vector (DirectX standard)
For example a vertex is normally transformed in this way:

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex

This will simply transform the vertex position by the model-view-projection matrix.

There are also many built-in function which can (and should) be used:

dot		a simple dot product
cross		a simple cross product
texture2D	used for sampling a texture
normalize	normalize a vector
clamp		clamping a vector to a minimum and a maximum

For a full list of built-in functions see reference [2], page 46.

Each shader must have a main() void. This void is called if the shader is executed.

Shader Examples

So, up to now we have heard a lot concerning GLSL. But how does a shader look like. Here are some simple examples:

Ambient Shader

The ambient shader surely is the simplest shader available. Each rendered pixel has one specific color:

Vertex Shader:

void main()
{
	// Transforming The Vertex
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader:

Void main()
{
	// Setting Each Pixel To Red
	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Diffuse Shader

The diffuse lighting model is one common used lighting model. It’s a little bit harder to implement:

Vertex Shader:

varying vec3 normal;
varying vec3 vertex_to_light_vector;

void main()
{
	// Transforming The Vertex
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

	// Transforming The Normal To ModelView-Space
	normal = gl_NormalMatrix * gl_Normal; 

	// Transforming The Vertex Position To ModelView-Space
	vec4 vertex_in_modelview_space = gl_ModelViewMatrx * gl_Vertex;

	// Calculating The Vector From The Vertex Position To The Light Position
	vertex_to_light_vector = vec3(gl_LightSource[0].position – vertex_in_modelview_space);
}

Fragment Shader:

varying vec3 normal;
varying vec3 vertex_to_light_vector;

void main()
{
	// Defining The Material Colors
	const vec4 AmbientColor = vec4(0.1, 0.0, 0.0, 1.0);
	const vec4 DiffuseColor = vec4(1.0, 0.0, 0.0, 1.0);

	// Scaling The Input Vector To Length 1
	vec3 normalized_normal = normalize(normal);
	vec3 normalized_vertex_to_light_vector = normalize(vertex_to_light_vector);

	// Calculating The Diffuse Term And Clamping It To [0;1]
	float DiffuseTerm = clamp(dot(normal, vertex_to_light_vector), 0.0, 1.0);

	// Calculating The Final Color
	gl_FragColor = AmbientColor + DiffuseColor * DiffuseTerm;
}

Texture Mapping Shader

This is a simple shader for texture mapping.

Vertex Shader

varying vec2 texture_coordinate;

void main()
{
	// Transforming The Vertex
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

	// Passing The Texture Coordinate Of Texture Unit 0 To The Fragment Shader
	texture_coordinate = vec2(gl_MultiTexCoord0);
}

Fragment Shader

varying vec2 texture_coordinate; uniform sampler2D my_color_texture;

void main()
{
	// Sampling The Texture And Passing It To The Frame Buffer
	gl_FragColor = texture2D(my_color_texture, texture_coordinate);
}

GLSL API – How To Use GLSL In Your OpenGL Application

All right, we heard how to write shaders and what they are good for. But how do we use them in our OpenGL application?

GLSL is available right now through 4 extensions:

GL_ARB_shader_objects
GL_ARB_shading_language_100
GL_ARB_vertex_shader
GL_ARB_fragment_shader

All extensions documents are available in the OpenGL extension registry, see reference [3].

GLSL is very similar to C/C++. So if you want to use a GLSL shader you have to do the following steps:

      Passing the shader source to a shader object

 

      Compiling the shader source

 

    Linking shaders to one program object

A shader object represents your source code. You are able to pass your source code to a shader object and compile the shader object.

A program object represents a useable part of render pipeline.

How To Create Those Objects?

A Program object is created with the command
GLhandleARB glCreateProgramObjectARB()

A Shader object is created with the commend
GLhandleARB glCreateShaderObjectARB(GLenum shaderType)
With shaderType equal to GL_VERTEX_SHADER_ARB or GL_FRAGMENT_SHADER_ARB.

So for example: if you want to create a vertex shader object you have to do the following:

GLenum my_vertex_shader;
my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);

How To Pass Your Shader Source Code To A Shader Object?

This can be done by using

void glShaderSourceARB(GLhangleARB shader, GLuint number_strings, const GLcharARB** strings, Glint * length);

With this command you are able to pass more than one string to one shader object.

      shader is the target shader object.

 

      number_strings is the number of strings you want to pass to the GL.

 

      strings is a pointer to a pointer of chars where the source code is saved.

 

    length is a pointer to ints where the length of each string is saved. If length is equal to NULL then the strings are assumed to be null terminated.

Here an example on how to pass a shader source to a vertex shader:

char * my_source;
my_source = GetSource();

GLenum my_vertex_shader;
my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);

glShaderSourceARB(my_vertex_shader, 1, &my_source, NULL);

How To Compile A Shader Object?

This can simply be done by using

void glCompileShader(GLhandleARB shader);

shader is our shader object

How to link my shader objects to a program object?

First of all we have to create a program object. Then we attach all shader objects which we want to use to this program object using

void glAttachObjectARB(GLhandleARB program, GLhandleARB shader);

program is our program object and shader is our shader object

Then we link our program object with

void glLinkProgramARB(GLhandleARB program);

program is our program object.

How To Use A Program Object?

Program objects can be used with

void glUseProgramObjectARB(GLhandleARB program);

program is our program object

If program is 0 then standard fixed function OpenGL is used

Putting All Together

Here is a code example for loading, compiling, linking and using shaders:

char * my_fragment_shader_source;
char * my_vertex_shader_source;

// Get Vertex And Fragment Shader Sources
my_fragment_shader_source = GetFragmentShaderSource();
my_vertex_shader_source = GetVertexShaderSource();

GLenum my_program;
GLenum my_vertex_shader;
GLenum my_fragment_shader;

// Create Shader And Program Objects
my_program = glCreateProgramObjectARB();
my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
my_fragment_shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

// Load Shader Sources
glShaderSourceARB(my_vertex_shader, 1, &my_vertex_shader_source, NULL);
glShaderSourceARB(my_fragment_shader, 1, &my_fragment_shader_source, NULL);

// Compile The Shaders
glCompileShaderARB(my_vertex_shader);
glCompileShaderARB(my_fragment_shader);

// Attach The Shader Objects To The Program Object
glAttachObjectARB(my_program, my_vertex_shader);
glAttachObjectARB(my_program, my_fragment_shader);

// Link The Program Object
glLinkProgramARB(my_program);

// Use The Program Object Instead Of Fixed Function OpenGL
glUseProgramObjectARB(my_program);

If all shaders were compiled successfully and the program object was linked successfully all renderings after glUseProgramObjectARB will be done using our shader.

Some Other Important Functions

Of course you also can delete (shader and program) objects with

void glDeleteObjectARB(GLhandleARB object)

Another VERY important command is

glGetInfoLogARB(GLhandleARB object, GLsizei maxLenght, GLsizei *length, GLcharARB *infoLog)

object is a shader or program object.
maxLenght is the maximum count of chars which will be written to infoLog.
length is the count of chars which were written to InfoLog.
infoLog is a pointer to chars where the info log will be saved.

This function gives you information about shader and program objects. For example: if you are calling this function after a failed compiling you will get information why the compiling failed.

Uniforms

Uniforms can be passed to the GL using

void glUniform{1|2|3|4}{f|i}ARB(GLint location, TYPE val)
void glUniform{1|2|3|4}{f|i}vARB(GLint location, GLuint count, const TYPE * vals)
void glUniformMatrix{2|3|4|}fvARB(GLint location, GLuint count, GLboolean transpose, const GLfloat * vals)

location is the location of the uniform.
count is the number of values from type TYPE you want to pass.
val is a value from TYPE.
vals is a pointer to values of TYPE (for matrices only float are available).
transpose specifies if the passed matrices should be transposed.

Getting the uniform location can easily be done with

GLint glGetUniformLocationARB(GLhandleARB program, const GLcharARB * name)

program is our program object.
name is the name of the uniform which location we want to get.

So here’s an example of how to use uniforms:

glUseProgramObjectARB(my_program);
int my_vec3_location = glGetUniformLocationARB(my_program, “my_3d_vector”);
glUniform3fARB(my_vec3_location, 1.0f, 4.0f, 3.0f);

Note that you are not able to pass built-in uniform via glUniform!

Generic Attributes

Generic Attributes are just like Uniforms:

void glVertexAttrib{1|2|3|4}{s|f|d}ARB(GLuint index, TYPE val)
void glVertexAttrib{1|2|3|4}{s|f|d}vARB(GLuint index, const TYPE * vals)

index is the location if the attribute.
val is a value from TYPE.
vals is a pointer to values of TYPE (for matrices only float are available)

Getting the attribute location can also easily be done with

GLint glGetAttribLocationARB(GLhandleARB program, const GLcharARB* name)

program is our program object.
name is the name of the attribute which location we want to get.

An example:

glUseProgramObjectARB(my_program);
int my_vec3_location = glGetAttribLocationARB(my_program, “my_3d_vector”);

glBegin(GL_TRIANGLES);
	glVertexAttrib3f(my_vec3_location, 4.0f, 2.0f, 7.0f);
	glVertex3f(1.0f, 1.0f, 1.0f);

	glVertexAttrib3f(my_vec3_location, 2.0f, 9.0f, 2.0f);
	glVertex3f(-1.0f, 1.0f, 1.0f);

	glVertexAttrib3f(my_vec3_location, 1.0f, 0.0f, 5.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);
glEnd();

Using Textures With GLSL

Because many GLSL beginners have problems with texturing I want to say something about this now.

As I said before... textures are available in GLSL via sampler, which have to be uniform. But how do we tell GLSL which texture image should be used for which sampler?

Textures are not directly passed to GLSL, but we pass the texture unit where our texture is bound to OpenGL. This works in the following way:

      Get the sampler uniform location.

 

      Bind the texture to texture unit i.

 

    Pass i as an integer by glUniform.

Here an example:

glUseProgramObjectARB(my_program);
int my_sampler_uniform_location = glGetUniformLocationARB(my_program, “my_color_texture”);

glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, my_texture_object);

glUniform1iARB(my_sampler_uniform_location, i);

i is the texture unit where I want to bind my texture.

There are many other useful functions for using GLSL. See the extension specification or reference [4] for more information.

Shader Hardware/Driver Overview

Alright, we have heard many about shaders and GLSL. But which hardware is able to run shaders?

GLSL is very similar to DirectX Shader Model 3.0

Right now GLSL is available on the following graphic cards:

ATI Radeon 9500, 9600, 9700 and 9800 using Catalyst 4.5 driver (which is the most bug-free ATI GLSL driver now).

nVidia GeForce FX 5200, 5600, 5700, 5800, 5900, 5950 using forceware 60 series (you can get these drivers at http://www.3dchipset.com/drivers/beta/nvidia/nt5/6111.php )

Because of the fact that these cards only support Shader Model 2.0 some features of GLSL are not available, like loops, vertex shader sampler …

I don't think anything older than these cards will support GLSL fragment shader. However I think there might be vertex shader emulation in driver software for these cards.

GLSL Shader Development Tools

There are also some nice shader development tools for GLSL:

Typhoon Labs has a really nice shader development tool called “Shader Designer”. You can get it at http://www.typhoonlabs.com

ATI and 3Dlabs are also working together on a GLSL version of RenderMonkey. This tool is not available yet but should come out very soon!

GLSL References

Finally, I would like to list some nice GLSL references:

http://www.clockworkcoders.com/oglsl
Some really nice tutorials!

http://www.3dlabs.com/support/developer/ogl2/index.htm
3DLabs GLSL site

http://www.ati.com/developer/sdk/RadeonSDK/Html/Samples/OpenGL/GLSLSimpleShader.html
ATI GLSL examples

http://developer.nvidia.com/object/sdk_home.html#NV40_Video_Clips
NV SDK 7.0 with some GLSL examples

http://esprit.campus.luth.se/~humus
Humus site with also some nice GLSL examples

Thanks for reading!
Questions and Feedback is welcome! :-)
Florian Rudolf
ICQ# 59184081

"If you want to contact my please make sure that it doesn't look like spam! I get a lot of spam mail/ICQ requests... Everything suspicious will be deleted"

References:

[1] OpenGL Shading Language Master Class Notes, 3DLabs, March/April 2004
http://www.3dlabs.com/support/developer/ogl2/presentations/GLSLMasterClass2004.pdf
[2] The OpenGL Shading Language Specification Version 1.051, 3DLabs, February 2003
http://www.3dlabs.com/support/developer/ogl2/downloads/ShaderSpecV1.051.pdf
[3] The OpenGL extension registry
http://oss.sgi.com/projects/ogl-sample/registry/
[4] OpenGL shading language “The orange book”, Randi J. Rost, ISBN 0-321-19789-5
http://www.3dshaders.com/