Diffuse and Lambertian Shading
Reading time: 22 mins.Diffuse or Lambertian Shading

Diffuse objects are easy to simulate in CG. However, to understand how it works, we first need to learn about the way light interacts with surfaces. Technically, to understand this topic thoroughly, we should study radiometry first. To keep this introduction simple and intuitive, we will proceed without it. Simplifying the concept, when we shade a point in CG, we assume this point represents a very small surface, which we call a differential area. Don’t focus too much on the term "differential" here, and pay attention instead to the term "area." Rather than considering a theoretical point that has no physical meaning, we will be considering a very small area around the point, which we generally denote in CG as

In other words, all light energy contained within this volume, which has the same cross-section as
To summarize:
-
When the light beam is perpendicular to
, 100% of the photons within the small cone of light, whose cross-section is the same as , strike . -
When the light beam makes an angle with the normal of the shaded point, then the cross-section of the beam with the surface becomes larger than
. This means that the photons or light energy are distributed over a larger region than . Since we are only interested in the amount of light energy arriving over the region covered by , we can also say that fewer photons reach as the angle of the light beam with the normal increases. The larger the angle, the fewer the photons. Thus, keeps receiving less and less energy as the angle between the beam and the normal increases. -
In the extreme case, when the beam is perpendicular to the normal at
, then photons do not fall on at all. In this particular case, does not receive any light energy at all.


From these observations, it becomes easier to understand that a surface element
This principle is one of the most basic and well-known phenomena in CG. It is known under the name of Lambert's Cosine Law. The amount of light that a surface receives is directly proportional to the angle between the surface normal
Hence the term cosine is in the law's name. The cosine of the angle
The sign
In computer graphics, the term albedo is often denoted with the Greek letter

This is almost the complete formula to compute the color of a point on a diffuse surface, knowing the incident light energy, the surface normal, the light direction, and the surface albedo. We are only missing one term to complete the equation.
As mentioned in the first chapter, diffuse surfaces have a unique property in that they reflect light impinging on their surface equally in every direction above the point of incidence. Imagine a light beam striking the surface of an object at one point and light energy being redistributed over that point in a hemisphere of directions expanding from the point of incidence outward, as shown in Figure 3. From a practical perspective, this means that the energy of the light beam, which we know is attenuated by the cosine of the angle between the surface normal and the light direction, is redistributed across the surface of a hemisphere. We can look at this problem the other way around and say that if we were to collect all the energy distributed across the surface of this hemisphere, it should equal the incident light energy, minus, of course, the amount of light that was absorbed by the surface. There is as much light redistributed as light incident on the surface, minus the amount of light absorbed by the surface. Mathematically, collecting the light energy across the surface of the hemisphere can be written using an integral:
The integral symbol means that we are interested in summing up, in a way, all the light energy that is spread across the surface of the hemisphere (imagine that you are counting the number of photons distributed over the surface of that hemisphere). The concept of the hemisphere in this equation is represented by the term
If you are not familiar with the concept of an integral, we advise you to read the lesson on The Mathematics of Shading.

We won't give much information about integrals in this lesson, nor explain what the term
Please refer to the lesson The Mathematics of Shading for more information on integrals and how they can be solved either analytically or using techniques such as Monte Carlo integration.
Now, hopefully, you will agree that the amount of light that is reflected by the surface can't be greater than the amount of light that is effectively reaching the surface minus the amount of light that is absorbed. Logical, right? In other terms, if we could measure the amount of energy reflected by the surface using the equation above, it should be lower than or equal to the amount of incident light energy:
Where

The problem with our integral is that it is expressed in terms of solid angle (the
Where does the use of
It comes from the need to account for the varying "width" of the area on the sphere's surface as
Spherical coordinates (
-
(theta) is the polar angle, measured from the positive z-axis. -
(phi) is the azimuthal angle, measured in the xy-plane from the positive x-axis.
When dealing with integrals over surfaces like spheres, we express areas in terms of solid angles. A solid angle
-
At the poles (
or ), the bands become point-like, having no width. -
At the equator (
), the bands are at their maximum width.
The factor of
The differential area element on a sphere (surface area) in spherical coordinates is given by
This integral sums up the function
Mathematical proof that this double integral is equal to the area of a unity sphere:
Let's perform the integration. The integral to find the total surface area of the sphere is:
-
Integrate over
: -
Integrate over
:
Combining these results:
This result,
What we integrate over in an integral are the quantities that, at the end of the equations, are preceded by the letter
Note also that we have the terms
Also:
Thus:
We can replace the first integral (the integral over
We can finally use the first fundamental theorem of calculus to solve the last integral. The antiderivative of
Just in case you didn't understand how the antiderivative was determined, here are the steps. We will start from the antiderivative and demonstrate that its derivative is indeed
-
Starting Expression:
-
Consider the function
.
-
-
Define the Inner Function:
-
Define
. This choice leads us to re-examine the expression: .
-
-
Apply the Chain Rule:
-
To differentiate
with respect to , we utilize the chain rule. The chain rule stipulates that to differentiate a composite function, you first differentiate the outer function treating the inner function as a constant, and then multiply by the derivative of the inner function with respect to .
-
-
Chain Rule in Action:
-
Differentiating
with respect to yields . -
The derivative of
with respect to , given , is .
-
-
Combining the Results:
-
The derivative thus becomes
.
-
-
Final Step:
-
By substituting back into the original expression and considering the steps, we find:
-
This confirms that the derivative of
is indeed , verifying that is the correct antiderivative.
-
Thus:
Finally, we get the result:
The term
And this would work. So the amount of light reflected by a diffuse surface is in fact:
Because if you integrate this equation over the hemisphere:
Then you indeed find out that with this equation, a diffuse surface cannot reflect more energy than it receives:
You can view the division of the albedo by
Now that we know the equation, let's put this into practice. This time, we will need to add a light to the scene. For simplicity, we will start with a distant light and only one light. We will learn about what we should do when there is more than one light in the scene and about spherical lights in the next chapter. We will modify the source code of the ray tracer we developed in the previous lesson. First, each object in the scene now has an albedo parameter, which you can see as the color of the object:
class Object { Object(const Matrix44f &o2w, const Vec3f &c = 0.18) : objectToWorld(o2w), worldToObject(o2w.inverse()), albedo(c) ... Vec3f albedo; ... };
Why is the default color 0.18? The reason we set the albedo default value to 0.18 is that objects from the real world reflect, on average, around 18% of the light they receive. This is an average value. In other words, if you look at how much light different fruits, asphalt, snow, tree leaves, grass, etc., reflect and take the average of all these values, you end up with a value close to 18%. More information on this topic can be found in the lesson A Creative Dive into BRDF, Linearity, and Exposure.
When a ray hits an object from the scene, we will first compute the surface normal at the intersection point. We then compute the pixel color associated with the camera ray using the equation we provided above:
Vec3f castRay( const Vec3f &orig, const Vec3f &dir, const std::vector<std::unique_ptr<Object>> &objects, const std::unique_ptr<DistantLight> &light, the Options &options) { Vec3f hitColor = options.backgroundColor; float tnear = kInfinity; Vec2f uv; uint32_t index = 0; Object *hitObject = nullptr; if (trace(orig, dir, objects, tnear, index, uv, &hitObject)) { Vec3f hitPoint = orig + dir * tnear; Vec3f hitNormal; Vec2f hitTexCoordinates; hitObject->getSurfaceProperties(hitPoint, dir, index, uv, hitNormal, hitTexCoordinates); Vec3f L = -light->dir; // compute the color of a diffuse surface illuminated // by a single distant light source. hitColor = hitObject->albedo / M_PI * light->intensity * light->color * std::max(0.f, hitNormal.dotProduct(L)); } return hitColor; }
This is just a straightforward application of the equation. The albedo is divided by
Note that if the light and the surface normal are on the same side of the plane perpendicular to the surface normal, then the result of the dot product between the surface normal and the light direction is positive. However, if the light is "behind" the surface, the result of the dot product is negative. Of course, if the light is technically behind the surface, it shouldn't illuminate the surface anymore, but more importantly, we don't want to introduce negative values in the computation of the surface color. Thus, we clamp the result of the dot product using the C++ std::max()
function (line 20).
Here are some results:

Congratulations! You now know about two shading techniques. You know about the facing ratio and simulating the appearance of the perfect diffuse surface. In the next chapter, we will learn about handling more than one light source as well as using spherical lights. We will also learn about adding shadows to the image.