WARNING
Content Under Development
See
release page for
latest official PDF version.
**Chapter 6 Rectangles and Lights**
First, let’s make a light emitting material. We need to add an emitted function (we could also add
it to hit_record instead -- that’s a matter of design taste). Like the background, it just tells the
ray what color it is and performs no reflection. It’s very simple:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class diffuse_light : public material {
public:
diffuse_light(texture *a) : emit(a) {}
virtual bool scatter(const ray& r_in, const hit_record& rec,
vec3& attenuation, ray& scattered) const { return false; }
virtual vec3 emitted(float u, float v, const vec3& p) const {
return emit->value(u, v, p);
}
texture *emit;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So that I don’t have to make all the non-emitting materials implement emitted(), I have the base
class return black:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class material {
public:
virtual bool scatter(const ray& r_in, const hit_record& rec,
vec3& attenuation, ray& scattered) const = 0;
virtual vec3 emitted(float u, float v, const vec3& p) const {
return vec3(0,0,0);
}
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Next, let’s make the background black in our color function, and pay attention to emitted:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
vec3 color(const ray& r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r, 0.001, MAXFLOAT, rec)) {
ray scattered;
vec3 attenuation;
vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
return emitted + attenuation*color(scattered, world, depth+1);
else
return emitted;
}
else
return vec3(0,0,0);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now, let’s make some rectangles. Rectangles are often convenient for modelling man-made
environments. I’m a fan of doing axis-aligned rectangles because they are easy. (We’ll get to
instancing so we can rotate them later.)
First, here is a rectangle in an xy plane. Such a plane is defined by its z value. For example, $z =
k$. An axis-aligned rectangle is defined by lines $x=x_0$ , $x=x_1$ , $y=y_0$ , $y=y_1$.

To determine whether a ray hits such a rectangle, we first determine where the ray hits the plane.
Recall that a ray $p(t) = a + t \cdot b$ has its z component defined by $z(t) = a_z + t \cdot b_z$.
Rearranging those terms we can solve for what the t is where $z=k$.
$$ t = (k-a_z) / b_z $$
Once we have t, we can plug that into the equations for x and y:
$$ x = a_x + t*b_x $$
$$ y = a_y + t*b_y $$
It is a hit if $x_0 < x < x_1$ and $y_0 < y < y_1$.
The actual xy_rect class is thus:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class xy_rect: public hitable {
public:
xy_rect() {}
xy_rect(float _x0, float _x1, float _y0, float _y1, float _k, material *mat)
: x0(_x0), x1(_x1), y0(_y0), y1(_y1), k(_k), mp(mat) {};
virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
virtual bool bounding_box(float t0, float t1, aabb& box) const {
box = aabb(vec3(x0,y0, k-0.0001), vec3(x1, y1, k+0.0001));
return true;
}
material *mp;
float x0, x1, y0, y1, k;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
And the hit function is:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
bool xy_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
float t = (k-r.origin().z()) / r.direction().z();
if (t < t0 || t > t1)
return false;
float x = r.origin().x() + t*r.direction().x();
float y = r.origin().y() + t*r.direction().y();
if (x < x0 || x > x1 || y < y0 || y > y1)
return false;
rec.u = (x-x0)/(x1-x0);
rec.v = (y-y0)/(y1-y0);
rec.t = t;
rec.mat_ptr = mp;
rec.p = r.point_at_parameter(t);
rec.normal = vec3(0, 0, 1);
return true;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If we set up a rectangle as a light:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
hitable *simple_light() {
texture *pertext = new noise_texture(4);
hitable **list = new hitable*[4];
list[0] = new sphere(vec3(0,-1000, 0), 1000, new lambertian(pertext));
list[1] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));
list[2] = new sphere(vec3(0, 7, 0), 2,
new diffuse_light(new constant_texture(vec3(4,4,4))));
list[3] = new xy_rect(3, 5, 1, 3, -2,
new diffuse_light(new constant_texture(vec3(4,4,4))));
return new hitable_list(list,4);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We get:

Note that the light is brighter than $(1,1,1)$. This allows it to be bright enough to light things.
Fool around with making some spheres lights too.

Now let’s add the other two axes and the famous Cornell Box.
This is yz and xz.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class xz_rect: public hitable {
public:
xz_rect() {}
xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat)
: x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
virtual bool bounding_box(float t0, float t1, aabb& box) const {
box = aabb(vec3(x0,k-0.0001,z0), vec3(x1, k+0.0001, z1));
return true;
}
material *mp;
float x0, x1, z0, z1, k;
};
class yz_rect: public hitable {
public:
yz_rect() {}
yz_rect(float _y0, float _y1, float _z0, float _z1, float _k, material *mat)
: y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
virtual bool bounding_box(float t0, float t1, aabb& box) const {
box = aabb(vec3(k-0.0001, y0, z0), vec3(k+0.0001, y1, z1));
return true;
}
material *mp;
float y0, y1, z0, z1, k;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With unsurprising hit functions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
bool xz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
float t = (k-r.origin().y()) / r.direction().y();
if (t < t0 || t > t1)
return false;
float x = r.origin().x() + t*r.direction().x();
float z = r.origin().z() + t*r.direction().z();
if (x < x0 || x > x1 || z < z0 || z > z1)
return false;
rec.u = (x-x0)/(x1-x0);
rec.v = (z-z0)/(z1-z0);
rec.t = t;
rec.mat_ptr = mp;
rec.p = r.point_at_parameter(t);
rec.normal = vec3(0, 1, 0);
return true;
}
bool yz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
float t = (k-r.origin().x()) / r.direction().x();
if (t < t0 || t > t1)
return false;
float y = r.origin().y() + t*r.direction().y();
float z = r.origin().z() + t*r.direction().z();
if (y < y0 || y > y1 || z < z0 || z > z1)
return false;
rec.u = (y-y0)/(y1-y0);
rec.v = (z-z0)/(z1-z0);
rec.t = t;
rec.mat_ptr = mp;
rec.p = r.point_at_parameter(t);
rec.normal = vec3(1, 0, 0);
return true;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let’s make the 5 walls and the light of the box:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
hitable *cornell_box() {
hitable **list = new hitable*[8];
int i = 0;
material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
list[i++] = new yz_rect(0, 555, 0, 555, 555, green);
list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
list[i++] = new xy_rect(0, 555, 0, 555, 555, white);
return new hitable_list(list,i);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
And the view info:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
vec3 lookfrom(278, 278, -800);
vec3 lookat(278,278,0);
float dist_to_focus = 10.0;
float aperture = 0.0;
float vfov = 40.0;
camera cam(lookfrom, lookat, vec3(0,1,0), vfov, float(nx)/float(ny),
aperture, dist_to_focus, 0.0, 1.0);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We get:

This is very noisy because the light is small. But why are the other walls missing? They are facing
the wrong way. We need outward facing normals. Let’s make a hitable that does nothing but hold
another hitable, but reverses the normals:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class flip_normals : public hitable {
public:
flip_normals(hitable *p) : ptr(p) {}
virtual bool hit(
const ray& r, float t_min, float t_max, hit_record& rec) const {
if (ptr->hit(r, t_min, t_max, rec)) {
rec.normal = -rec.normal;
return true;
}
else
return false;
}
virtual bool bounding_box(float t0, float t1, aabb& box) const {
return ptr->bounding_box(t0, t1, box);
}
hitable *ptr;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This makes Cornell:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
hitable *cornell_box() {
hitable **list = new hitable*[6];
int i = 0;
material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
return new hitable_list(list,i);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
And voila:
