Creating Shaders with GLSL (OpenGL Programming) Part 1

The Starting Point

A shader program, just like a "C" program, starts execution in main(). Every GLSL shader program begins life as:

tmp73ec-89_thumb

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

tmp73ec-90_thumb

are only scoped for the body of the loop.

Variable Initialization

Variables may also be initialized when declared. For example:

tmp73ec-91_thumb

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,

tmp73ec-92_thumb

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,

tmp73ec-93_thumb

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:

tmp73ec-94_thumb

and converting between types is equally accessible:

tmp73ec-95_thumb

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.

tmp73ec-96_thumb

Likewise, vectors are lengthened in somewhat the same manner. Scalar values can be promoted to vectors, as in

tmp73ec-97_thumb

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

tmp73ec-98_thumb

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.

tmp73ec-99_thumb

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

tmp73ec-100_thumb

or using a zero-based index scheme. The following yield identical results to the above:

tmp73ec-101_thumb

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:

tmp73ec-102_thumb

Likewise, if you needed to move components around in a vector, you might do:

tmp73ec-103_thumb

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:

tmp73ec-104_thumb

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,

tmp73ec-105_thumb

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:

tmp73ec-106_thumb

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.

tmp73ec-107_thumb

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

tmp73ec-108_thumb

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.

Next post:

Previous post: