Lesson 2: The first shader

In this lesson, we will take our first steps in actually programming a shader. This will also include a first introduction to the shader language GLSL, its datatypes, and constructs. Finally, you will create your first shader, which you can then check out right away. Let's go!


 1. GLSL - The GL Shading Language

When the first shading processors where introduced, developers had to write their shaders in pure assembler. Nowadays, a couple of standard languages have been developed to make programming shaders clear and simple. Beside CG from Nvidia and HLSL from Microsoft, there's GLSL from the GL ARB (Architectures Review Board). It's closely related to C and a part of the OpenGL 2.0 standard. This has the big advantage that no external compiler is needed to compile the sourcecode, because it's included in the OpenGL implementation.


 A Typical Shader

The following code snippet shows a typical GLSL vertex shader, which is saved into a file. Vertex shaders normally have the extension .vert, where pixel shader use .frag:


[flat_wave.vert]
	uniform float time;

	void main(void)
	{
		vec4 v = vec4(gl_Vertex);
		v.z = sin(5.0 * v.x + time * 0.01) * 0.25;
		gl_Position = gl_ModelViewProjectionMatrix * v;
	}

Listing 1: An animating vertex shader


This above listing shows the shader's close similarity to C. Every shader needs a function void main(void), which takes fragment texturing and coloring stage a parameter, nor returns one.

Inside the shader, all sorts of computations can be performed and variables read or written. In addition to the standard datatypes: bool, int, and float, there are types for vectors, matrices, and textures.

Datatype Description
 vec2, vec3, vec4  Floatvector with 2, 3 or 4 elements
 ivec2, ivec3, ivec4  Integervector with 2, 3 or 4 elements
 bvec2, bvec3, bvec4  Boolvector with 2, 3 or 4 elementes
 mat2, mat3, mat4  Floatmatrix with 2x2, 3x3 or 4x4 elements
 sampler1D, sampler2D,
 sampler3D
 1D-, 2D-, 3D-Texture
 samplerCube  Cubemap-Texture
Table #1: New datatypes in GLSL

Working with these datatypes alleviates some of the restrictions of C and is very intuitive. For example, it's possible to multiply matrices with vectors or other matrices. Dealing with vectors, on a whole, has been simplified.


 Vectors

Vectors are initialized by their constructors. The constructors accept single values (scalars), as well as vectors or a combination of both. The vector elements are initialized in the order of the given scalars and/or vectors. If a vector is constructed using a single scalar, all its fields are assigned the same value.

The fields of a vector are either selected, using the traditional bracked operator [] or using the dot operator. The developer is free to choose any of the aliases .r, .g, .b or .a for colors (instead of the indicies 0, 1, 2, 3), .x, .y, .z, .w for space coordinates, or .s, .t, .p, .q for texture coordinates. It's even possible to access multiple fields at once, by using a sequence of the characters mentioned above. The following snippet shows what code taking advantage of these extended features looks like:


	vec3 position = vec3(1.0, 2.0, 3.0);
	vec4 color = vec4(position, 3.0);
	vec4 white = vec4(1.0);
	
	float xPos = position[0];
	float yPos = position.y;
	vec2 redalpha = color.ra;
	vec2 doublePos = position.xyxy;
	color.r = 0.75;
		
	struct dirlight {
		vec3 direction;
		vec3 color;
	};
	
	dirlight d1;
	dirlight d2 = dirlight(vec3(1.0,1.0,0.0), vec3(0.8,0.8,0.4));

Listing #2: Example vectors using GLSL


 Control structures

Except for the switch statement, all common control structures, like if, and else, for, and while can be used.
On older graphic boards, attention must be paid to the while loop. The compiler needs to be able to unroll the loop, this means that the number of interations has to be determined at compile time. Therefore, dependencies on (non-constant) variables are not allowed.


 Predefined functions

GLSL provides a wide range of predefined functions. Besides the trigonometric functions, sin(float) and cos(float), there are functions like dot(float) from linear algebra.
A special function, which should be mentioned here, is ftransform(). It returns the result of the vertex transformation from the fixed functionality. It also uses the same optimizations as the fixed functionality.


 Communication between application and shader

Developers have several options to communicate with a shader. However, this communication is only one-way, because you can't call a shader and poll its results. Instead of returning values to the calling application, the shader writes into the color- and the depthbuffer.

As already mentioned in lesson #1, shaders are able to read OpenGL states. This makes sense when querying, for example, OpenGL light sources or the current fog color from your shader. Theoretically, you could use unused fields from light sources to store user data, but things would get really messy.
In fact, shaders are able to access texture memory. So, you might use texture data as a new way of communicating with a shader. On newer hardware, it is even possible to communicate between shaders using this method.

Luckily, GLSL also defines a proper way of communicating with a shader using the qualifier uniform and attribute. Shader variables using these qualifiers have to be accessed read-only inside the shader, but the application can change them at anytime, e.g. a time variable letting the shader animate something over time).

Uniform variables can be seen as global variables, which are constant throughout a primitive or a whole scene. They can be any of the datatypes supported by the shaders. Their values can be changed in the application at runtime and can be read by vertex shaders and pixel shaders alike. In contrast to attribute variables, uniform variables can only be set outside glBegin() and glEnd().

Attribute variables make it possible to define new attributes for a vertex, like heat or weight. Because they only apply to vertices, they can't be read by pixel shader. In the following lessons, we will only work with uniform variables but for the sake of completeness attribute variables are mentioned here.


 Communication between OpenGL and shader

Communication between OpenGL and a shader is done using a predefined set of global variables. Depending on the variable, they can be read or written by a shader.

Variable name  Datatype  Description
Readable in vertex shaders (vertex attributes)
 gl_Color  vec4  First drawing color
 gl_SecondaryColor  vec4  Second drawing color
 gl_Normal  vec3  Normal vector of a vertex
 gl_Vertex  vec4  Orientation vector of a vertex
 gl_MultiTexCoord[0-7]  vec4  Texture coordinates of the eight texture units
Writeable in vertex shaders
 gl_Position  vec4  Final position of the vertex on the screen
 gl_PointSize  float  Pixel size of the vertex
Readable in pixel shaders
 gl_FragCoord  vec4  Position of the fragment on the screen
 gl_FrontFacing  bool  Is the primitive of the fragment facing forward?
Writeable in fragment shaders
 gl_FragColor  vec4  Final color value of the fragment
 gl_FragDepth  float  Overwrites the calculated Z-value of the fragment 
Table #2: Global variables and attributes in shaders

The variables gl_Position and gl_FragColor play a central role , as they represent the "results" of the individual shaders. gl_Position has to be set (!) by every vertex shader, even if its value doesn't make sense. The same goes for gl_FragColor, which is the pixel shader's counterpart (although a fragment can also be dismissed, so it's simply not drawn).

Furthermore, a couple of built-in uniform variables exist (remember: the global ones for communication between application and shader). They give access to the different matrices of OpenGL and are shown in Table 3.

Uniform name  Datatype  Description
 gl_ModelViewMatrix  mat4  Translation, rotation and scalling of a model
 gl_ProjectionMatrix  mat4  Transformation from world- to screen space
 gl_ModelViewProjectionMatrix  mat4  Result of the multiplication of these two matrices
 gl_NormalMatrix  mat3  Special transformation matrix for normal vectors 
Table #3: Builtin uniform variables in shaders


 Communication between the shaders

Communication between the shaders is done using the qualifier varying. Variables, which are qualified varying, have to be declared globally, in both the vertex and pixel shader. The values, which are assigned inside the vertex shader, are interpolated automatically between the vertices of the primitive the vertex belongs to. The interpolated values are then given to the pixel shader. That this interpolation is neccesary may become clear, if you think about a simple triangle. It consists of three vertices, but when it is drawn solid on the screen, it may need thousands of fragments. In this case, three calls of the vertex shader are followed by thousands of calls of the pixel shader.

Besides the user defined varying variables, there are also a couple of predefined GLSL varyings you can use to communicate between the shaders:

Varying name  Datatype  Description
Only inside the vertex shader (writing)
 gl_FrontColor  vec4  Front color of the vertex
 gl_BackColor  vec4  Back color of the vertex
 gl_FrontSecondaryColor  vec4  Second front color
 gl_BackSecondaryColor  vec4  Second back color
Only inside the pixel shader (reading)
 gl_Color  vec4  First draw color
 gl_SecondaryColor  vec4  Second draw color
Inside both shaders
 gl_TexCoord[]  vec4  Texturecoordinates of the respective texture unit 
Table #4: Predefined varying variables


Except for gl_TexCoord[] all of these variables can be used, without a prior declaration. gl_TexCoord need to be declared with the same number of elements in both shaders.

The varying variables gl_FrontColor, gl_FrontSecondaryColor, gl_BackColor, and gl_BackSecondaryColor can only be read inside the pixel shader using the aliases gl_Color and gl_SecondaryColor respectively. Which value from the vertex shader is used in the pixel shader depends on whether the fragment belongs to a front or back facing primitive.


 2. The first shader

After we have learned the basics of shaders and the shading language GLSL in general, it's time to do some practicing!


 Task

Your task is to write a minimal shader, which does nothing more than filling a model with a constant color. To the user, the model will look like a solid shape version of the 3D model. For this, we need both, a vertex and a pixel shader. The vertex shader should only transform the incoming vertex data, while the pixel shader colors the incoming fragments with a constant color.


Figure #1: Possible result of the minimal shader


 Tips