Creating Shaders with GLSL (OpenGL Programming) Part 2

Uniform Type Modifier

The uniform modifier specifies that a variable’s value will be specified by the application before the shader’s execution and does not change across the primitive being processed. Uniform variables are shared between vertex and fragment shaders and must be declared as global variables. Any type of variable, including structures and arrays, can be specified as uniform.

Consider a shader that uses an additional color in shading a primitive. You might declare a uniform variable to pass that information into your shaders. In the shaders, you would make the declaration:

tmp73ec-109_thumb[2]

Within your shaders, you can reference BaseColor by name, but to set its value in your application, you need to do a little extra work. The GLSL compiler creates a table of all uniform variables when it links your shader program. To set BaseColof s value from your application, you need to obtain the index of BaseColor in the table, which is done using the glGetUniformLocation() routine.

GLint glGetUniformLocation(GLuint program, const char *name)

Returns the index of the uniform variable name associated with the shader program, name is a null-terminated character string with no spaces. A value of minus one (-1) is returned if name does not correspond to a uniform variable in the active shader program, or if a reserved shader variable name (those starting with gi_ prefix) is specified.

Name can be a single variable name, an element of an array (by including the appropriate index in brackets with the name),, or a field of a structure (by specifying name, then followed by the field name, as you would in the shader program). Additionally, the location of an array can be retrieved by either specifying "arrayName[0]", or simply "arrayName".

The returned value will not change unless the shader program is relinked (see glLinkProgramQ).

Once you have the associated index for the uniform variable, you can set the value of the uniform variable using the glUniform*() or glUniformMatrix*() routines.

void glUniform|1234}{if}(GLint location, TYPE value); void glUniform{1234)jif|v(GIint location, GLsizei count, TYPE values); void glUniformMatrix{234}fv(GLint location, GLsizei count, GLboolean transpose, const float* values);-___.

Sets the value for the uniform variable associated with the index location.

The vector form loads count sets of values (from one to four values, depending upon which glUniform*() call is used) into the uniform variables starting location. If location is the start of an array, count sequential elements of the array are loaded.

The floating-point forms can be used to load a single float, a floating-point vector, an array of floats, or an array of vectors of floats.

The integer forms can be used to update a single integer, an integer vector, an array of integers, or an array of integer vectors. Additionally, individual and arrays of texture samplers can also be loaded.

For glUnitormMatrix(), count sets of 2 χ 2, 3 *3, or 4 χ 4 matrices are loaded from values. If transpose is GL_TRUE, values are specified in row-major order (like arrays in "C”); or if GL_FALSE is specified, values are taken to be in column-major order (ordered in the same manner as glLoadMatrixQ).

Example 15-4 demonstrates obtaining a uniform variable’s index and assigning values.

Example 15-4 Obtaining a Uniform Variable’s Index and Assigning Values

Obtaining a Uniform Variable's Index and Assigning Values

Varying Type Modifier

Quite often vertex and fragment shaders operate cooperatively. To pass data computed into a vertex shader to its matching fragment shader, or even back to the fixed-function pipeline, we’ll use varying variables.

Varying variables are written in a vertex shader, and then OpenGL iterates those values across the primitive in a perspective-correct manner. Every time the fragment shader executes, the appropriate iterated values for that fragment are passed into the shader through the varying variables.

OpenGL defines a set of varying variables that are written in a vertex shader for passing data back to OpenGL, and another set of values that are passed into fragment shaders to be read and used in generating the final fragment color and depth values.

The types of varying quantities are limited to GLSL’s float, vector types (vec*), and matrix types (mat*), or arrays of those types. Integers, booleans, and structures cannot be declared varying.

Similar to uniform variables, varying values must be global variables, and their declaration must match between the vertex and fragment programs. For example,

tmp73ec-111_thumb[2]

would need to appear as a global in both the vertex and fragment shaders. Statements

The real work in a shader is done by computing values and making decisions. In the same manner as C++, GLSL has a rich set of operators for constructing arithmetic operations for computing values and a standard set of logical constructs for controlling shader execution.

Arithmetic Operations

No text describing a language is complete without the mandatory table of operator precedence (see Table 15-5). The operators are ordered in decreasing precedence. In general, the types being operated on must be the same, and for vector and matrices, the operands must be of the same dimension.

Operators

Accepted Types

Description

tmp73ec-112

Grouping of operations

tmp73ec-113

arrays

Array subscripting

tmp73ec-114

functions

Function calls

tmp73ec-115

structures

Structure field access

tmp73ec-116 tmp73ec-117

Post-increment and -decrement

tmp73ec-118 tmp73ec-119

Pre-increment and -decrement Unary operations: explicit positive or negative value, bit-wise inversion, negation

tmp73ec-120 tmp73ec-121

Multiplicative operations

tmp73ec-122 tmp73ec-123

Additive operations

Table 15-5 GLSL Operators and Their Precedence

Operators

Accepted Types

Description

tmp73ec-124 tmp73ec-125

Relational operations

tmp73ec-126 tmp73ec-127

Equality operations

tmp73ec-128 tmp73ec-129

Logical and operation

tmp73ec-130 tmp73ec-131

Logical exclusive-or operation

tmp73ec-132 tmp73ec-133

Logical or operation

tmp73ec-134 tmp73ec-135

Selection operation (inline "if" operation; if (a) then (b) else (c))

tmp73ec-136 tmp73ec-137

Assignment Arithmetic assignment

tmp73ec-138 tmp73ec-139

Sequence of operations

Table 15-5 GLSL Operators and Their Precedence

Note: This table lists all currently implemented operators of GLSL. Various operations that exist in C++ (%, the modulus operator, for example) are currently reserved but not implemented in GLSL.

Overload Operators

Most operators in GLSL are overloaded, meaning that they operate on a varied set of types. Specifically, arithmetic operations (including pre- and post-increment and -decrement) for vectors and matrices are well-defined in GLSL. For example, to multiply a vector and a matrix (recalling that the order of terms is important—matrix multiplication is non-commutative, for all you math-heads), use the following operation:

tmp73ec-140_thumb[2]

The normal restrictions apply, that the dimensionality of the matrix and the vector must match. Additionally, scalar multiplication with a vector and matrix will produce the expected result. One notable exception is that the multiplication of two vectors will result in component-wise multiplication of components; however, multiplying two matrices will result in normal matrix multiplication.

tmp73ec-141_thumb[2]

Additional common vector operations (e.g., dot and cross products) are supported by function calls, as well as various per-component operations on vectors and matrices.

Logical Operations

GLSL’s only logical control structure is the if-then-else statement. As with "C," the else clause is optional, and multiple statements require a block.

tmp73ec-142_thumb[2]

There is no switch statement as in "C."

Looping Constructs

GLSL supports the familiar "C" form of for, while, and do … while loops.

The for loop permits the declaration of the loop iteration variable in the initialization clause of the for loop. The scope of iteration variables declared in this manner is only for the lifetime of the loop.

tmp73ec-143_thumb[2]

Flow Control Statements

Additional control statements beyond conditionals and loops are available in GLSL. Table 15-6 describes available flow-control statements.

Statement

Description

break

Terminates execution of the block of a loop, and continues execution after the scope of that block.

continue

Terminates the current iteration of the enclosing block of a loop, resuming execution with the next iteration of the loop.

return [result]

Returns from the current subroutine, optionally providing a value to be returned from the function (assuming return value matches the return type of the enclosing function).

discard

Discards the current fragment. Discard statements are only valid in fragment shader programs.

Table 15-6 GLSL Flow-Control Statements

The discard statement is available only in fragment programs. The execution of the fragment shader may be terminated at the execution of the discard statement, but this is implementation dependent.

Functions

Functions permit you to replace occurrences of common code with a function call. This, of course, allows for smaller code, and less chances for errors. GLSL defines a number of built-in functions.User-defined functions can be defined in a single shader object, and reused in multiple shader programs.

Declarations

Function declaration syntax is very similar to "C," with the exception of the access modifiers on variables:

tmp73ec-144_thumb[2]

Function names can be any combination of letters, numbers, and the underscore character, with the exception that it can neither begin with a digit nor with gl_.

Return types can be any built-in GLSL type and user-defined structure; arrays are not available as return values. If a function doesn’t return a value, its return type is void.

Parameters to functions can be of any type, including arrays (which must specify their size).

Functions must be either declared, or prototyped, before their use. Just as in C++, the compiler must have seen the function’s definition before its use or an error will be raised. If a function is used in a shader object other than the one where it’s defined, a prototype must be declared. A prototype is merely the function’s signature without its accompanying body. Here’s a simple example:

tmp73ec-145_thumb[2]

Parameters Access Modifiers

While functions in GLSL are able to modify and return values after their execution, there’s no concept of a pointer or reference, as in "C" or C++. Rather, parameters of functions have associated access modifiers indicating if the value should be copied into, or out of, a function after execution. Table 15-7 describes the available parameter access modifiers in GLSL.

Access Modifier

Description

in

? ‘value copied into a function (default if not specified)

const in

read-only value copied into a function

out

value copied out of a function (uninitalized upon entrance into the function)

inout

value copied into and out of a function

Table 15-7 GLSL Function Parameter Access Modifiers

The in keyword is optional. If a variable does not include an access modifier, then an "in" modifier is implicitly added to the parameter’s declaration. However, if the variable’s value needs to be copied out of a function, it must either be tagged with an "out" (for write-only variables) or an "inout" (for read-write variables). Writing to an variable not tagged with one of these modifiers will generate a compile-time error.

Additionally, to verify at compile time that a function doesn’t modify an input-only variable, adding a "const in" modifier will cause the compiler to check that the variable is not written to in the function.

Using OpenGL State Values in GLSL Programs

Almost all values that you set in using the OpenGL API are accessible from within vertex and fragment shader programs.

Next post:

Previous post: