sampler1D -
abstraction for looking up values in 1D textures sampler2D -
ditto for 2D textures sampler3D -
ditto for 3D textures and a few more
arrays - similar
to C
structures -
similar to C
no pointers
no casting, but you have int() and float() functions
Variables:
can do
initialization when declaring a variable as long as the type is correct: int a = 2; float b = 2.0; bool c = true; vec4 d = vec4(1.0, 2.0, 3.0, 4.0);
can access
vector components more easily: xyzw and rgba, and stpq float xPos = d.x vec3 e = d.rgb
vec2 p = d.xy
d[1] == d.y == d.g == d.t
const
compile
time constant
attribute
global
variable (at the top level of the shader code)
can change per vertex
(within a glBegin/glEnd)
passed
in from OpenGL program
only
used in vertex shaders
read-only
within
the
shader
uniform
global
variable
(at the top level of the shader code)
can change per primitive
(not within a glBegin/glEnd)
passed
in from OpenGL program
used
in vertex, geometry, and fragment shaders
read-only
within
the
shader
varying
used to
pass data from a vertex shader to a fragment shader (or vertex to
geometry, geometry to fragment)
describe
attributes that vary across a primitive
read-only
in
fragment
shader
declarations
must
match
in
the
two
connected shaders (vert and geom, vert and frag, geom
and frag)
gets
per-vertex values in the vertex shader which are then automatically
interpolated into a value for the current fragment in the fragment
shader
For a single
primitive
vertex
shader runs once per vertex
fragment
shader runs once per fragment
geometry
shader runs once per primitive
multiple
instances of the same shader may run in parallel - order is unknown
There is no
communication from vertex to vertex or fragment to fragment or geometry
to geometry
Statements and Functions
if (boolean expression) {} else {}
and possibly:
for (init; boolean expression;
loop expression) {}
while (boolean expression) {} do
do {} while (boolean expression)
The vertex shader, geometry shader, and the fragment shader must have a
single main
function - typically void main()
you can also write your own functions with return values to help main
do its job
Communication
between OpenGL and GLSL:
How do you communicate with a shader:
Shader
has
access
to
part
of
the OpenGL state
Read
only
user
defined
variables
Uniform
Variables
Attribute
Varriables
Textures
OpenGL
state
available
to
vertex
shaders,
geometry shaders, and fragment shaders
through
uniform variables
Built-in
Uniform Variables Can be accessed within vertex shaders, geometry shaders, or
fragment shaders
(there is a full list on pages 105-107 of the Orange Book V2) and more
info on this in Chapter 4 in general
Uniform Variables
- value can be changed, but not within a glBegin glEnd pair
- can be read but not written within vertex, geometry, and fragment
shaders
- program must be linked before trying to get at the memory location
- within the application you need to get the memory location of the
variable GLint
glGetUniformLocation(GLuint program, const char *name);
e.g.
in
the
shader:
uniform vec3 LightPos;
in
the
application:
GLint lightLoc;
lightLoc =
glGetUniformLocation(p, "LightPos"); // assuming program p
- then you can assign values to it:
GLint
glUniform{1|2|3|4}{if}(GLint location, TYPE value);
GLint
glUniform{1|2|3|4}{if}v(GLint location, GLsizei
count, const TYPE *values);
e.g. glUniform3f(lightLoc, 1.0, 1.0, 4.0);
for
booleans
you
can
use
either int or float version
(0=false, anything else=true)
Vertex
Shader:
(so
you know where normal and lightDir are coming from)
varying vec3 normal, lightDir;
void main()
{
vec4 p;
// the light position is already stored in eye space
coordinates
lightDir =
normalize(vec3(gl_LightSource[0].position));
// convert normal to eye space coordinates using
gl_NormalMatrix //
gl_NormalMatrix is built in uniform matrix provided by GLSL
// representing the inverse transpose model-view
matrix
normal = normalize(gl_NormalMatrix * gl_Normal);
n = normalize(normal);
intensity = max(dot(lightDir, n),0.0);
// intensity tells me how perpendicular the light is
at this point
if (intensity > specIntensity)
color = specColor;
else if (intensity > t[0])
color =
colors[0];
else if (intensity > t[1])
color = colors[1];
else
color =
colors[2];
gl_FragColor = color;
}
OpenGL application
(in void setShaders() in the typical examples after reading, compiling,
attaching, linking, and using the shaders ... just before the glut main
loop gets called):
- value
can be changed within a glBegin glEnd pair
- can be read but not written in a vertex shader
- not useful in a fragment shader
- within
the application you need to get the memory location of the
variable GLint
glGetAttribLocation(GLuint
program, const char *name);
- then
you can assign values to it: GLint
glVertexAttrib{1|2|3|4}{s|f|d}ARB(GLint index, TYPE v); GLint
glVertexAttrib{1|2|3|4}{s|f|d}vARB(GLint
index,
const
TYPE
*v);
The geometry
shader had access to the built in atributes for each vertex that passes
through it.
example:
GLint
loc;
(in
void setShaders() in the typical examples after reading, compiling,
attaching, linking, and using the shaders ... just before the glut main
loop gets called):
// gl_Vertex is an attribute variable provided by
GLSL
p.xz = gl_Vertex.xy;
p.y = height;
p.w = 1.0;
// gl_ModelViewProjectionMatrix is built in uniform
matrix provided by GLSL
// we're not just passing through the vertex
information untouched this time
gl_Position = gl_ModelViewProjectionMatrix * p;
}
Fragment
Shader:
void main()
{
// make everything greenish
gl_FragColor = vec4(0.4,0.8,0.4,1.0);
}
Without the vertex shader we would have a square (-1 to 1 in two
dimensions) spinning around the vertical axis through its center as
shown in the movie below on the left. The
shader takes the 2D vertex given and converts it to a 3D vertex
(x->x, y->z, height becomes y) which
gives us a green rectangle rotating horizontally as shown in the movie
below on the right.
smoothstep (edge0, edge1, x) we will look at this next time
Geometric
Functions:
length
distance
dot
cross
normalize
ftransform
faceforward
reflect
refract
Matrix Functions:
matrixcompmult
Vector
Relational Functions:
lessThan
lessThanEqual
greaterThan
greaterThanEqual
equal
notEqual
any
all
not
Texture Access
Functions:
Fragment Processing Functions:
Noise Functions:
One important
thing to note is that it can be tricky to debug shaders. You can't just
add a printf() and see what's happening. What you can do is check the
info log using functions like this from www.lighthouse3d.com:
if
(infoLogLength > 0) {
infoLog
=
(char
*)
malloc(infoLogLength); glGetProgramInfoLog(obj,
infoLogLength,
&charsWritten,
infoLog);
printf("%s\n",infoLog);
free(infoLog); } }
and then you can call these functions like this:
glCompileShader(v);
glCompileShader(f);
printShaderLog(v);
printShaderLog(f);
p =
glCreateProgram();
glAttachShader(p,v);
glAttachShader(p,f);
glLinkProgram(p);
printProgramLog(p);
which will give errors like this if you mistype something within the
shader code:
ERROR: 0:25: '/' : syntax error syntax error
ERROR: Parser found no code to compile in source strings.
ERROR: One or more attached shaders not successfully compiled
Thursday:
Go through the
brick shader tutorial that we briefly showed in Lecture 1, which
is Chapter 6 of the Orange Book
One particular thing
to think about in this shader is how the bricks are generated - rather
than using loops and conditionals to assign a colour to each fragment
in order, each fragment needs to independently figure out what its
colour is. This will be a common theme in re-thinking how you approach
problems in parallel computation.