The Starting Point
A shader program, just like a "C" program, starts execution in main(). Every GLSL shader program begins life as:
The "//" construct is a comment and terminates at the end of the current line. "C"-type, multi-line comments—the /* and */ type—are also supported. However, unlike ANSI "C," main() does not return an integer value; it is declared void.
While this is a perfectly legal GLSL vertex- or fragment-shader program that compiles and even runs, its functionality leaves something to be desired. We will continue by describing variables and their operation.
Also, as with C and its derivative languages, statements are terminated with a semicolon.
Declaring Variables
GLSL is a strongly typed language; that is, every variable must be declared and have an associated type. Variable names conform to the same rules as those for C: you can use letters, numbers, and the underscore character (_) to compose variable names. A digit cannot be the first character in a variable name.
Table 15-1 shows the three basic types available in GLSL.
Type |
Description |
float |
IEEE-like floating-point value |
int |
signed 16-bit integer value |
bool |
boolean value |
Table 15-1 Basic Data Types in GLSL
An additional set of types is named samplers, which are used as opaque handles for accessing texture maps.
Note: An OpenGL implementation is not required to implement these types stringently, as one might like. As long as their operation is semantically and operationally correct, the underlying implementation may vary. For example, integers may be stored in floating-point registers. It is not a good idea to assume particular numeric outcomes, such as the maximum-sized integer value being 215, based upon these types.
Variable Scoping
While all variables must be declared, they may be declared any time before their use (unlike "C," where they must be the first statements in a block of code). The scoping rules of GLSL closely parallel those of C++:
• Variables declared outside of any function definition have global scope, and are visible to all functions within the shader program
• Variables declared within a set of curly braces (e.g., function definition, block following a loop or "if" statement, and so on) exist within the scope of those braces only.
• Loop iteration variables, such as i in the loop
are only scoped for the body of the loop.
Variable Initialization
Variables may also be initialized when declared. For example:
Integer constants may be expressed as octal, decimal, or hexadecimal values. An optional minus sign before a numeric value negates the constant.
Floating-point values must include a decimal point, unless described in scientific format (e.g., 3E-7).
Boolean values are either true or false, and can be initialized to either of those values or as the result of a operation that resolves to a boolean expression.
Constructors
As mentioned, GLSL is a strongly typed language, even more so than C++. There is no implicit conversion between values. For example,
will result in a compilation error due to assigning a constant integer to a floating-point value. Any conversion of values requires using a conversion function (a C++-like constructor). For example,
uses the float() function to do the conversion. Likewise, the other types also have conversion functions: int() and bool(). These functions also illustrate another feature of GLSL: operator overloading, whereby each function takes various input types, but all use the same base function name. We will discuss more on functions in a bit.
Aggregate Types
Three of GLSL’s primitive types can be combined to better match core OpenGL’s data values and to ease computational operations.
First, GLSL supports vectors of two, three, or four dimensions for each of the primitive types. Also, matrices of 2 χ 2, 3 χ 3, and 4×4 floats are available. Table 15-2 lists the valid vector and matrix types.
Base Type |
2-D vec |
3-D vec |
4-D vec |
Matrix Types |
float |
vec2 |
vec3 |
vec4 |
mat2, mat3, mat4 |
int |
ivec2 |
ivec3 |
ivec4 |
— |
bool |
bvec2 |
bvec3 |
bvec4 |
— |
Table 15-2 GLSL Vector and Matrix Types
Variables of these types can be initialized just as their scalar counterparts are:
and converting between types is equally accessible:
Vector constructors can also be used to truncate or lengthen a vector. If a longer vector is passed into the constructor of a smaller vector, the vector is truncated to the appropriate length.
Likewise, vectors are lengthened in somewhat the same manner. Scalar values can be promoted to vectors, as in
Matrices are constructed in the same manner and can be initialized to either a diagonal matrix or a fully populated matrix.
In the case of diagonal matrices, a single value is passed into the constructor, and the diagonal elements of the matrix are set to that value, with all others being set to zero, as in
Matrices can also be created by specifying the value of every element in the matrix in the constructor. This can be accomplished using 4, 9, or 16 elements, or using vectors of the appropriate size to initialize the columns of the matrix.
Accessing Elements in Vectors and Matrices
The individual elements of vectors and matrices can also be accessed and assigned to. Vectors support two types of element access: a named-component method and an array-like method. Matrices use a twodimensional array-like method.
Components of a vector can be accessed by name, as in
or using a zero-based index scheme. The following yield identical results to the above:
In fact, as shown in Table 15-3, there are three sets of component names available, all of which do the same thing. The multiple sets are useful for clarifying the operations that you’re doing.
Component Accessors |
Description |
(x, y, z, w) |
components associated with positions |
(r, g, b, a) |
components associated with colors |
(s, t, p, q) |
components associated with texture coordinates |
Table 15-3 Vector Component Accessors
A common use for component-wise access to vectors is for swizzling components, as you might do with colors, perhaps for color space conversion. For example, you could do the following to specify a luminance value based on the red component of an input color:
Likewise, if you needed to move components around in a vector, you might do:
The only restriction is that only one set of components can be used with a variable in one statement. That is, you can’t do:
Also, a compile-time error will be raised if you attempt to access an element that’s outside of what the type supports. For example,
Matrix elements can be accessed using the array notation. Either a single scalar value or an array of elements can be accessed from a matrix:
Structures
You can also logically group together collections of different types in a structure. Structures are convenient for passing groups of associated data into functions. When a structure is defined, it automatically creates a new type, and implicitly defines a constructor function that takes the types of the elements of the structure as parameters.
Likewise, to reference elements of a structure, use the familiar "dot.” Arrays
GLSL also supports one-dimensional array of any type, including structures. As with "C," the indexing uses brackets ([ ]). The range of elements in an array of size n is 0 … n-1. Unlike "C," however, negative array indices are not permitted.
Type Modifiers
Types can also have modifiers that affect their behavior. There are four modifiers defined in GLSL, as shown in Table 15-4.
Type Modifier |
Description |
const |
Labels a variable as a read-only, compile-time constant. |
attribute |
Specifies that the variable is associated with OpenGL vertex attributes set in the application |
uniform |
Specifies that the value is passed to the shader from the application and is constant across a given primitive |
varying |
Specifies that the variable is used both in a vertex and fragment shader program. Varying variables allows data to be passed from vertex programs to fragment programs. |
Table 15-4 GLSL Type Modifiers Const Type Modifier
Just as with "C," the const type modifier indicates that the variable is readonly. For example, the statement
sets the variable Pi to an approximation of π. With the addition of the const modifier, it becomes an error to write to a variable after its declaration, so they must be initialized when declared.
Attribute Type Modifier
The attribute modifier is used only with variables in vertex shaders. They are used for associating shader variables with per-vertex data passed in from the OpenGL application and must be declared global.