top of page

Qualifiers and Data Types

Data Types

In GLSL, we cannot edit the data of variables that are brought into the shader from outside souces (i.e. other shader stages or external scripts). In order to alter this data we will have to create our own variables to copy the data and modify it. For example; the gl_Vertex buit-in variable (that we used in the previous tutorial) cannot have a value assigned to it or have it's value modified.

To accomplish this, we will need to declare the type of data that the new variables will hold (i.e. int, float, vec2, etc.). However, first we need to understand the data types that are allowed. I will start with the scalars:

bool - boolean conditional, true/false

int - signed 32-bit integer

uint - unsigned 32-bit integer

float - single-precision floating point number

double - double-precision floating point number (only available in versions 4.0+)

examples:

bool MyBool = true;

int MyInt = 6;

uint MyUint = 2;

float MyFloat = 4.0;

double MyDouble = 8.0;

These types are useful by themselves, but are not the only types available. There are also vectors, matices and arrays. First up are vectors:

(to some people it might be easier to think of vectors as arrays of 2, 3, or 4 values)

(where # - 2, 3, or 4)

bvec# - a vector of bools

ivec# - a vector of ints

uvec# - a vector of uints

vec# = a vector of floats

dvec# - a vector of doubles

examples:

bvec2 MyBvec = bvec2(true, false);

ivec3 MyIvec = ivec3(1, 3, 5);

uvec2 MyUvec = uvec2(2, 4);

vec4 MyVec = vec4(1.0, 0.5, 0.0, 1.0);

dvec2 MyDvec = dvec2(0.5, 0.5);

Notice with these examples I have included the data type in the value assignment, this is required for c-style languages. So, in addition to declaring the variable data type, we also have to declare the data type of the value. If we are assigning the value as a variable that we have already declared, there is no reason to do this since we have already declared the data type when we declared that variable. Also we only have to declare the data type when we declare the variable, not every time we use the variable (that would throw an error). We can also use the value data type to convert a certain data type to a different data type.

example:

int MyInt = 0;

float MyZeros = float(Myint); //same as 0.0

vec3 MyVar1 = vec3(1.0, 0.5, MyZeros); //same as vec3(1.0, 0.5, 0.0)

vec3 MyVar2 = vec3(float(MyInt)); //same as vec3(0.0) or vec3(0.0, 0.0, 0.0)

MyVar2 = MyVar1; //same as vec3(1.0, 0.5, 0.0)

While we are on the subject or vectors, I want to introduce swizzling. We access the different components of a vector through swizzling. For example:

vec3 Pos1 = vec2(1.0, 0.5, 0.3);

vec3 Pos2 = vec2(0.0, 1.0, 0.2);

vec4 NewPos = vec4(Pos1.xy, Pos2.xy); //same as vec4(1.0, 0.5, 0.0, 1.0)

In the above example, the 'x' and 'y' access the first and second component of the vectors. We can use x, y, z, and w to access the first, second, third, and fourth component of the vector respectively. Those are not the only letters we can use to swizzle; there is also 'rgba' (typically used for colors) and 'stpq' (typically used for coordinates). You can use any of these that you want (i.e. you are not limited to using 'rgba' for colors), it is up to you to determine which of these you want to use. We can alse use these in any order (although their assignment will not change), and we can use a component more than once. However, we cannot access more components that we have available, that would generate an error.

Example:

vec2 MyVar1 = vec2(1.0, 0.5)

vec4 MyVar2 = MyVar1.xxyx //same as vec4(1.0, 1.0, 0.5, 1.0)

vec4 MyVar3 = MyVar2.wyyz //same as vec4(1.0, 1.0, 1.0, 0.5)

vec3 MyError1 = MyVar1.xzy //error - MyVar1 does not have 3 components

vec2 MyError2 = MyVar2.xyz //error - MyError2 does not have 3 components

We can also swizzle a variable when we are assigning a value to it:

float MyFloat1 = 1.0;

float MyFloat2 = 0.5;

vec3 MyVec3 = vec3(1.0);

vec4 MyVec4 = vec4(0.0);

MyVec4.x = MyFloat1;

MyVec4.yz = vec2(MyFloat1, 0.75);

MyVec4.zw = MyVec3.xy;

Arrays are a sequence of the above data types. They must be declared with an array size using brackets. We can then access/assign the different values by calling the specific order of the array using the same brackets (note - the order of the array starts at 0). For example:

float MyArrayOfFloats[8]; //an array of 8 floats

MyArrayOfFloats[2] = 1.0; //assigning the 3rd float

MyArrayOfFloats[0] = 0.5; //assigning the 1st float

MyArrayOfFloats[7] = 0.74; //assigning the 8th float

float MyNewFloat = MyArrayOfFloats[5]; //accessing the 6th float of the array

We can use arrays for any of the data types that we have talked about thus far.

vec2 MyArray1[3];

MyArray1[0] = vec2(1.0, 0.5);

ivec3 MyArray2[2];

MyArray2[1] = ivec3(1,0,3);

etc..

Matrices, (for simpicity) might be thought of as a table or chart. All matrtices are floating point (no integers or booleans), and are presented by rows and columns.

Example 4x4 matrix:

(1.0, 0.5, 0.0, 1.0)

(1.0, 0.0, 0.5, 1.0)

(1.0, 0.6, 0.4, 0.0)

(0.0, 0.3, 0.6, 0.9)

There are two different ways of declaring matrices:

(similar to vectors, # = 2, 3, or 4)

mat#x# - a matrix or # columns and # rows respectively

mat# - a matrix with # columns and rows (same amount)

For GLSL versions 4.0+, we can produce double-precision floating point matices by adding a 'd' in front. Example

dmat4 - double-precision floating point 4x4 matix

Swizzling is not allowed on matices, but we can access the parts of a matrix using array syntax.

Example:

mat4 MyMatrix;

MyMatrix[3] = vec4(1.0, 0.5, 0.0, 1.0); //changing the 3rd row

MyMatrix[2][2] = 0.75; //changing the value in the 2nd row, 2nd column

Finally we get to samplers, which are an opaque type (meaning external data). We will use samplers to read textures. For our purposes within Blender, know that sampler2d is texture data. When we bring in texture data it will be of type sampler2d.

For more information on samplers (and sampler types). You can check out this LINK; however, I would like to note that many of these are not useable within blender.

Qualifiers

For qualifiers, although there are a few different types, I am only going to touch on storage qualifiers. Other types of qualifers are situation specific, and are less-than general purpose. Qualifiers determine the behavior and storage of variables.

Uniform Qualifier

The uniform qualifier is a variable that is constant (unchanging) for all polygons being processed within the current frame (per-shader). For our purposes (Blender) they are most useful to bring in variables from outside the shader. for example; if we want to bring in a timer from python, we will need to declare a uniform float to store it. If we want to bring in a texture, we will need to declare a uniform sampler2d. (etc..)

uniform float MyTimer; //bring in a float

uniform vec2 ExtVec1, ExtVec2; //two external vec2's

uniform sampler2d MyTexture; //an external texture

Constant Qualifier

The constant qualifier is a variable that is a compile time constant. It will not change as long as the shader is active. This also means that when we declare the variable, we will also have to initialize it with a value. These are useful for creating a variable outside of the main function to be used accross multiple user-created functions (or for readability). This is especially true since, according to the GLSL specification, variables declared outside of the main fuction have to be of type uniform or constant (however, it is vendor dependant - NVidia allows it).

const float MyFloat = 1.0;

const vec3 MyVec = vec3(1.0, 0.5, 0.0);

Input/Output Qualifiers

Input and output qualifiers are variable that are either given a value in the previous stage or passed on to the next stage. Up to GLSL version 1.2, both input and output variables were given the 'varying' qualifier (vertex output, fragment input). Beyond version 1.2, input are denoted with the 'in' qualifier and output variables are denoted with the 'out' qualifier. In any version input variables are constant, but not compile-time constants (cannot be edited in the shader that recieves the variable). As with all qualifiers, we declare the data type with the qualifier so there is no need to declare the data type again in the shader.

(For version 1.2)

vertex shader

varying float MyFloat;

void main() {

MyFloat = 1.0;

etc..

fragment shader

varying float MyFloat;

void main() {

float NewFloat = MyFloat;

etc..

(For version 1.3+)

in float MyFloat;

out float NewFloat;

void main() {

NewFloat = MyFloat;

etc..

The name and data type of the variables must match between the shader stages for them to be connected (as demonstrated in the above 1.2 example). Also a shader may only recieve a variable from the previous stage. For example; if you have a vertex, geometry, and fragment shader - then the fragment shader may only recieve variables from the geometry shader.

In the next tutorial I will discuss operations and their order so that we can alter these new variables.


Featured Posts
Recent Posts
Search By Tags
No tags yet.
Follow Me
  • YouTube Clean
bottom of page