WARNING
Content Under Development
See release page for latest official PDF version.
**Chapter 7: Diffuse Materials**
Now that we have objects and multiple rays per pixel, we can make some realistic looking materials.
We’ll start with diffuse (matte) materials. One question is whether we can mix and match shapes and
materials (so we assign a sphere a material) or if it’s put together so the geometry and material
are tightly bound (that could be useful for procedural objects where the geometry and material are
linked). We’ll go with separate -- which is usual in most renderers -- but do be aware of the
limitation.
Diffuse objects that don’t emit light merely take on the color of their surroundings, but they
modulate that with their own intrinsic color. Light that reflects off a diffuse surface has its
direction randomized. So, if we send three rays into a crack between two diffuse surfaces they will
each have different random behavior:

They also might be absorbed rather than reflected. The darker the surface, the more likely
absorption is. (That’s why it is dark!) Really any algorithm that randomizes direction will produce
surfaces that look matte. One of the simplest ways to do this turns out to be exactly correct for
ideal diffuse surfaces. (I used to do it as a lazy hack that approximates mathematically ideal
Lambertian.)
Pick a random point s from the unit radius sphere that is tangent to the hitpoint, and send a ray
from the hitpoint $p$ to the random point $s$. That sphere has center $(p + N)$:

We also need a way to pick a random point in a unit radius sphere centered at the origin. We’ll use
what is usually the easiest algorithm: a rejection method. First, we pick a random point in the unit
cube where x, y, and z all range from -1 to +1. We reject this point and try again if the point is
outside the sphere. A do/while construct is perfect for that:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
vec3 random_in_unit_sphere() {
vec3 p;
do {
p = 2.0*vec3(random_double(), random_double(), random_double()) - vec3(1,1,1);
} while (p.squared_length() >= 1.0);
return p;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
vec3 color(const ray& r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r, 0.0, MAXFLOAT, rec)) {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
return 0.5 * color(ray(rec.p, target - rec.p), world);
}
else {
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This gives us:

Note the shadowing under the sphere. This picture is very dark, but our spheres only absorb half the
energy on each bounce, so they are 50% reflectors. If you can’t see the shadow, don’t worry, we will
fix that now. These spheres should look pretty light (in real life, a light grey). The reason for
this is that almost all image viewers assume that the image is “gamma corrected”, meaning the 0 to 1
values have some transform before being stored as a byte. There are many good reasons for that, but
for our purposes we just need to be aware of it. To a first approximation, we can use “gamma 2”
which means raising the color to the power $1/gamma$, or in our simple case ½, which is just
square-root:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
col /= float(ns);
col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) );
int ir = int(255.99*col[0]);
int ig = int(255.99*col[1]);
int ib = int(255.99*col[2]);
std::cout << ir << " " << ig << " " << ib << "\n";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
That yields light grey, as we desire:

There’s also a subtle bug in there. Some of the reflected rays hit the object they are reflecting
off of not at exactly $t=0$, but instead at $t=-0.0000001$ or $t=0.00000001$ or whatever floating
point approximation the sphere intersector gives us. So we need to ignore hits very near zero:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
if (world->hit(r, 0.001, MAXFLOAT, rec)) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This gets rid of the shadow acne problem. Yes it is really called that.