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:
-
Sphere normal — at a hit P on a sphere of
centre C,
N = (P - C)/\lVert P - C\rVert.
-
Smooth (interpolated) normal — across a triangle, blend the vertex
normals by the hit's barycentric weights:
N = u N_A + v N_B + w N_C.
-
Renormalise — a blend of unit vectors isn't unit, so set
\hat{N} = N/\lVert N\rVert before use.
-
Shading frame — the normal \hat{N}, view
\hat{V} and light \hat{L} are the
inputs every lighting model consumes (e.g.
\max(0, \hat{N}\cdot\hat{L})).
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.
-
Flat shading — one face normal per triangle. Every pixel on the triangle
shares it, so the lighting is constant across the facet and the silhouette of each polygon
shows. Cheap, blocky, sometimes exactly the stylised look you want.
-
Gouraud shading — compute the lighting at each vertex using the
vertex normals, then barycentrically interpolate the resulting brightness across
the face. Smooth and fast, but it can miss a small highlight that falls between vertices.
-
Phong shading — interpolate the normal itself across the face
(Steps 3–4), renormalise per pixel, and light with that. Highlights land crisply wherever
the surface truly faces the light, so a low-poly sphere reads as genuinely round.
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.