Core Idea
The dot product takes two vectors and returns a single number — how much one vector projects onto the other. Drag the arrowheads; the gold segment is that projection, and crossing 90° flips its sign:
dot(a, b) = a.x·b.x + a.y·b.y + a.z·b.z = |a| · |b| · cos(θ)
For normalized vectors the result is exactly cos θ:
| Result | Meaning |
|---|---|
1.0 |
same direction |
0.0 |
perpendicular |
-1.0 |
opposite directions |
> 0 / < 0 |
same / opposite half-space |
Order doesn’t matter (dot(a,b) = dot(b,a)), and the vectors don’t need to touch — only their directions are compared, as if both tails sat at the origin.
Lighting Is a Dot Product
The surface at the top of this page is lit by nothing else: max(dot(N, L), 0.0) per pixel — surface normal against light direction, negative values clamped to darkness. No light objects, no lighting system; one instruction. This Lambertian trick gives realistic diffuse shading at almost zero GPU cost.
It runs in the fragment shader — once per pixel. A 1920×1080 viewport at 60 fps is over 124 million dot products per second for a single lighting pass; the GPU shrugs, because dot() is a single hardware instruction executed by thousands of cores in parallel. The rasterizer interpolates normals across each triangle, so the value varies smoothly per pixel — soft gradients instead of flat facets.
What It’s For
| Use case | Expression |
|---|---|
| Facing check — is A looking toward B? | dot(aForward, toB) > 0 |
| Field-of-view / cone trigger | dot(forward, toTarget) > 0.95 |
| Impact strength along a surface | abs(dot(velocity, surfaceNormal)) |
| Diffuse lighting | max(dot(N, L), 0.0) |
| Fresnel rim glow | 1.0 - max(dot(N, viewDir), 0.0) |
| Terrain: floor vs. wall | dot(N, (0,1,0)) > 0.9 |
| Project a point onto an axis | dot(point, axisDir) |
The Fastest Angle Is One You Never Calculate
acos and atan2 cost many GPU cycles; dot() costs one. Almost every “what’s the angle?” question is really a threshold question — so compare dot(a, b) > 0.95 instead of computing degrees. See trigonometry for the sin/cos machinery underneath.
One Rule: Same Space
Both vectors must live in the same coordinate space. A light direction in world space against a normal in object space produces a number that is valid math and geometric nonsense — the most common source of “why does my lighting look wrong?”. In shaders, transform normals (normal matrix / model matrix) before the dot. See coordinate spaces.
Connections
dot(â, b̂) = cos θ ties it to trigonometry — an angle detector that never computes an angle. A matrix × vector multiply is a stack of dot products — it is the operation inside every transform. The cross product is the companion: perpendicular vector out, where dot gives an aligned scalar out. And because the result is a clean −1…1 number, it slots directly into interpolation — geometry turned into a blend parameter, a slider computed from the scene itself.