Vectors and Matrices

CS 381 Lecture, Dr. Lawlor

First, a pet peeve.  The plural of "Matrix" is "Matrices".  The plural of "Vertex" is "Vertices".  There is no such word as "Matrice"; the singular is just "Matrix". There is no such word as "Vertice"; just say "Vertex".  English's foreign words make no sense.  For some reason I can deal with "Matrixes", although it's hard to say.  "Vertice"

Ok, so you've got a point in 3D space.  You're representing that point with 3 floating-point numbers, call them (x,y,z).  We usually refer to the whole bundle of 3 coordinates as a "vector", and think of it as a single thing.  Virtually everything you'll do in computer graphics requires mathematically operating on vectors.

Vector Operations

The graphics hardware prefers dealing with 4-component vectors, called "vec4"s in GLSL.  So I can set up a vec4 to represent a point (x,y,z) like this:

    vec4 v = vec4(x, y, z, 1.0); 

Warning: the usual C++ style initialization "vec4 v(x, y, z, 1.0);" will give a rather unhelpful "syntax error parse error".  Just keep reminding yourself that GLSL isn't C++...

The 4'th component of your vectors, "w", is usually 1.0. 

You can pull out the components of a vector using a period like "v.x", which is just the vector v's x coordinate.

Dot Product

The coolest vector operation is "dot product".  The dot product just multiplies corresponding coordinates of the vector, and then adds the multiplied coordinates together.  You could write a version of dot product in GLSL (or C++) like this:

float my_dot(vec4 a,vec4 b) {
    return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}

But there's a faster (single clockcycle!) builtin routine called "dot(a,b)" that does the same thing.  So for example, in the previous lecture when we did isometric projections and wrote:

gl_Position = vec4(
    /* output X_w */  +0.7*v.x +0.0*v.y -0.6*v.z -0.3*v.w,
    ...);

we could have equivalently written this faster code:

const vec4 make_x = vec4(+0.7,+0.0,-0.6,-0.3);  
gl_Position = vec4(
    /* output X_w */ dot(make_x,v),
    ...);

The "const" puts the variable into read-only memory on the graphics card, which speeds up the program a bit.

Dot product has all sorts of nice properties:

Matrix Operations

Sadly there are two different, but entirely equivalent ways to think about matrix arithmetic for graphics.

Vectors as Columns (Good)

With column vectors, you think of transforming a 2D homogenous vector by a matrix by multiplying the matrix by the vector, like this:
[ s ]   [ a  b  c ][ x ]
[ t ] = [ d e f ][ y ]
[ 1 ] [ 0 0 1 ][ 1 ]
We do the matrix-vector multiply by taking dot products of matrix rows with our input vector.  Here's a silly animation of matrix-vector product using column vectors.  Note the bottom row of the transform matrix is 0 0 1 (to make sure the output W coordinate is still 1). 

Most mathematics (including the Math 314 textbook used at UAF) use the good column-vector format.  The OpenGL documentation uses the good column-vector format.  Graphics textbooks have mostly come around to the good format.  The Angel book Appendix C describes both formats.  In this class, I will always use good column vectors, and never evil row vectors.

Vectors as Rows (Evil)

With evil row vectors, you think of transforming a 2D homogenous vector by a matrix by multiplying the vector by the matrix, like this:
                      [ a d 0 ]
[ s t 1 ] = [ x y 1 ] [ b e 0 ]
[ c f 1 ]
Note now the rightmost column of the transform matrix is 0 0 1.  We do the vector-matrix multiply by taking dot products of the matrix columns with our input vector.

Most older graphics textbooks and papers use evil row vectors.  DirectX uses evil row vectors (the few times it comes up).  OpenGL's "glLoadMatrixf" routine, and GLSL's "mat4" matrix element order looks suspiciously like the evil row vector version, even though the documentation describes it as good column vectors.

Other descriptions of this controversy: on usenet, on  hplus.

When This Doesn't Really Matter

Note in both cases, s=a*x + b*y +c  and  t=d*x+e*y+f.  So the bottom-line floating-point operations are exactly the same in both cases.  The incoming X axis projects to (a,d); the Y axis to (b,e) ; and the origin to (c,f) in both cases.  The hardware doesn't deal with matrices in any case, but instead uses individual vectors.

When This Does Matter

GLSL's matrix load is carefully calculated to make nobody happy.
When you're building a matrix in GLSL, it looks a lot like the evil row-vector version, and the documentation doesn't actually say where the elements go.  But once you've loaded the matrix, you can use it for good or evil. In reality, you're just specifying the values in the columns of the matrix, which only looks funny when you write them out in rows:

 /* Compact but confusing way to write GLSL matrix initialization */
 mat4 m = mat4(
        a,   d, 0.0, 0.0,  /* <- column 0 of matrix */
        b,   e, 0.0, 0.0,    /* <- column 1 of matrix */
        0.0,0.0,0.0, 0.0,  /* <- column 2 of matrix */
        c,   f, ,0.0,1.0,    /* <- column 3 of matrix */
    );

 /* Long but clearer way to write the same GLSL matrix initialization */
mat4 m = mat4(
  a,   /* <- top of column 0 of matrix */
  d,
  0.0,
  0.0, 
     b,   /* <- top of column 1 of matrix */
     e,
     0.0,
     0.0,   
         0.0, /* <- top of column 2 of matrix (all zeros) */
         0.0,
         0.0,
         0.0,
            c,  /* <- top of column 3 of matrix */
            f,
            0.0,
            1.0  
    );

Often, you won't hardcode matrices into GLSL, but upload them from the C++ program using glUniformMatrix4fv, which luckily takes a "transpose" parameter you probably want to set to GL_TRUE--this lets you upload the matrix in the usual left-to-right, row-by-row order.

To use any matrix in GLSL with good column vectors, you just multiply the matrix by the vector, like "m*v".  To use the same matrix with evil row vectors, multiply the vector by the matrix, like "v*m".

In GLSL "m[i]" returns the i'th column of the matrix, as a column vector.  As a bonus, this syntax appears to be less likely to randomly crap out for big matrices (due to driver bugs) than the above 16-floats syntax.