Table of Contents

1 Vectors

A mathematical vector (or more precisely, a vector over the field of the real numbers) is a list of numbers with a set of operations. We use mathematical vectors in computer graphics to represent five things:

  • Points in space
  • Vectors: that is, direction and magnitude
  • Directions, more commonly called unit vectors (or normals if the direction is defined by a surface)
  • Homogeneous vectors: these have their own writeup
  • Mathematical vectors alone, with no additional structure

When dealing with a vector, it is common to call numbers scalars.

Let ss be a scalar, x=(x1,x2,,xn)\vec x = (x_1, x_2, \dots, x_n) and y=(y1,y2,,yn)\vec y = (y_1, y_2, \dots, y_n) be two vectors, and θ\theta be the angle between x\vec x and y\vec y.

Operation Result Notes
x+y\vec x + \vec y (x1+y1,x2+y2,,xn+yn)(x_1+y_1, x_2+y_2, \dots, x_n+y_n) Vector addition, element-wise addition
xy\vec x - \vec y (x1y1,x2y2,,xnyn)(x_1-y_1, x_2-y_2, \dots, x_n-y_n) Vector subtraction or difference, element-wise subtraction
sxs \vec x (sx1,sx2,,sxn)(s x_1, s x_2, \dots, s x_n) Scaling, multiplication by a scalar
xs\dfrac{\vec x}{s} (x1/s,x2/s,,xn/s)(x_1/s, x_2/s, \dots, x_n/s) Scaling, division by a scalar
xy\vec x\cdot\vec y x1y1+x2y2++xnynx_1y_1 + x_2y_2 + \cdots + x_ny_n Dot product, inner product; also written xTx\vec x^{T} \vec x; is a scalar equal to xycos(θ)\|\vec x\| \|\vec y\| \cos(\theta)
x\|\vec x\| xx\sqrt{\vec x \cdot \vec x} Magnitude, length, 2\ell_2 norm

Depending on what vectors represent, their operations may represent specific things:

  • point 1 − point 2 = vector extending from point 2 to point 1
  • point + vector = new point
  • vector ± vector = vector
  • point + point = error
  • vector ÷ its own magnitude = direction. This operation is called normalizing the vector
  • direction × scalar = vector made out of direction and magnitude

Two vectors are said to be orthogonal or perpendicular if their dot product is 00.

A vector is called a unit vector if its magnitude is 11.

Two-dimensional vectors have just two components (x1,x2)(x_1, x_2). They have two special operations:

  • (x2,x1)(-x_2, x_1) is perpendicular to (x1,x2)(x_1, x_2)
  • the 2D cross product (x1,x2)×(y1,y2)=x1y2x2y1(x_1, x_2) \times (y_1,y_2) = x_1y_2-x_2y_1 is scalar equal to xysin(θ)\|\vec x\| \|\vec y\| \sin(\theta) where θ\theta is the angle between the two vectors.

Three-dimension vectors have three components (x1,x2,x3)(x_1, x_2, x_3). They have one special operation:

  • the 3D cross product (x1,x2,x3)×(y1,y2,y3)=(x2y3y2x3,x3y1y3x1,x1y2y1x2)(x_1, x_2, x_3) \times (y_1,y_2,y_3) = (x_2y_3-y_2x_3, x_3y_1-y_3x_1,x_1y_2-y_1x_2) is a vector perpendicular to both input vectors with a magnitude equal to xysin(θ)\|\vec x\| \|\vec y\| \sin(\theta).
Javascript code implementing these operators
const add = (x,y) => x.map((e,i)=>e+y[i])
const sub = (x,y) => x.map((e,i)=>e-y[i])
const mul = (x,s) => x.map(e=>e*s)
const div = (x,s) => x.map(e=>e/s)
const dot = (x,y) => x.map((e,i)=>e*y[i]).reduce((s,t)=>s+t)
const mag = (x) => Math.sqrt(dot(x,x))
const normalize = (x) => div(x,mag(x))
const cross = (x,y) => x.length == 2 ?
  x[0]*y[1]-x[1]*y[0] :
  x.map((e,i)=> x[(i+1)%3]*y[(i+2)%3] - x[(i+2)%3]*y[(i+1)%3])

2 Matrices

A matrix is a grid of numbers. They can be seen as a vector of vectors in two ways: either as a vector of columns or a vector of rows.

The matrix transpose ATA^T is computed by treating the rows of AA as columns and vice-versa. If A=ATA = A^T we say AA is symmetric. If A=ATA = -A^T we say AA is skew-symmetric or antisymmetric.

[123456789101112]T=[159261037114812]\begin{bmatrix}1&2&3&4 \\ 5&6&7&8 \\ 9&10&11&12\end{bmatrix}^T = \begin{bmatrix}1&5&9\\2&6&10\\3&7&11\\4&8&12\end{bmatrix}

The matrix product ABA B is a matrix CC. The value in CC at row rr and column cc is equal to the dot product of row rr of AA and column cc of BB. Hence, the matrix product ABA B is only defined if AA has the same number of columns as BB has rows.

[123456][789101112]=[5864139154]\begin{bmatrix}1&2&3 \\ 4&5&6\end{bmatrix} \begin{bmatrix}7&8 \\ 9&10 \\ 11&12\end{bmatrix} = \begin{bmatrix}58&64\\139&154\end{bmatrix}

The matrix-vector product AxA \vec x is computed by treating x\vec x as if it were a matrix with just one column.

[123456]x=[123456][x1x2x3]=[1x1+2x2+3x34x1+5x2+6x3]=(x1+2x2+3x3,4x1+5x2+6x3) \begin{bmatrix}1&2&3 \\ 4&5&6\end{bmatrix} \vec x= \begin{bmatrix}1&2&3 \\ 4&5&6\end{bmatrix} \begin{bmatrix}x_1\\x_2\\x_3\end{bmatrix} = \begin{bmatrix}1x_1+2x_2+3x_3\\4x_1+5x_2+6x_3\end{bmatrix} =(x_1+2x_2+3x_3,4x_1+5x_2+6x_3)

Some texts define the vector-matrix product xA\vec x A to be treating x\vec x as if it were a matrix with just one row, but others disagree and say vectors are always columns an write xTA\vec x^T A instead.

Any linear system of equations can be written as Ax=bA \vec x = \vec b

The linear system of equations 3x+4y+3z=02y=1x+z=4\begin{matrix} 3x &+& 4y &+& 3z &=& 0\\ && 2y && &=& -1\\ -x && &+& z &=& 4\\ \end{matrix} can be written as [343020101][xyz]=[014]\begin{bmatrix}3&4&3\\0&2&0\\-1&0&1\end{bmatrix}\begin{bmatrix}x\\y\\z\end{bmatrix} = \begin{bmatrix}0\\-1\\4\end{bmatrix}

A square matrix has the same number of rows and columns.

The main diagonal of a matrix are the cells with the same row and column index. A diagonal matrix is square and has zeros everywhere except along the main diagonal.

The following is a diagonal matrix [3000040000000001]\begin{bmatrix}3&0&0&0\\0&-4&0&0\\0&0&0&0\\0&0&0&1\end{bmatrix}

The following is not a diagonal matrix because it has non-zero values on the wrong diagonal: [003040100]\begin{bmatrix}0&0&3\\0&-4&0\\1&0&0\end{bmatrix}

In graphics1, we’d say the following is not a diagonal matrix because it is not square: [300004000010]\begin{bmatrix}3&0&0&0\\0&-4&0&0\\0&0&1&0\end{bmatrix}

An identity matrix is a diagonal matrix with 1 as the value on its main diagonal. An identity matrix is often written as II, and often called the identity matrix even though there are several of them (one for each square matrix size).

Two vectors x\vec x and y\vec y are said to be conjugate with respect to a matrix AA if (x)(Ay)=0\left(\vec x\right) \cdot \left(A \vec y\right) = 0. If A=IA = I, conjugate is a synonym for orthogonal or perpendicular. Note that conjugate is also a common term for complex numbers and quaternions with a different meaning in those contexts.

An orthogonal matrix is a square matrix with the property that x  .  Ax=x\forall \vec x \;.\; \|A \vec x\| = \|\vec x\|. Each row of an orthogonal matrix is a unit vector that is orthogonal to all other rows. Each column of an orthogonal matrix is a unit vector that is orthogonal to all other columns. Each orthogonal matrix is either a rotation matrix or a reflection matrix. If AA is orthogonal, then ATA=AAT=IA^T A = A A^T = I. Orthogonal matrix and real-valued unitary matrix are synonyms.

Every matrix can be written as A=USVTA = U S V^T where UU and VV are both orthogonal matrices and SS is a diagonal matrix. This is called the singular value decomposition of AA. The values on the main diagonal of SS are called AA’s singular values.

The condition number of a matrix is the ratio of its largest singular value to its smallest singular value. The farther from 1 the condition number of a matrix AA is, the less effective (less efficient and/or less precise) almost all algorithms using AA become. The condition number of any orthogonal matrix is exactly 1, making them computationally optimal in many situations.

Every matrix also has an eigendecomposition, based on eigenvalues and eigenvectors; this is somewhat like the singular value decomposition and can be easier to compute, but it generally has complex numbers in it and is rarely used in graphics.

The inverse of a square matrix AA is written A1A^{-1} and has the property that A1A=A1A=IA^{-1}A = A^{-1}A = I. AA has an inverse if and only all of its singular values are non-zero.

Javascript code implementing some of these operators

WebGL2 assumed 4×4 matrices in column-major order; this code only works for that specific case.

const m4row = (m,r) => new m.constructor(4).map((e,i)=>m[r+4*i])
const m4rowdot = (m,r,v) => m[r]*v[0] + m[r+4]*v[1] + m[r+8]*v[2] + m[r+12]*v[3]
const m4col = (m,c) => m.slice(c*4,(c+1)*4)
const m4transpose = (m) => m.map((e,i) => m[((i&3)<<2)+(i>>2)])
const m4mul = (...args) => args.reduce((m1,m2) => {
  if(m2.length == 4) return m2.map((e,i)=>m4rowdot(m1,i,m2)) // m*v
  if(m1.length == 4) return m1.map((e,i)=>m4rowdot(m2,i,m1)) // v*m
  let ans = new m1.constructor(16)
  for(let c=0; c<4; c+=1) for(let r=0; r<4; r+=1)
    ans[r+c*4] = m4rowdot(m1,r,m4col(m2,c))
  return ans // m*m
})