WARNING
Content Under Development
See
release page for
latest official PDF version.
**Chapter 1: Motion Blur**
When you decided to ray trace, you decided visual quality was worth more run-time. In your fuzzy
reflection and defocus blur you needed multiple samples per pixel. Once you have taken a step down
that road, the good news is that almost all effects can be brute-forced. Motion blur is certainly
one of those. In a real camera, the shutter opens and stays open for a time interval, and the camera
and objects may move during that time. Its really an average of what the camera sees over that
interval that we want. We can get a random estimate by sending each ray at some random time when the
shutter is open. As long as the objects are where they should be at that time, we can get the right
average answer with a ray that is at exactly a single time. This is fundamentally why random ray
tracing tends to be simple.
The basic idea is to generate rays at random times while the shutter is open and intersect the model
at that one time. The way it is usually done is to have the camera move and the objects move, but
have each ray exist at exactly one time. This way the “engine” of the ray tracer can just make sure
the objects are where they need to be for the ray, and the intersection guts don’t change much.
For this we will first need to have a ray store the time it exists at:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class ray
{
public:
ray() {}
ray(const vec3& a, const vec3& b, float ti = 0.0) { A = a; B = b; _time = ti;}
vec3 origin() const { return A; }
vec3 direction() const { return B; }
float time() const { return _time; }
vec3 point_at_parameter(float t) const { return A + t*B; }
vec3 A;
vec3 B;
float _time;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now we need to modify the camera to generate rays at a random time between `time1` and `time2`.
Should the camera keep track of `time1` and `time2` or should that be up to the user of camera when
a ray is created? When in doubt, I like to make constructors complicated if it makes calls simple,
so I will make the camera keep track, but that’s a personal preference. Not many changes are needed
to camera because for now it is not allowed to move; it just sends out rays over a time period.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class camera {
public:
// new: add t0 and t1
camera(
vec3 lookfrom,
vec3 lookat,
vec3 vup,
float vfov, // vfov is top to bottom in degrees
float aspect,
float aperture,
float focus_dist,
float t0,
float t1) {
time0 = t0;
time1 = t1;
lens_radius = aperture / 2;
float theta = vfov*M_PI/180;
float half_height = tan(theta/2);
float half_width = aspect * half_height;
origin = lookfrom;
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup, w));
v = cross(w, u);
lower_left_corner = origin
- half_width*focus_dist*u
- half_height*focus_dist*v
- focus_dist*w;
horizontal = 2*half_width*focus_dist*u;
vertical = 2*half_height*focus_dist*v;
}
// new: add time to construct ray
ray get_ray(float s, float t) {
vec3 rd = lens_radius*random_in_unit_disk();
vec3 offset = u * rd.x() + v * rd.y();
float time = time0 + drand48()*(time1-time0);
return ray(
origin + offset,
lower_left_corner + s*horizontal + t*vertical - origin - offset,
time);
}
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
vec3 u, v, w;
float time0, time1; // new variables for shutter open/close times
float lens_radius;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We also need a moving object. I’ll create a sphere class that has its center move linearly from
`center0` at `time0` to `center1` at `time1`. Outside that time interval it continues on, so those
times need not match up with the camera aperture open close.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class moving_sphere: public hitable {
public:
moving_sphere() {}
moving_sphere(vec3 cen0, vec3 cen1, float t0, float t1, float r, material *m)
: center0(cen0), center1(cen1), time0(t0),time1(t1), radius(r), mat_ptr(m)
{};
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
virtual bool bounding_box(float t0, float t1, aabb& box) const;
vec3 center(float time) const;
vec3 center0, center1;
float time0, time1;
float radius;
material *mat_ptr;
};
vec3 moving_sphere::center(float time) const{
return center0 + ((time - time0) / (time1 - time0))*(center1 - center0);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An alternative to making a new moving sphere class is to just make them all move and have the
stationary ones have the same begin and end point. I’m on the fence about that trade-off between
fewer classes and more efficient stationary spheres, so let your design taste guide you. The
intersection code barely needs a change: `center` just needs to become a function `center(time)`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
// replace "center" with "center(r.time())"
bool moving_sphere::hit(
const ray& r, float t_min, float t_max, hit_record& rec) const {
vec3 oc = r.origin() - center(r.time());
float a = dot(r.direction(), r.direction());
float b = dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - a*c;
if (discriminant > 0) {
float temp = (-b - sqrt(discriminant))/a;
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center(r.time())) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
temp = (-b + sqrt(discriminant))/a;
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center(r.time())) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
}
return false;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Be sure that in the materials you have the scattered rays be at the time of the incident ray.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
class lambertian : public material {
public:
lambertian(const vec3& a) : albedo(a) {}
virtual bool scatter(const ray& r_in, const hit_record& rec,
vec3& attenuation, ray& scattered) const {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
scattered = ray(rec.p, target-rec.p, r_in.time());
attenuation = albedo;
return true;
}
vec3 albedo;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If we take the example diffuse spheres from scene at the end of the last book and make them move
from their centers at `time==0`, to `center + vec3(0, 0.5*drand48(), 0)` at `time==1`, with the
camera aperture open over that frame.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
hitable *random_scene() {
int n = 50000;
hitable **list = new hitable*[n+1];
list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(checker));
int i = 1;
for (int a = -10; a < 10; a++) {
for (int b = -10; b < 10; b++) {
float choose_mat = drand48();
vec3 center(a+0.9*drand48(),0.2,b+0.9*drand48());
if ((center-vec3(4,0.2,0)).length() > 0.9) {
if (choose_mat < 0.8) { // diffuse
list[i++] = new moving_sphere(
center,
center+vec3(0, 0.5*drand48(), 0),
0.0, 1.0, 0.2,
new lambertian(
vec3(drand48()*drand48(),
drand48()*drand48(),
drand48()*drand48())
)
);
}
else if (choose_mat < 0.95) { // metal
list[i++] = new sphere(
center, 0.2,
new metal(
vec3(0.5*(1 + drand48()),
0.5*(1 + drand48()),
0.5*(1 + drand48())),
0.5*drand48()
)
);
}
else { // glass
list[i++] = new sphere(center, 0.2, new dielectric(1.5));
}
}
}
}
list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));
return new hitable_list(list,i);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
And with these viewing parameters gives:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
vec3 lookfrom(13,2,3);
vec3 lookat(0,0,0);
float dist_to_focus = 10.0;
float aperture = 0.0;
camera cam(
lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny), aperture,
dist_to_focus, 0.0, 1.0
);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
