CS424 Notes, 23 January 2012
- About Coordinate Systems
- We start with some leftover material on coordinate systems from Friday's class.
- Minimal Vertex and Fragment Shaders
- Here are just about the simplest shaders you can use to get something on the screen.
When a primitive is drawn, any fragments in that primitive are colored yellow. To get
any primitives at all, we need an attribute to specify its vertices, and the vertex shader
has to assign a value to gl_POSITION representing the coordinates in the default
coordinate system.
Vertex shader source code: attribute vec2 vertexCoords; void main() { gl_Position = vec4(vertexCoords.x, vertexCoords.y, 0.0, 1.0); } Fragment shader source code: void main() { gl_FragColor = vec4(1.0,1.0,0.0,1.0); }
These shaders are used on the sample web page jan23-minimal-shaders.html. A copy will be handed out in class. - Suppose that we wanted to be able to draw different primitives in different colors.
What would we have to add to these shaders? Where? [Answer: A uniform variable in the
fragment shader.] Here is a fragment shader that does this.
precision mediump float; uniform vec4 color; void main() { gl_FragColor = color; }
(The "precision mediump float will require some explanation.) - Suppose that we wanted to be able to assign different colors to different vertices in the same primitive, with the colors "interpolated" to the interiors of the primitives? What would we have to add to the shader programs? [Answer: An attribute to vertex shader to represent the vertex colors, and a varying variable to both the vertex and fragment shaders.] You will do this on Thursday.
- Here are just about the simplest shaders you can use to get something on the screen.
When a primitive is drawn, any fragments in that primitive are colored yellow. To get
any primitives at all, we need an attribute to specify its vertices, and the vertex shader
has to assign a value to gl_POSITION representing the coordinates in the default
coordinate system.
- About the vertex shader language
- The language is GLSL ES 1.0. It is based on C.
- The only control structures that you can depend on are if and simple for loops, although while and do loops seem to be an (optional?) part of the specification. There is no switch.
- You can have functions, arrays, and structs. Non-constant array indices might not work in some cases, though they should always work with arrays that are uniform variables.
- Both the vertex shader and the fragment shader must have a function void main(). This is the function that is called for each vertex/fragment in the primitive.
- Predefined types include
float int bool (int/bool might actually be implemented as float) vec2 ivec2 bvec2 (vectors containing 2 float/int/bool values) vec3 ivec3 bvec3 (vectors containing 3 float/int/bool values) vec4 ivec4 bvec4 (vectors containing 4 float/int/bool values) mat2 (2-by-2 matrices of floats) mat3 (3-by-3 matrices of floats) mat4 (3-by-3 matrices of floats)
The vector and matrix types are useful in the kinds of math that are used in graphics. - Variables can be declared with the qualifiers attribute, uniform, varying. (attribute can only be used in vertex shaders.)
- Variables of type int and float can be declared as
highp, mediump, or lowp.
For example:
uniform lowp vec4 color;
These are precision qualifiers. They exist to allow the GPU to use lower precision computations when appropriate. Fragment shaders are not required to support highp! And in fragment shaders, there is no default precision, so you have to specify one for each integer or floating point variable! (See page 3 of the WebGL Quick Reference Card.) - The declaration "precision mediump float" says that all subsequent floats will be meduim precision.
- Constructors, indexing, and swizzlers for vecs
- If vctr is of type vec4, you can refer to the four elements of vctr as vctr[0], ..., vctr[3] or, more commonly, as vctr.x, vctr.y, vctr.z, vctr.w. You can also use vctr.r, vctr.g, vctr.b, vctr.a (since vec4 can be used to represent colors as well as coordinates). Similarly for vec2, ivec4, etc.
- Matrices can be indexed just like 2-dimensional arrays (m[2][0]), but the indexing uses column-major order. That is, the first index specifies the column and the second index specifies the row.
- If vctr is a vec4, then vctr.xy is the vec2 made up of the numbers vctr.x and vctr.y. You could do vctr.yx, which puts the y-value before the x-value. You could do vctr.xx, which uses two copies of the x-value. Similarly for vctr.xyz, vctr.zy, vctr.xxzz, etc. (This is known as swizzling.)
- Vector values are made with constructors such as vec3(1.0,2.3,3.1). You can use swizzlers in constructors to provide more than one number at a time, for example, vec3(0.0,vctr.xy) or vec4(vertexCoords.xy,0.0,1.0).
- There are also constructors for matrices.
- Operators and functions
- The shader language has most of C's operators (but not the bitwise logical operators like &, |, and <<).
- The arithmetic operators +, -, * and / are extended to work with vectors and matrices. In particular, + and - are used for vector addition and subtraction, and * is used to do matrix multiplication or to multiply a matrix by a vector. We will learn about this when we need it, mostly to do lighting computations.
- You can also operate on a vector and a float. For example, if vctr is a vector, vctr*2.0 will create the vector that is obtained by multiplying each component of vctr by 2.0
- There are many predefined functions, including a lot that are specifically useful in graphics. Take a look at page 4 of the WebGL Quick Reference Card.
- Examples
- gl_Position is a special, predefined variable of type vec4 in the vertex shader. The vertex shader should always assign a value to this variable. The w-coordinate of this vector should be 1.0 for now. It will be a while before we see why four coordinates are used for 3D graphics.
- gl_FragColor is a special, predefined variable of type vec4 in the fragment shader. The fragment shader should assign a value to this varaible, unless it wants to disgard the fragment entirely. (To discard the fragment, the fragment shader uses the build-in command discard; which will terminate the fragment shader and stop any further processing of that fragment.)
- Suppose that you have two colors represented by variables c1 and
c2 of type vec3, with RGB components in the range 0.0 to 1.0.
Suppose you want gl_FragColor to be a color that blends 30% of c1 with 70% of c2.
you could say:
gl_FragColor = c1 * 0.3 + c2 * 0.7;
As it turns out, this is such a common operation that there is a built-in function for doing it, so you could also say:gl_FragColor = mix(c1,c2,0.3);
The function mix(x,y,a) computes x*a+y*(1-a), and it works when x and y are float, vec2, vec3, or vec4.