Hello! Welcome back to your C++ ray tracing workshop.
In the last lesson, we made a significant leap by implementing a function that calculates the precise distance (t value) to a ray-sphere intersection. This told us where along the ray a hit occurs. Now, we'll build on that foundation to understand the properties of the surface at that intersection point.
This lesson focuses on computing the surface normal, a vector that is fundamental to virtually all lighting and shading calculations. By the end of this session, you'll understand what a normal is, how to calculate it for a sphere, and how to verify your calculation visually. This is a crucial step that moves us from rendering simple silhouettes to creating surfaces that can realistically interact with light, bringing you closer to understanding the core mechanics of production renderers like Arnold and Karma.
From Intersection Distance t to a 3D Hit Point
First things first. We have the distance t, but we need the actual 3D coordinates of the intersection point. Fortunately, this is a direct application of the ray's parametric equation, which you know well:
Where is a point on the ray, is the ray's origin, and is its direction. Since our hit_sphere function now returns the correct value of t for the closest intersection, we can find the world-space hit point, , by simply plugging t into the equation.
// Assuming 'r' is our ray and 't' is the value from hit_sphere
point3 P_hit = r.at(t); // or r.origin() + t * r.direction();
This simple step is powerful; it translates our 1D distance t into a full 3D coordinate. This point is where all our subsequent shading calculations will happen.
What is a Surface Normal?
Before we calculate it, let's clarify what a surface normal is. A surface normal (or just "normal") is a vector that is perpendicular to the surface at a specific point. It essentially tells you which direction the surface is "facing" at that location.
Think about standing on a smooth hill. The direction pointing straight up from the ground beneath your feet is the surface normal at your position. As you walk around, the normal's direction changes, always pointing away from the surface. In graphics, this vector is the key to figuring out how light should reflect off a surface.
To get a solid intuition for this concept and why it's so critical for lighting, please watch the following segment.
LIGHTING AND SHADING // Ray Tracing series
This clip from The Cherno's Ray Tracing series explains what a surface normal is and uses a simple real-world analogy to show why it's essential for determining how bright a surface should appear based on its orientation to a light source.
Watch from 20:56 to 23:23. Focus on the core idea: the normal vector represents the direction a surface is facing, and this information is crucial for calculating lighting.
Calculating the Normal for a Sphere
For some complex shapes, calculating the normal can be mathematically intensive. For a sphere, however, it's beautifully simple. The vector from the center of a sphere to any point on its surface is, by definition, perpendicular to the surface at that point.

This gives us a straightforward formula:
In words: subtract the sphere's center from the intersection point, and then normalize the resulting vector. Normalizing it (making its length 1) is a critical convention. It turns the vector into a pure direction, which simplifies lighting equations later on. This policy is explained well in Ray Tracing in One Weekend.
Ray Tracing in One Weekend Series
This section from Ray Tracing in One Weekend discusses the geometry of a sphere normal and the important design decision to always keep normal vectors as unit length.
Read the subsection "Shading with Surface Normals". Pay attention to the rationale for adopting the policy that all normal vectors will be of unit length. The geometric description hit point minus the center is the key takeaway.
The resource below from Scratchapixel provides the same formula and a concise code snippet, reinforcing the concept.
A Minimal Ray-Tracer: Ray-Sphere Intersection
The article 'A Minimal Ray-Tracer' provides a very direct mathematical definition and C++ snippet for calculating the normal.
Read the short section "Computing the Normal at the Intersection Point". This confirms the formula and shows what the implementation looks like.
Test your understanding!
A sphere is centered at (10, 0, -5) with a radius of 3. Your ray tracer finds an intersection at the point (10, 3, -5). What is the surface normal vector at this hit point?
Show answer
-
Find the vector from center to hit point:
-
Normalize the vector:
The length of is3.
The surface normal is , pointing straight up along the Y-axis.
Putting It Into Practice: Visualizing Normals
We now have the theory to calculate the normal. But how do we check if our code is correct? We can't just print out millions of vectors. A common and powerful technique in graphics programming—one you've likely encountered in your VFX work with things like "normal passes"—is to visualize data as color.
We can map the X, Y, and Z components of our normal vectors directly to the R, G, and B channels of our output image. There's one small catch: normal components are in the range [-1, 1], while color components need to be in [0, 1]. We can remap the range with a simple linear transformation:
For example, a normal component of -1 becomes 0.5 * (-1 + 1) = 0, and a component of 1 becomes 0.5 * (1 + 1) = 1. This neatly scales our normal into a visible color range.
The following resources show exactly how to implement this, bringing our concepts together into working code.
LIGHTING AND SHADING // Ray Tracing series
This clip continues from where the last one left off, showing how to calculate the sphere normal in code and then how to implement the remapping to visualize it as a color.
Watch from 23:23 to 26:48. The first part (to 24:55) shows the normal calculation. The second part demonstrates the visualization, including the (normal * 0.5) + 0.5 remapping trick.
The C++ code in Ray Tracing in One Weekend provides a perfect template for updating our ray_color function.
Ray Tracing in One Weekend Series
The code in this section demonstrates exactly how to modify your ray_color function to compute the normal and return its color representation.
Focus on the code block for ray_color. Notice how it first calls hit_sphere, then uses the returned t to compute the hit point (r.at(t)), calculates the normal N, and finally returns the remapped color: 0.5*color(N.x()+1, N.y()+1, N.z()+1).
Your Updated ray_color Function
By combining these ideas, your ray_color function should now look something like this:
color ray_color(const ray& r) {
// In the last lesson, we created this function. Now we'll use its return value.
auto t = hit_sphere(point3(0, 0, -1), 0.5, r);
// If the ray hits the sphere...
if (t > 0.0) {
// Calculate the hit point using the 't' value.
point3 hit_point = r.at(t);
// Calculate the normal at the hit point.
vec3 normal = unit_vector(hit_point - point3(0, 0, -1));
// Map the normal's [-1, 1] components to [0, 1] color range and return it.
return 0.5 * color(normal.x() + 1, normal.y() + 1, normal.z() + 1);
}
// If the ray misses, return the background color.
vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5 * (unit_direction.y() + 1.0);
return (1.0 - a) * color(1.0, 1.0, 1.0) + a * color(0.5, 0.7, 1.0);
}
When you run this, you should see a colorful sphere, where red corresponds to the X-axis, green to the Y-axis, and blue to the Z-axis.

Conclusion
Excellent work! You have now connected all the dots from ray-casting to surface analysis. This is a major milestone. You're no longer just detecting objects; you're extracting essential geometric information from them.
Key Takeaways:
- The 3D intersection point is found by plugging the
tvalue from your intersection test into the ray equation: . - A surface normal is a unit vector perpendicular to a surface, indicating the direction it faces.
- For a sphere, the normal is calculated by subtracting the sphere's center from the hit point and normalizing the result: .
- Visualizing normals by mapping their
[-1, 1]components to[0, 1]RGB colors is a powerful debugging technique.
Next Steps
The next official learning outcome is to "visualize surface normals as colors," which we've already covered as a necessary verification step. In our next lesson, we will formalize this, ensure our code is robust, and then move right on to a topic you've been waiting for: Module 4: Positionable Camera and Scene View. We'll use our knowledge of vectors to build a complete camera system that can be positioned anywhere in the scene, looking at any point, preparing you to explore more complex worlds.