Surface Normals & Shading

The ray has hit something — a sphere, a triangle, a floor. Knowing where it hit is only half the story; to colour the pixel we need to know which way the surface faces at that point. That arrow sticking straight out of the surface is the surface normal N, and together with the view and light directions it forms the little frame that every lighting model in graphics consumes.

The normal at a hit, line by line

Step 1 — the sphere normal points straight out from the centre. On a sphere with centre C, the surface bulges directly away from the middle. So at a hit point P the outward direction is simply P - C, normalised to unit length:

N = \frac{P - C}{\lVert P - C\rVert}.

No cross products, no stored data — the geometry alone hands you the normal. (Its length is always the radius r, so dividing by \lVert P - C\rVert = r is the normalisation.)

Step 2 — a flat triangle has one face normal. For a single triangle the normal is the same everywhere on it: the face normal (B - A)\times(C - A), normalised. Use it directly and the triangle shades as a flat facet — correct, but you can see every edge of the mesh.

Step 3 — for a smooth look, blend the vertex normals. To make a faceted mesh look round, each vertex stores its own normal N_A, N_B, N_C (the average of the faces meeting there), and at a hit we blend them with the hit's barycentric weights u, v, w — the very same weights the intersection produced:

N = u\,N_A + v\,N_B + w\,N_C.

Step 4 — renormalise after blending. A weighted sum of unit vectors is generally not unit length (it sags toward the inside of the bend), so we must restore it to length one before using it:

\hat{N} = \frac{N}{\lVert N\rVert}.

This single renormalised, interpolated normal is what turns a coarse polygon soup into a smoothly curved surface.

Step 5 — assemble the shading frame. Lighting needs three directions at the hit: the surface normal \hat{N}, the direction to the viewer \hat{V}, and the direction to the light \hat{L}. Every lighting model — diffuse, specular, Phong, PBR — is some function of these three. The simplest, Lambert's diffuse term, is just a dot product:

\text{brightness} = \max\!\big(0,\ \hat{N}\cdot\hat{L}\big).

Face the light squarely (\hat{N}\cdot\hat{L} = 1) and the surface is fully lit; turn edge-on (0) and it goes dark; face away (negative, clamped to 0) and it is in shadow.

Step 6 — flip the normal to face the ray if needed. A stored normal can point the "wrong" way for the side you hit (you struck the back face, or you're inside the surface). If the normal already agrees with the incoming ray direction D — that is, \hat{N}\cdot D > 0 — flip it so it faces the viewer:

\hat{N} \leftarrow -\hat{N} \quad \text{when} \quad \hat{N}\cdot D > 0. At a ray hit, the surface normal drives all shading:

The choice of which normal you use at a hit is the entire difference between a faceted and a rounded surface — the geometry is identical.

Same vertices, same barycentric weights — only the question of what you interpolate (a face normal, a per-vertex brightness, or the normal itself) separates the three. The interpolated normal is where ray–triangle, barycentrics, and lighting all come together.