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: ![Figure 7-1](../assets/fig07-1.jpg) 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)$: ![Figure 7-2](../assets/fig07-2.jpg) 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: ![Image 7-1](../assets/img07-1.jpg) 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: ![Image 7-2](../assets/img07-2.jpg) 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.