3.2. Data TypesWe saw vectors of floating-point numbers in the example in the previous section. Many other built-in data types are available to ease the expression of graphical operations. Booleans, integers, matrices, vectors of other types, structures, and arrays are all included. Each is discussed in the following sections. Notably missing are string and character types, since there is little use for them in processing vertex and fragment data. 3.2.1. ScalarsThe scalar types available are
These declare variables, as is familiar from C/C++. float f; float g, h = 2.4; int NumTextures = 4; bool skipProcessing; Unlike the original C, the OpenGL Shading Language requires you to provide the type name because there are no default types. As in C++, declarations may appear when needed, not just after an open curly brace ({). Literal floating-point numbers are also specified as in C, except there are no suffixes to specify precision since there is only one floating-point type. 3.14159 3. 0.2 .609 1.5e10 0.4E-4 etc. In general, floating-point values and operations act as they do in C. Integers are not the same as in C. There is no requirement that they appear to be backed in hardware by a fixed-width integer register. Consequently, wrapping behavior, when arithmetic would overflow or underflow a fixed-width integer register, is undefined. Bit-wise operations like left-shift (<<) and bit-wise and (&) are also not supported. What can be said about integers? They are guaranteed to have at least 16 bits of precision; they can be positive, negative, or zero; and integer arithmetic that stays within this range gives the expected results. Note that the precision truly is 16 bits plus the sign of the valuethat is, a full range of [-65535,65535] or greater. Literal integers can be given as decimal values, octal values, or hexadecimal values, as in C. 42 // a literal decimal integer 052 // a literal octal integer 0x2A // a literal hexadecimal integer Again, there are no suffixes to specify precision since there is only one integer type. Integers are useful as sizes of structures or arrays and as loop counters. Graphical types, such as color or position, are best expressed in floating-point variables within a shader. Boolean variables are as bool in C++. They can have only one of two values: true or false. Literal Boolean constants true and false are provided. Relational operators like less-than (<) and logical operators like logical and (&&) always result in Boolean values. Flow-control constructs like if-else accept only Boolean-typed expressions. In these regards, the OpenGL Shading Language is more restrictive than C++. 3.2.2. VectorsVectors of float, int, or bool are built-in basic types. They can have two, three, or four components and are named as follows:
Vectors are quite useful. They conveniently store and manipulate colors, positions, texture coordinates, and so on. Built-in variables and built-in functions make heavy use of these types. Also, special operations are supported. Finally, hardware is likely to have vector-processing capabilities that mirror vector expressions in shaders. Note that the language does not distinguish between a color vector and a position vector or other uses of a floating-point vector. These are all just floating-point vectors from the language's perspective. Special features of vectors include component access that can be done either through field selection (as with structures) or as array accesses. For example, if position is a vec3, it can be considered as the vector (x, y, z), and position.x will select the first component of the vector. In all, the following names are available for selecting components of vectors:
There is no explicit way of stating that a vector is a color, a position, a coordinate, and so on. Rather, these component selection names are provided simply for readability in a shader. The only compile-time checking done is that the vector is large enough to provide a specified component. Also, if multiple components are selected (swizzling, discussed in Section 3.7.2), all the components are from the same family. Vectors can also be indexed as a zero-based array to obtain components. For instance, position[2] returns the third component of position. Variable indices are allowed, making it possible to loop over the components of a vector. Multiplication takes on special meaning when operating on a vector since linear algebraic multiplies with matrices are understood. Swizzling, indexing, and other operations are discussed in detail in Section 3.7. 3.2.3. MatricesBuilt-in types are available for matrices of floating-point numbers. There are 2 x 2, 3 x 3, and 4 x 4 sizes.
These are useful for storing linear transforms or other data. They are treated semantically as matrices, particularly when a vector and a matrix are multiplied together, in which case the proper linear-algebraic computation is performed. When relevant, matrices are organized in column major order, as is the tradition in OpenGL. You may access a matrix as an array of column vectorsthat is, if transform is a mat4, transform[2] is the third column of transform. The resulting type of transform[2] is vec4. Column 0 is the first column. Because transform[2] is a vector and you can also treat vectors as arrays, transform[3][1] is the second component of the vector forming the fourth column of transform. Hence, it ends up looking as if transform is a two-dimensional array. Just remember that the first index selects the column, not the row, and the second index selects the row. 3.2.4. SamplersTexture lookups require some indication as to which texture or texture unit will do the lookup. The OpenGL Shading Language doesn't really care about the underlying implementation of texture units or other forms of organizing texture lookup hardware. Hence, it provides a simple opaque handle to encapsulate what to look up. These handles are called SAMPLERS. The sampler types available are
When the application initializes a sampler, the OpenGL implementation stores into it whatever information is needed to communicate what texture to access. Shaders cannot themselves initialize samplers. They can only receive them from the application, through a uniform qualified sampler, or pass them on to user or built-in functions. As a function parameter, a sampler cannot be modified, so there is no way for a shader to change a sampler's value. For example, a sampler could be declared as uniform sampler2D Grass; (Uniform qualifiers are discussed in more detail in Section 3.5.2.) This variable can then be passed into a corresponding texture lookup function to access a texture: vec4 color = texture2D(Grass, coord); where coord is a vec2 holding the two-dimensional position used to index the grass texture, and color is the result of doing the texture lookup. Together, the compiler and the OpenGL driver validate that Grass really references a two-dimensional texture and that Grass is passed only into two-dimensional texture lookups. Shaders may not manipulate sampler values. For example, the expression Grass + 1 is not allowed. If a shader wants to combine multiple textures procedurally, an array of samplers can be used as shown here: const int NumTextures = 4; uniform sampler2D textures[NumTextures]; These can be processed in a loop: for (int i = 0; i < NumTextures; ++i) . . . = texture2D(textures[i], . . .); The idiom Grass+1 could then become something like textures[GrassIndex+1] which is a valid way of manipulating the sampler. 3.2.5. StructuresThe OpenGL Shading Language provides user-defined structures similar to C. For example, struct light { vec3 position; vec3 color; }; As in C++, the name of the structure is the name of this new user-defined type. No typedef is needed. In fact, the typedef keyword is still reserved because there is not yet a need for it. A variable of type light from the preceding example is simply declared as light ceilingLight; Most other aspects of structures mirror C. They can be embedded and nested. Embedded structure type names have the same scope as the structure in which they are declared. However, embedded structures must be named. Structure members can also be arrays. Finally, each level of structure has its own name space for its members' names, as is familiar. Bit-fields (the capability to declare an integer with a specified number of bits) are not supported. Currently, structures are the only user-definable type. The keywords union, enum, and class are reserved for possible future use. 3.2.6. ArraysArrays of any type can be created. The declaration vec4 points[10]; creates an array of ten vec4 variables, indexed starting with zero. There are no pointers; the only way to declare an array is with square brackets. Declaring an array as a function parameter also requires square brackets and a size because, currently, array arguments are passed as if the whole array is a single object, not as if the argument is a pointer. Arrays, unless they are function parameters, do not have to be declared with a size. A declaration like vec4 points[]; is allowed, as long as either of the following two cases is true:
In this case, at runtime the array has only one size, determined by the largest index the compiler sees. Such automatically sized arrays cannot be passed as function arguments. This feature is quite useful for handling the built-in array of texture coordinates. Internally, this array is declared as varying vec4 gl_TexCoord[]; If a program uses only compile-time constant indices of 0 and 1, the array is implicitly sized as gl_TexCoord[2]. If a shader uses a nonconstant variable to index the array, that shader must explicitly declare the array with the desired size. Of course, keeping the size to a minimum is important, especially for varying variables, which are likely a limited hardware resource. Multiple shaders sharing the same array must declare it with the same size. The linker verifies this. 3.2.7. VoidThe type void declares a function that returns no value. For example, the function main returns no value and must be declared as type void. void main() { . . . } Other than for functions that return nothing, the void type is not useful. 3.2.8. Declarations and ScopeVariables are declared quite similarly to the way they are declared in C++. They can be declared where needed and have scope as in C++. For example, float f; f = 3.0; vec4 u, v; for (int i = 0; i < 10; ++i) v = f * u + v; The scope of a variable declared in a for statement ends at the end of the loop's substatement. However, variables may not be declared in an if statement. This simplifies implementation of scoping across the else substatement, with little practical cost. As in C, variable names are case sensitive, must start with a letter or underscore (_), and contain only letters, numbers, and underscores (_). Userdefined variables cannot start with the string "gl_", because those names are reserved for future use by OpenGL. Names containing consecutive underscores (__) are also reserved. 3.2.9. Type Matching and PromotionThe OpenGL Shading Language is strict with type matching. In general, types being assigned must match, argument types passed into functions must match formal parameter declarations, and types being operated on must match the requirements of the operator. There are no automatic promotions from one type to another. This may occasionally make a shader have an extra explicit conversion. However, it also simplifies the language, preventing some forms of obfuscated code and some classes of defects. For example, there are no ambiguities in which overloaded function should be chosen for a given function call. |