Vectors and Coordinate Spaces
You cannot understand cameras, intersections, or lighting without a small set of vector operations. This is not a full linear algebra course. It is the working toolkit of a renderer.
A 3D vector has three components. Sometimes it denotes a direction. Sometimes the same three numbers denote a point, a location relative to an origin. The storage looks identical. The meaning does not. Confusing the two is a standard beginner failure.
The dot product measures alignment. The cross product builds a third axis perpendicular to two others.
Operations you must own
Component-wise addition and subtraction. The vector from a to b is b - a.
Scaling multiplies every component. Ray stepping is o + t * d.
Length:
|v| = sqrt(vx*vx + vy*vy + vz*vz)
Normalization:
normalize(v) = v / |v|
Normalize directions used in angle and lighting formulas. Do not normalize positions. Do not normalize a known unit vector after every trivial copy unless a long transform chain has introduced drift.
Dot product:
a · b = ax*bx + ay*by + az*bz = |a||b| cos θ
If both vectors are unit length, the dot product is cos θ. That is why n · l appears in diffuse lighting. A negative value means the light lies behind the surface.
Cross product:
a × b = (ay*bz - az*by,
az*bx - ax*bz,
ax*by - ay*bx)
The result is perpendicular to both inputs. In a right-handed system, x × y = z. Cross products build camera bases and triangle normals. Reverse the operand order and you reverse the orientation.
Points, directions, normals
- Points transform with translation.
- Directions transform with the linear part only.
- Normals require the inverse transpose of the linear part. That special case is mandatory under non-uniform scale.
If you translate a direction, lighting and reflections become nonsense. If you transform a normal like a point, the terminator moves to the wrong place on scaled meshes.
Spaces you will name constantly
- Object space: the mesh as authored
- World space: the scene after placement
- Camera space: the world relative to the eye
- Raster space: pixels
Choose whether each shape intersects in world space or object space, then keep that policy consistent inside the class.
Orthonormal bases
A camera needs three mutually perpendicular unit axes. Given look direction w and an approximate up vector:
w = normalize(look)
u = normalize(cross(up, w)) // match your handedness
v = cross(w, u)
If look and up are nearly parallel, the cross product collapses. Keep a fallback up vector. A broken basis is often misdiagnosed as an intersection bug.
Numerical discipline
- Prefer unit directions in formulas that assume cosines.
- Use an epsilon for near-zero and near-parallel tests.
- Do not compare transformed floats with exact equality.
- Normalize once when required, then reuse the result.
Common errors
- Feeding a non-unit vector into a cosine formula
- Crossing axes in the wrong order
- Translating normals or directions
- Normalizing the zero vector after a failed cross product
If the vector layer is boring and correct, later chapters become possible. If it is sloppy, every later chapter lies to you with confidence.
Worked identities
You will use these constantly. Memorize them by implementing them once and testing them.
|a × b| = |a||b| sin θ
(a · b)^2 + |a × b|^2 = |a|^2 |b|^2 // for any a, b
reflect(i, n) = i - 2 (i · n) n // with i pointing into the surface toward the hit, n unit
Build a tiny unit test file for normalize, dot, cross, and reflect. Feed known axes. Assert lengths and orthogonality within an epsilon. Renderer bugs love to hide in unchecked vector helpers.
Coordinate diagrams on paper
Before coding a camera, draw the axes. Label u, v, w, the image plane, and one sample. If you cannot draw it, you do not understand it yet. Most “mysterious” flipped images are undrawn conventions.