Chapter 10

Dot Products & Duality

The dot product seems like a random formula -- multiply matching components and add. But it hides a deep geometric truth about projection.

You've seen how matrices transform vectors, how determinants measure area scaling, how rank tells you what survives and null spaces tell you what's lost. All of that was about transformations -- functions that take vectors to vectors. Now we turn to something that seems simpler: an operation that takes two vectors and produces a number.

The dot product of two vectors is the sum of their component-wise products: ab=axbx+ayby\vec{a} \cdot \vec{b} = a_x b_x + a_y b_y. That's the algebraic definition, and it's easy to compute. But it doesn't tell you why you'd want to compute it, or what the result means. The geometric picture does.

Projection: the shadow on a line

Take two vectors a=(3,2)\vec{a} = (3, 2) and b=(4,1)\vec{b} = (4, 1). If you shine a light straight down onto the line containing b\vec{b}, the vector a\vec{a} casts a "shadow" on that line. That shadow is the projection of a\vec{a} onto b\vec{b}.

Drop a perpendicular from the tip of a\vec{a} to the line through b\vec{b}. The foot of that perpendicular is the projection point. The length of the shadow, multiplied by the length of b\vec{b}, equals the dot product.

a = (3, 2) b = (4, 1) proj (56/17, 14/17) a . b = 3(4) + 2(1) = 14 shadow length x |b| = dot product

The blue vector a\vec{a} is projected onto the green vector b\vec{b}. The orange arrow is the projection -- the "shadow" of a\vec{a} on the line of b\vec{b}. The dashed line drops perpendicular from a\vec{a}'s tip, forming a right angle.

The formula for the projection vector is:

projba=abb2b\text{proj}_{\vec{b}}\vec{a} = \frac{\vec{a}\cdot\vec{b}}{|\vec{b}|^2}\vec{b}

The dot product ab=14\vec{a} \cdot \vec{b} = 14 is doing real geometric work here. It measures how much of a\vec{a} lies in the direction of b\vec{b}. Divide by b2=17|\vec{b}|^2 = 17 to get the scalar coefficient, then scale b\vec{b} by that coefficient to get the projection vector.

The sign tells you the angle

The dot product isn't just a magnitude -- its sign carries critical information. When two vectors point roughly the same direction, the dot product is positive. When they're perpendicular, it's exactly zero. When they point in roughly opposite directions, it's negative.

acute a b a . b > 0 acute angle a b a . b = 0 perpendicular obtuse a b a . b < 0 obtuse angle

Three cases. Top-left: the vectors point in similar directions -- their dot product is positive. Top-right: the vectors are perpendicular -- their dot product is exactly zero. Bottom: the vectors point in opposing directions -- their dot product is negative.

This is the geometric formula at work:

ab=abcosθ\vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta

The cosine of the angle between the vectors determines the sign. Acute angles have positive cosine. Right angles have zero cosine. Obtuse angles have negative cosine. The dot product inherits that sign directly.

This makes the dot product a natural tool for testing alignment. In code, you don't need to compute the angle explicitly -- just check whether the dot product is positive, negative, or near zero.

Duality: a 1x2 matrix is a vector in disguise

Here's where the dot product connects to everything we've built so far about transformations. Consider a 1×21 \times 2 matrix:

[21]\begin{bmatrix} 2 & 1 \end{bmatrix}

This matrix takes any 2D vector and transforms it into a single number:

[21][xy]=2x+1y\begin{bmatrix} 2 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = 2x + 1y

Look at that formula. It's exactly the dot product of the vector (2,1)(2, 1) with the vector (x,y)(x, y). The 1×21 \times 2 matrix [21]\begin{bmatrix} 2 & 1 \end{bmatrix} and the vector (2,1)(2, 1) are the same thing viewed from two perspectives. The matrix sees it as a transformation from 2D to 1D. The vector sees it as a geometric arrow. But they compute the same result.

(2, 1) (1, 0) output: 2 (1, 3) output: 5 (-1, 1) output: -1 Matrix view: [2 1] * [x, y] = 2x + y Same as dotting with (2, 1)

The blue vector (2,1)(2, 1) defines a linear function: it maps every 2D vector to a number via the dot product. The green input vectors land on the orange number line. The 1×21 \times 2 matrix [2,1][2, 1] computes the same thing.

This is duality -- the deep connection between vectors and linear functions. Every linear function from R2\mathbb{R}^2 to R\mathbb{R} can be written as "dot with some vector." And every vector in R2\mathbb{R}^2 defines a linear function from R2\mathbb{R}^2 to R\mathbb{R}. They're two sides of the same coin.

This isn't just abstract elegance. It means that whenever you compute a dot product, you're secretly applying a linear transformation. And whenever you have a linear function that outputs a single number, there's a vector hiding inside it.

Lighting: normal dot light direction

The dot product shows up everywhere in graphics, but its most visible application is literally about visibility -- how bright a surface appears under a light.

A surface has a normal vector n\vec{n} pointing straight out from it. A light has a direction vector L\vec{L} pointing toward the surface. The brightness of the surface is controlled by the dot product nL\vec{n} \cdot \vec{L}.

When the surface faces the light directly, the normal and light direction are aligned, and the dot product is large. When the surface is angled away, the dot product shrinks. When the surface faces completely away, the dot product goes negative (and we clamp to zero -- negative brightness doesn't make sense).

n bright n . L = 1.0 facing light n L dim n . L = 0.7 angled n L dark n . L = 0.0 edge-on Diffuse Lighting: brightness = max(0, n . L) n = surface normal, L = direction toward light

Three surfaces at different angles to a light source. Left: the normal aligns with the light direction, giving maximum brightness. Center: the surface is tilted, reducing brightness. Right: the surface is edge-on to the light, producing zero brightness.

This is the Lambertian lighting model, and it's the foundation of every real-time renderer. The formula is just:

brightness = max(0, dot(normal, lightDir))

The max(0, ...) clamps negative values -- when the dot product is negative, the surface faces away from the light and receives no illumination. The dot product does all the geometric heavy lifting: it naturally produces a cosine falloff that matches how real diffuse surfaces behave.

The formal bit

The dot product has two equivalent definitions:

Algebraic: Given a=(ax,ay)\vec{a} = (a_x, a_y) and b=(bx,by)\vec{b} = (b_x, b_y):

ab=axbx+ayby\vec{a} \cdot \vec{b} = a_xb_x + a_yb_y

Geometric: Given the angle θ\theta between the two vectors:

ab=abcosθ\vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta

The fact that these two formulas always give the same result is not obvious -- it's a genuine theorem. It connects coordinate arithmetic to angle measurement.

The projection formula follows directly:

projba=abb2b\text{proj}_{\vec{b}}\vec{a} = \frac{\vec{a}\cdot\vec{b}}{|\vec{b}|^2}\vec{b}

The scalar abb2\frac{\vec{a}\cdot\vec{b}}{|\vec{b}|^2} is the component of a\vec{a} in the direction of b\vec{b}, measured in units of b|\vec{b}|. Multiply by b\vec{b} to get the actual projection vector.

Duality is the deep structural result: every linear function f:RnRf : \mathbb{R}^n \to \mathbb{R} can be written as f(v)=wvf(\vec{v}) = \vec{w} \cdot \vec{v} for a unique vector w\vec{w}. In other words, linear functions to the real numbers and vectors are secretly the same thing. A 1×n1 \times n row matrix is a vector, just written sideways. The dot product is how they act on inputs.

Key properties:

Worked example: diffuse lighting in 3D graphics

You're building a simple renderer. Each surface point has a unit normal vector n\vec{n}, and there's a directional light described by a unit vector L\vec{L} pointing toward the light source. The diffuse brightness at each point is:

brightness=max(0,  nL)\text{brightness} = \max(0, \; \vec{n} \cdot \vec{L})

Suppose the light comes from the upper-right: L=(12,12,0)(0.707,0.707,0)\vec{L} = \left(\frac{1}{\sqrt{2}}, \frac{1}{\sqrt{2}}, 0\right) \approx (0.707, 0.707, 0).

Surface A faces directly toward the light: nA=(0.707,0.707,0)\vec{n}_A = (0.707, 0.707, 0).

nAL=(0.707)(0.707)+(0.707)(0.707)+(0)(0)=0.5+0.5=1.0\vec{n}_A \cdot \vec{L} = (0.707)(0.707) + (0.707)(0.707) + (0)(0) = 0.5 + 0.5 = 1.0

Full brightness. The surface is perfectly aligned with the light.

Surface B faces upward: nB=(0,1,0)\vec{n}_B = (0, 1, 0).

nBL=(0)(0.707)+(1)(0.707)+(0)(0)=0.707\vec{n}_B \cdot \vec{L} = (0)(0.707) + (1)(0.707) + (0)(0) = 0.707

About 70% brightness. The surface catches some of the light, but not all of it -- it's tilted 45 degrees away from the ideal angle.

Surface C faces away from the light: nC=(0.707,0.707,0)\vec{n}_C = (-0.707, -0.707, 0).

nCL=(0.707)(0.707)+(0.707)(0.707)+(0)(0)=0.50.5=1.0\vec{n}_C \cdot \vec{L} = (-0.707)(0.707) + (-0.707)(0.707) + (0)(0) = -0.5 - 0.5 = -1.0

The dot product is 1.0-1.0, so max(0,1.0)=0\max(0, -1.0) = 0. The surface is in complete shadow.

In code:

function diffuseBrightness(normal, lightDir) {
  const dot = normal.x * lightDir.x
             + normal.y * lightDir.y
             + normal.z * lightDir.z;
  return Math.max(0, dot);
}

This single dot product call is the core of the Lambertian shading model. Every pixel in a real-time 3D scene runs this computation (or something built on it) to determine its color. It's fast, it's correct, and the geometry behind it is just the projection story from the first diagram -- how much of the normal points in the direction of the light.

Key Takeaway: The dot product measures how much two vectors point in the same direction. It connects vectors and linear transformations through duality -- every linear function to a scalar is secretly a dot product with some vector.

What's next

The dot product gives a scalar from two vectors. In 3D, there's another operation -- the cross product -- that gives a vector perpendicular to both inputs. Where the dot product measures alignment, the cross product measures the area of the parallelogram two vectors span, and it points in the direction that's orthogonal to both.