GLSL Overview
Disclaimer
This page presents an initial guide to several common features and other aspects of the GLSL
language. It is meant to be a quick start guide, and is not even close to being "complete".
The reference texts shown on the Home page of this web site should be consulted for
additional details. Each program in the series of "Sample Program Sets" on this web site
include increasingly complex shader program examples for you to study.
You should also consult the official specifications document for the
version of GLSL you are targeting with your "#version" directive:
[
GLSL 4.50 ;
GLSL 4.40 ;
GLSL 4.30 ;
GLSL 4.20 ;
GLSL 4.10 ;
GLSL 4.00 ]
GLSL
GLSL is basically a subset of C/C++ with extensions. The next section enumerates the main parts of C/C++
that are not in GLSL. The remaining sections cover extensions and various other differences.
The Major Missing Pieces
- Recursion
- Dynamic memory allocation (While you cannot dynamically allocate
GPU memory in GLSL programs, we can – and, in fact, must – dynamically allocate and deallocate
it on the CPU side via an OpenGL API. We will generally use glBufferData for allocation and
glDeleteBuffers for returning dynamically allocated GPU memory to the free pool.)
- Pointers. (Hence the following type declaration
modifiers and pointer-related unary operators are not allowed in GLSL: * and
&.)
- I/O. (No reading from or writing to external files or
the console.)
- char or string data types
- Also note that GLSL is not object-oriented. You cannot declare
class types or variables, there are no methods, no inheritance, etc.
Extensions and Other Language Differences
- New storage classes
Variables declared at global scope may be prefixed with exactly one of the following:
- in:
signifies that the variable is a read-only input to the shader from the
CPU code (if this is a vertex shader) or from a previous shader stage for all other shader
program types.
- out:
signifies that this is a writable variable whose value at the end of the program
will be passed into a subsequent shader stage.
- uniform:
signifies that the variable is a read-only input to the shader
from the CPU code. (The difference between an "in" variable from the CPU and a
uniform variable is that the former is a per-vertex attribute accessible
only in a vertex shader while the
latter is a per-primitive attribute available in any shader program. See
this diagram.)
Any variable at global scope that does not have one of these modifiers
is interpreted exactly as if it were a C/C++ variable with the
same declaration.
- Formal parameter modifiers in function prototypes
Formal parameters may have exactly one of the following modifiers:
- in: This is classical pass-by-value. The value of
the actual parameter will be copied into the formal parameter when the
function is called, but the value will not be copied back out when
the function ends.
- out: When the function is called, no value will be
copied into the formal parameter, but when the function exits, its current value will
be copied back to the actual parameter in the calling environment. If no value was assigned
to such a parameter, its contents (and hence the contents of the actual parameter after the
function returns) are undefined!
- inout:
The value of the actual parameter is copied into the formal parameter when the
function is called, and its final value will be copied back out to the actual parameter when
the function exits. While this oftentimes behaves as C++ pass by reference, it is not
the same. Be sure you understand why.
Any formal parameter with no modifier is treated as if it had the "in" modifier.
See also the comments on passing arrays as formal parameters below in the "Arrays" section.
- Built-in types
- vecn, where n can be
2, 3, or 4: These
declare single precision floating point n-tuples that can be treated as having
named fields (x, y,
z, w; r, g, b, a; etc.) or as an
array. The arithmetic operators are overloaded to work componentwise. For example,
given two vec3 instances, "d" and "e":
This expression... |
...is equivalent to |
vec3 f = d - e; |
vec3 f = vec3(d.x-e.x, d.y-e.y, d.z-e.z); |
vec3 f = d * e; |
vec3 f = vec3(d.r*e.r, d.g*e.g, d.b*e.b); |
vec3 f = 2.7 * e; |
vec3 f = vec3(2.7*e[0], 2.7*e[1], 2.7*e[2]); |
Don't forget that wherever you see "e.x", you can equivalently write
"e.r", "e[0]", or "e.s". Analogous equivalent references
can be used for the other fields of vec2, vec3, and vec4
instances.
To perform vector dot and cross products, use the corresponding built-in functions
introduced below.
Constructor parameter lists can be assembled in various ways. For example, given two
vec2 instances called v2A and v2B, and given a vec3
instance called v3, the following are valid ways of creating a vec4:
- vec4 v4A = vec4(v2A, v2B);
- vec4 v4B = vec4(-3.1, v2A, 1.0);
- vec4 v4C = vec4(v3, 0.0);
- matn (dmatn),
where n can be 2, 3, or 4: These declare single (double)
precision floating point square matrices of size n.
NOTE: Matrix indices
must be referenced as: m[col][row].
As with vecn, the arithmetic
operators have been overloaded so that, for example, "*" applied to two
mat instances of compatible sizes will perform a standard matrix
multiplication. The "*" operator also works as expected when multiplying a
vecn instance by a matn instance of compatible size.
- Arrays
Arrays are first-class types. They are initialized with constructor syntax (not C-style array
initialization syntax):
float buf[] = float [] (1.3, 2.1, -9.3, 8.7, 6.6, 3.1);
They also know their length. For example:for (int i=0 ; i<buf.length() ; i++)
buf[i] = 2.0 * buf[i];
Arrays passed as parameters to GLSL functions must have an explicit size declared. For example:
void myArrayFunc(float buf[]) // ILLEGAL
whereas:
void myArrayFunc(float buf[10]) // OK
Note: Arrays will be copied to the formal parameter on entry to a function (if
declared as in or inout);
they will be copied back out to the actual parameter on return (if declared out
or inout). You may therefore want to avoid passing large arrays as parameters.
- Built-in identifiers for variables
Shader programs have sets of predefined input and/or output variables.
The set of these predefined variables is different for each shader program type,
but their names all begin with gl_.
It is a compilation error to declare any GLSL variable of your own whose name starts with gl_.
See the specifications for a complete list of the built-in variables for each shader type,
but the main one for our purposes as we begin our exploration of GLSL programming is:
- Vertex shader: vec4 gl_Position is defined as an
out variable whose value must be set by the vertex shader to the LDS (x,y,z,w) coordinates
of the current vertex. (For basic 2D applications, set z=0 and w=1.)
Don't forget we are also required to define an "out vec4" variable in the fragment shader
and set it to the desired RGBA color for the fragment.
- Built-in functions
- There are a whole host of built-in functions (sqrt,
trigonometric functions, etc.), nearly all of which
work on simple variables of type float as well as polymorphically for all other
built-in types, including vecn, matn, etc.
- Note especially
the functions dot and cross which can be used to perform the
corresponding operations on vectors.
- See chapter 8 of the
GLSL language specification (links at the top of this page) for a complete list.
- Other features
- "swizzle" operation: For example,
given a variable of type vec4 called blah, we
can write:
- vec2 blip = blah.zx;
The effect is blip[0] will have
blah.z, and blip[1] will have blah.x.
- vec3 justXYZPart = blah.xyz;
Execution Environment Differences
A "shader program" might actually be best imagined as a cooperating collection of programs with
a DAG-like organizational structure. The
output of some programs are gathered and used to generate the input to others. For example, a
"GLSL program" that executes when a single triangle is rendered via glDrawArrays will
consist of exactly three vertex shader executions and probably hundreds or even thousands (or
even more) fragment shader executions. All shader program executions of a given type conceptually
execute simultaneously. Depending on the GPU hardware, hundreds or even thousands will actually
execute simultaneously.
Recall that shader programs in general are required to set certain out variables (either
implicitly declared built-in variables, or explicitly declared variables) so that they can be
collected, interpolated (if necessary), and passed into other shader programs.
The main function of each shader program must have the prototype: void main( ).