Shading with Surface Normals
Introduction: From Flat to Three-Dimensional
In the previous lesson, you successfully rendered your first 3D object — a sphere — using ray tracing. However, if you look closely at the result, you'll notice something that might seem a bit disappointing: the sphere appears as a flat red circle. There's no sense of depth, no curvature, no indication that you're looking at a three-dimensional object rather than a simple 2D disk painted on the screen.
This flatness occurs because you're coloring every pixel that hits the sphere with the exact same solid red color, regardless of where on the sphere's surface that ray intersects. In the real world, objects appear three-dimensional because light interacts differently with different parts of their surfaces depending on the surface orientation. A sphere's surface curves away from us, and this curvature should be visible in how it's shaded.
In this lesson, you'll learn how to give your sphere a sense of depth and dimensionality by using surface normals. By the end of this lesson, your flat red circle will transform into a sphere that clearly looks three-dimensional, with color gradients that reveal its curved surface. You'll accomplish this by computing the surface normal at each intersection point and mapping that normal vector directly to RGB color values. While this isn't yet realistic lighting (that comes later), it's a crucial stepping stone that will help you understand how surface orientation affects appearance and prepare you for implementing proper lighting models.
The mathematics involved are straightforward, and you'll see that with just a few modifications to your existing code, you can achieve dramatically more convincing results. Let's begin by understanding what surface normals are and why they're so important in computer graphics.
Understanding Surface Normals
A surface normal is a vector that points perpendicular to a surface at a specific point. Think of it as an arrow sticking straight out from the surface, indicating which direction the surface is "facing" at that location. Surface normals are fundamental in computer graphics because they encode information about surface orientation, which is essential for calculating how light should interact with that surface.

For a sphere, the surface normal at any point has a particularly elegant property: it always points directly outward from the sphere's center, passing through the surface point. This makes intuitive sense — if you imagine standing on the surface of a perfectly round ball, "straight up" from your perspective would be the direction pointing away from the ball's center through where you're standing.
Mathematically, if we have a sphere centered at point c and a point p on the sphere's surface, the outward normal at p is simply the vector from c to p, normalized to unit length. We can write this as: N = (p - c) / r, where r is the sphere's radius. Since p is on the sphere's surface, the distance from c to p is exactly r, so dividing by r gives us a unit vector (a vector with length 1). In our code, we'll use the unit_vector() function to achieve the same result: unit_vector(p - center).
Why do we care about unit length? In computer graphics, normals are conventionally kept at unit length for consistency and to simplify calculations. When we later implement lighting models, having unit-length normals means we can use them directly in dot products without worrying about scaling factors. For now, unit-length normals also ensure that our color mapping (which we'll discuss shortly) produces consistent results regardless of the sphere's size.
Surface normals are the key to making surfaces look three-dimensional. They tell us how the surface is oriented at each point, and this orientation information is what allows us to simulate depth, curvature, and eventually realistic lighting. Even though we're not implementing full lighting yet, visualizing the normals themselves will immediately reveal the sphere's three-dimensional structure.
