13.2 - GLSL Data Types & Variables¶
GLSL allows for three basic types of data:
bool
: Boolean values;true
orfalse
int
: integer values; whole numbers in a certain range, -n..nfloat
: floating point values; numbers with a fractional component
GLSL facilitates the manipulation of
vectors and matrices. A vector or matrix is always composed of values of the
same basic data type. If a data type starts with a b
it contains Boolean
values; if it starts with an i
it contains integer values; if it starts with
anything else it contains floating point values. The vector and matrix data
types are:
bvec2
,bvec3
,bvec4
: 2, 3, and 4-component Boolean vectorsivec2
,ivec3
,ivec4
: 2, 3, and 4-component integer vectorsvec2
,vec3
,vec4
: 2, 3, and 4-component floating point vectorsmat2
,mat3
,mat4
: 2x2, 3x3, and 4x4 floating point matrices
There are three other specialized data types:
sampler2D
: a reference to a TEXTURE_2D texture unit (which has an attached texture object)samplerCube
: a reference to a SAMPLER_CUBE texture unitvoid
: used to identify functions that do not return a value or parameter lists to a function that are empty.
Precision of int
and float
Data Types¶
Most programming languages define the range of values that can be stored in a specific data type. For example, an “int” in Java can store values in the range -2,147,483,648(-231) to 2,147,483,647 (231 -1). GLSL let’s you pick from three different precision levels for the basic data types.
The following precisions are minimum requirements. The GPU hardware can use more precision if it wants to, but never less. If a GPU can’t support a program’s requested precision, it will fail to compile. Don’t worry too much about the precision of data types, because this concept goes away in higher versions of GLSL.
Boolean values do not have a precision. A boolean value is either true
or false
.
Integers can have one of three possible precisions:
precision | Range of values | Specific Range |
---|---|---|
lowp | (-28,28) | -256 … +256 |
mediump | (-210,210) | -1024 … +1024 |
highp | (-216,216) | -65,536 … +65,536 |
Floats can have one of three possible precisions:
precision | Range of values | Fractional accuracy |
---|---|---|
lowp | (-2,2) | 2-8 = 0.00390625 |
mediump | (-214,214) | 2-10 = 0.0009765625 |
highp | (-262,262) | 2-16 = 0.0000152587 |
Individual variables can have different precisions, or all variables of a particular type can be the same precision using a “precision statement,” such as:
precision highp int;
precision mediump float;
Literals & Constant Values¶
Boolean constants are either true
or false
.
Integers can be specified in decimal, octal, or hexadecimal (base 10, 8, or 16), base on their leading character. For example:
int alpha = 176; // base 10 starts with a non-zero digit
int beta = 0176; // base 8 starts with 0
int gamma = 0x176; // base 16 starts with 0x
Floats are specified using a series of digits that include a decimal point, an exponent, or both. Floats are always in base 10. For example:
float delta = 1.;
float epsilon = 0.3421;
float phi = 2e4;
float theta = 2.45e-2
Your can create constants using the const
storage qualifier.
The compiler guarantees that the value will not be changed during shader
execution. For example:
const float pi = 3.141592653589793;
const int number_lights = 5;
Variables¶
Variable names must start with a letter, a-z,A-Z,
or the underscore character, _
.
A variable name can contain letters, a-z,A-Z
, digits, 0-9
, and the
underscore character, _
. User variable names are not allowed to start
with gl_
.
All variables must be declared before they can be used.
Variables can be declared with, or without, an initialization value.
float alpha;
float beta = 5.0;
Storage Qualifiers¶
Some variable are used to pass data between the web browser and a shader program, and between a shader program and object buffers. These special variables must be designated with a “storage qualifier”. (Variables that do not have a “storage qualifier” are used for storing constants and performing calculations.)
uniform
: The variable is assigned a value from the JavaScript code before agl.drawArrays()
call is made. The value is accessible in both the vertex and fragment shader.attribute
: The variable is assigned a value from a object buffer as a series of graphics primitives are rendered. The value is only accessible in the vertex shader.varying
: The variable is assigned a value by a vertex shader and automatically interpolated across the surface of a graphics primitive before a fragment shader receives it. The value can be used in a fragment shader, but not changed.
User Defined Aggregate Data Types¶
You can create new data types that contain a combination of values. A struct
data type can contain values of different data types. The array
data type
requires that all values in the array be of the same data type.
A struct
is a good way to organize values that logically go together. The
identifier after the keyword struct
is the structure name.
struct my_light {
float intensity;
vec3 position;
vec4 color;
};
An array
is a good way to organize values that logically go together
if they all have the same data type. The size of the array must be a constant.
Array indexes are zero-subscripted. Individual elements of an array must be
assigned individually.
float frequencies[3];
const int numLights = 2;
my_light lights[numLights];
frequencies[0] = 0.23;
frequencies[1] = 0.67;
frequencies[2] = 0.82;
Vector Components¶
The individual element of a vector can be accessed using array notation,
[2]
, or “dotted notation”, .x
. The names of the vector components
are x,y,z,w
, or r,g,b,a
, or s,t,p,q
. You can use
any of these names on a vector, regardless of the actual data in the vector,
but the intent is to use x,y,z,w
when you are accessing geometric data,
r,g,b,a
when you are accessing color data, and s,t,p,q
when
you are accessing texture data. The array notation always
accesses a single component. The “dotted notation” returns either a single
component or a new vector depending on the number of field names that is
used. This is best explained by studying the following examples:
vec3 alpha = vec3(1.0, 2.0, 3.0);
vec4 a;
vec3 b;
vec2 c;
float d;
b = alpha.xyz; // b is now (1.0, 2.0, 3.0)
d = alpha[2]; // d is now 3.0
a = alpha.xxxx; // a is now (1.0, 1.0, 1.0, 1.0)
c = alpha.zx; // c is now (3.0, 1.0)
b = alpha.rgb; // b is now (1.0, 2.0, 3.0)
b = alpha.stp; // b is now (1.0, 2.0, 3.0)
a = alpha.yy; // compiler error; the right hand side is a 2-component vector,
// while "a" is a 4-component vector.
Using multiple property names to create a new vector is called swizzle notation. Swizzle notation can also be used on the left-hand side of an assignment statement, with the exception that each field name can only be used once. This is best explained by studying the following examples:
alpha.xx = vec2(10.0, 20.0) // compiler error; can't use x twice
alpha.zxy = vec3(3.0, 4.0, 5.0); // alpha is now (4.0, 5.0, 3.0)
alpha.zx = vec2(10.0, 20.0) // alpha is now (20.0, 5.0, 10.0)
alpha.xyz = vec2(10.0, 20.0) // compiler error; not enough values
Constructors and Data Type Conversions¶
You can convert one data type to another data type using a “cast”, which is a “call” to a conversion function that has the same name as the data type. Casting is important because GLSL does not support equations with mixed data types.
int a = 37;
float b = float(a) * 2.3;
Constructors also have the same name as their associated data types. A call to a constructor creates a value of the indicated data type and must be sent the correct number of initial values, but those values can be any combination of variables that contain the appropriate number of initialization values. Please study these examples:
vec3 alpha = vec3(1.0, 2.0, 3.0);
vec4 beta = vec4(4.0, 5.0, 6.0, 7.0);
vec3 delta = vec3(alpha.xy, beta.w); // delta is now (1.0, 2.0, 7.0)
vec4 gamma = vec4(alpha[2], beta.rrr); // gamma is now (3.0, 4.0, 4.0, 4.0)
Glossary¶
- data type
- The description of a memory location that specifies three things: 1) the number of bits used to represent a value, 2) the meaning of the bits, and 3) the valid operations that can be performed on the bits.
- integer
- A whole number, e.g., -5, 37, 0
- float
- A number that can represent fractions, e.g., 2.1, -6.74
- precision
- The number of bits used to represent a value. The number of bits determines the possible range of values.
- constant
- A value that never changes after its initial assignment.
- variable
- A memory location whose value can change as a program executes.
- storage qualifier
- Designates certain variables for special uses. These variables are used to exchange data between CPU and GPU components.
- swizzle notation
- The use of a vector’s property names to access and create new vectors.
- cast
- An operation that changes the data type of a value.
- constructor
- An operation that creates and initializes a variable of a particular data type.