RT Graphics Programming
Lesson 06 of 28

Part 1 · Ray tracing by hand

6. Normals and Surface Materials

Separate the fact that a ray hit something from the information required to shade that surface.

Graphics glossary 27 terms mentioned in this lesson

Select any term for a clear definition. The return button brings you back to the exact term link you used.

A hit tells us that a ray met a surface first. It does not yet tell us how that surface faces the ray or how it should respond to light. Those two questions are related but they are not the same. The normal belongs to the surface geometry at a position. The material describes how the surface will be interpreted. Keeping them separate allows one sphere equation to support clay, metal or glass without becoming three different sphere equations.

The normal is also the first result that exposes orientation. A sphere has an obvious outward direction, but a ray may begin outside or inside it. Later reflection and refraction calculations need a normal that is consistently oriented against the incoming ray, along with a separate fact recording which side was hit.

What you should be able to account for

  • Calculate the outward unit normal of a sphere at a hit position.
  • Orient the stored normal against the incoming direction and retain a front-face flag.
  • Explain why geometry and material data meet in a hit record without becoming the same responsibility.

The sphere normal follows from its definition

Every point on a sphere is one radius from its centre. The displacement P - C therefore points directly outwards. Dividing by the radius gives unit length:

Nout = (P − C) / r

This formula assumes the hit point is on the surface and the radius is positive. If an implementation allows a zero radius, the geometry has no usable surface normal. If finite precision places P slightly away from the exact surface, the result may not be exactly unit length; normalising defensively can be reasonable, although it adds work and may conceal a larger intersection error. The teaching code uses division by the known radius so the connection remains visible.

const position = pointOnRay(ray, t);
const outward = scale(
    subtract(position, sphere.centre),
    1 / sphere.radius
);

Record which side was entered

Take the dot product between the incoming ray direction and the outward normal. A negative result means they oppose one another: the ray met the outside of the surface. A positive result means the ray is moving in roughly the same direction as the outward normal, so it is leaving from inside.

const frontFace = dot(ray.direction, outward) < 0;
const normal = frontFace ? outward : scale(outward, -1);

return {
    t,
    position,
    normal,
    frontFace,
    material: sphere.material
};

The stored normal now points against the incoming ray in both cases. The frontFace flag preserves which side was originally encountered. Replacing the outward normal without preserving the flag would lose information required by a refractive material, which must know whether the ray is entering or leaving.

Outside and inside

An outside ray travels down negative Z and hits the front of a sphere whose outward normal points along positive Z. Their dot product is -1, so frontFace is true and the stored normal remains positive Z. A ray inside the sphere travels along positive Z and meets the same surface with an outward positive-Z normal. Their dot product is 1, so frontFace is false and the stored normal is flipped to negative Z. In both records the stored normal opposes the incoming direction.

Hit-record laboratory

Read One Hit Record Two Ways

Switch between the stored surface normal and material albedo while the intersections, hit distances and geometry remain unchanged.

Change this, then watch this: Switch between Normal and Material albedo. The displayed field changes; the hit distance and geometry do not.

Selected diagnostic viewThe same sphere hits displayed using either their surface normals or material albedos.Ready
The same sphere hits displayed using either their surface normals or material albedos.
One unchanged hit recordOne fixed hit record with its normal and material fields shown separately.Calculation
One fixed hit record with its normal and material fields shown separately.
Hit distance t
2.340
Stored normal N
(0.000, 0.000, 1.000)
Material albedo
(0.820, 0.240, 0.130)
Displayed field
Normal

RGB = 0.5 × (N + 1) = (0.500, 0.500, 1.000)

What this establishes:

Turn records into diagnostic views

The normal components lie between -1 and 1. Remapping each component with 0.5 × (N + 1) produces a displayable RGB colour. This creates a normal diagnostic in which smoothly changing colour across a sphere exposes its curvature. The albedo view instead reads the material's base colour.

function normalColour(hit) {
    return scale(add(hit.normal, [1, 1, 1]), 0.5);
}

function albedoColour(hit) {
    return hit.material.albedo;
}

Switch between the two in the laboratory. The intersection distances and visible object boundaries do not change. Only the field read from the record changes. This is a useful debugging habit: display an intermediate quantity directly before asking a more elaborate lighting calculation to conceal it. A normal image can reveal inverted orientation, discontinuities and wrong coordinate spaces far more clearly than a polished material.

This is a normal diagnostic, not a texture normal map

The word normal map is also used for a texture whose stored values perturb a surface normal, usually after a tangent-space transformation. We are not doing that here. We are mapping the actual geometric normal into a visible colour. The glossary makes the local meaning explicit because shared vocabulary can otherwise disguise two different operations.

The material is a set of decisions

For now a material stores albedo, specular strength and shininess. Later it may state reflection, transmission and index of refraction. A material is not merely a final RGB colour. It supplies values and rules that determine how the hit contributes to the ray's result and whether more rays are created.

const blueMaterial = {
    albedo: [0.14, 0.42, 0.82],
    specular: 0.34,
    shininess: 32,
    reflectivity: 0
};

Attaching a material reference to geometry is convenient because the geometry knows which surface was intersected. The sphere intersection must not start performing lighting in response. It should populate the record and stop. This boundary allows the same closest-hit traversal to support diagnostic normals, local lighting and recursive materials without rewriting the geometric tests.

Orient the record

A ray direction is (0, -1, 0) and the outward normal is (0, 1, 0). What are frontFace and the stored normal? What changes if the ray direction becomes (0, 1, 0)?

Reveal the record

The first dot product is -1, so the hit is on the front face and the stored normal remains (0, 1, 0). The second dot product is 1, so the hit is from inside, frontFace is false and the stored normal becomes (0, -1, 0). In both cases it points against the incoming ray.

Make a record inspector

Select five pixels across one sphere. For each, print t, position, normal length, frontFace and material name. Confirm that the normal length is close to one and that the centre-front normal points towards the camera. Then place the camera inside a large sphere and repeat. Explain the flipped stored normals using the face flag.

Keep this model for Vulkan

Vulkan shaders will pass intersection attributes and material indices through explicit buffers and shader interfaces. Coordinate spaces and data layout will become more visible, not less. The present hit record gives us a semantic checklist: distance, position, oriented normal, side and material must agree before the representation is changed for GPU execution.

We now have a position, an oriented normal and a material for the nearest surface. Lesson 7 introduces a light and calculates what the relationships between its direction, the normal and the viewer permit that surface to contribute.