|
39 | 39 |
|
40 | 40 | Motion Blur
|
41 | 41 | ====================================================================================================
|
42 |
| -When you decided to ray trace, you decided that visual quality was worth more than run-time. In your |
43 |
| -fuzzy reflection and defocus blur you needed multiple samples per pixel. Once you have taken a step |
44 |
| -down that road, the good news is that almost all effects can be brute-forced. Motion blur is |
45 |
| -certainly one of those. In a real camera, the shutter opens and stays open for a time interval, and |
46 |
| -the camera and objects may move during that time. Its really an average of what the camera sees over |
47 |
| -that interval that we want. |
| 42 | +When you decided to ray trace, you decided that visual quality was worth more than run-time. When |
| 43 | +rendering fuzzy reflection and defocus blur, we used multiple samples per pixel. Once you have taken |
| 44 | +a step down that road, the good news is that almost _all_ effects can be similarly brute-forced. |
| 45 | +Motion blur is certainly one of those. |
| 46 | + |
| 47 | +In a real camera, the shutter remains open for a short time interval, during which the camera and |
| 48 | +objects in the world may move. To accurately reproduce such a camera shot, we seek an average of |
| 49 | +all the instant images that the camera perceives while its shutter is open to the world. |
48 | 50 |
|
49 | 51 |
|
50 | 52 | Introduction of SpaceTime Ray Tracing
|
51 | 53 | --------------------------------------
|
52 |
| -We can get a random estimate by sending each ray at some random time when the shutter is open. As |
53 |
| -long as the objects are where they should be at that time, we can get the right average answer with |
54 |
| -a ray that is at exactly a single time. This is fundamentally why random ray tracing tends to be |
55 |
| -simple. |
56 |
| - |
57 |
| -The basic idea is to generate rays at random times while the shutter is open and intersect the model |
58 |
| -at that one time. The way it is usually done is to have the camera move and the objects move, but |
59 |
| -have each ray exist at exactly one time. This way the “engine” of the ray tracer can just make sure |
60 |
| -the objects are where they need to be for the ray, and the intersection guts don’t change much. |
| 54 | +We can get a random estimate of a single photon by sending a single ray at some random instant in |
| 55 | +time when the shutter is open. As long as the objects are where they should be at that instant, we |
| 56 | +can get an accurate measure of the light for that ray at that same instant. This is yet another |
| 57 | +example of how random (Monte Carlo) ray tracing ends up being quite simple. Brute force wins again! |
61 | 58 |
|
62 | 59 | <div class='together'>
|
63 |
| -For this we will first need to have a ray store the time it exists at: |
| 60 | +Since the “engine” of the ray tracer can just make sure the objects are where they need to be for |
| 61 | +each ray, the intersection guts don’t change much. To accomplish this, we need to store the exact |
| 62 | +time for each ray: |
64 | 63 |
|
65 | 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
66 | 65 | class ray {
|
|
82 | 81 | return orig + t*dir;
|
83 | 82 | }
|
84 | 83 |
|
85 |
| - public: |
| 84 | + private: |
86 | 85 | point3 orig;
|
87 | 86 | vec3 dir;
|
88 | 87 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
|
94 | 93 | </div>
|
95 | 94 |
|
96 | 95 |
|
| 96 | +Managing Time |
| 97 | +-------------- |
| 98 | +Before continuing, let's think about time, and how we might manage it across one or more renders. |
| 99 | +There are two aspects of shutter timing to think about: the time from one shutter opening to the |
| 100 | +next shutter opening, and how long the shutter stays open for each frame. Standard movie film used |
| 101 | +to be shot at 24 frames per second. Modern digital films can be 30 or even 60 frames per second. |
| 102 | +Each frame can have its own shutter speed, which need not be -- and typically isn't -- the maximum |
| 103 | +duration of the entire shutter period. You could have the shutter open for 1/1000th of a second per |
| 104 | +frame, or 1/60th of a second. It may surprise you to learn that for most film motion pictures, the |
| 105 | +screen is actually dark more than it is lit up with the movie frames! |
| 106 | + |
| 107 | +If you wanted to render a sequence of images, you would need to set up the camera with the |
| 108 | +appropriate shutter timings: frame-to-frame period, shutter/render duration, and the total number of |
| 109 | +frames (or total shot time). If the camera is moving and the world is static, you're good to go. |
| 110 | +However, if anything in the world is moving, you would need to add a method to `hittable` to |
| 111 | +broadcast the current shot timing to every object in the world. This method would then provide a way |
| 112 | +for all animate objects to set up their motion during that frame. |
| 113 | + |
| 114 | +This is fairly straight-forward, and definitely a fun avenue for you to experiment with if you wish. |
| 115 | +However, for our purposes right now, we're going to proceed with a drastically simplified model. We |
| 116 | +will be render only a single frame, assuming a start at time = 0 and ending at time = 1. Our first |
| 117 | +task is to modify the camera to launch rays with random times in $[0,1]$, and our second task will |
| 118 | +be the creation of an animate sphere class. |
| 119 | + |
| 120 | + |
97 | 121 | Updating the Camera to Simulate Motion Blur
|
98 | 122 | --------------------------------------------
|
99 |
| -Now we need to modify the camera to generate rays at a random time between the start time and the |
100 |
| -end time. Should the camera keep track of the time interval, or should that be up to the user of |
| 123 | +We need to modify the camera to generate rays at a random instant between the start time and the end |
| 124 | +time. Should the camera keep track of the time interval, or should that be up to the user of the |
101 | 125 | camera when a ray is created? When in doubt, I like to make constructors complicated if it makes
|
102 | 126 | calls simple, so I will make the camera keep track, but that’s a personal preference. Not many
|
103 | 127 | changes are needed to camera because for now it is not allowed to move; it just sends out rays over
|
|
130 | 154 |
|
131 | 155 | Adding Moving Spheres
|
132 | 156 | ----------------------
|
133 |
| -We also need a moving object. I’ll create a sphere class that has its center move linearly from |
134 |
| -`center0` at `time_start` to `center1` at `time_end`. Outside that time interval it continues on, so |
135 |
| -those times need not match up with the camera aperture open and close. |
| 157 | +Now to create a moving object. I’ll create a sphere class that has its center move linearly from |
| 158 | +`center0` at time 0 to `center1` at time 1. (It continues on outside that time interval, so it |
| 159 | +really can be sampled at any time.) |
136 | 160 |
|
137 | 161 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
138 | 162 | #ifndef MOVING_SPHERE_H
|
|
147 | 171 | moving_sphere() {}
|
148 | 172 | moving_sphere(point3 c0, point3 c1, double r, shared_ptr<material> m)
|
149 | 173 | : center0(c0), center1(c1), center_vec(c1 - c0), radius(r), mat_ptr(m)
|
150 |
| - { |
151 |
| - // To Be Implemented |
152 |
| - }; |
| 174 | + { }; |
153 | 175 |
|
154 | 176 | bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
|
155 |
| - // To Be Implemented |
156 |
| - } |
157 |
| - |
158 |
| - bool bounding_box(aabb& output_box) const override { |
159 |
| - output_box = bbox; |
160 |
| - return true; |
| 177 | + // Implementation below. |
161 | 178 | }
|
162 | 179 |
|
163 | 180 | point3 center(double time) const {
|
|
171 | 188 | vec3 center_vec;
|
172 | 189 | double radius;
|
173 | 190 | shared_ptr<material> mat_ptr;
|
174 |
| - aabb bbox; |
175 | 191 | };
|
176 | 192 |
|
177 | 193 | #endif
|
|
226 | 242 | [Listing [moving-sphere-hit]: <kbd>[moving_sphere.h]</kbd> Moving sphere hit function]
|
227 | 243 | </div>
|
228 | 244 |
|
229 |
| -We need to implement the `interval::contains()` method mentioned above: |
| 245 | +We need to implement the new `interval::contains()` method mentioned above: |
230 | 246 |
|
231 | 247 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
232 | 248 | class interval {
|
|
305 | 321 | ----------------------------
|
306 | 322 | <div class='together'>
|
307 | 323 | The code below takes the example diffuse spheres from the scene at the end of the last book, and
|
308 |
| -makes them move during the image render. (Think of a camera with shutter opening at time 0 and |
309 |
| -closing at time 1.) Each sphere moves from its center $\mathbf{C}$ at time $t=0$ to $\mathbf{C} + |
310 |
| -(0, r/2, 0)$ at time $t=1$, where $r$ is a random number in $[0,1)$: |
| 324 | +makes them move during the image render. Each sphere moves from its center $\mathbf{C}$ at time |
| 325 | +$t=0$ to $\mathbf{C} + (0, r/2, 0)$ at time $t=1$: |
311 | 326 |
|
312 | 327 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
313 | 328 | ...
|
|
725 | 740 | </div>
|
726 | 741 |
|
727 | 742 | <div class='together'>
|
728 |
| -For `moving sphere`, we can take the box of the sphere at $t_0$, and the box of the sphere at $t_1$, |
| 743 | +For `moving sphere`, we want the bounds of all places the moving sphere could occupy while it moves. |
| 744 | +To do this, we can take the box of the sphere at time = 0, and the box of the sphere at time = 1, |
729 | 745 | and compute the box of those two boxes. We'll add a new `aabb` constructor that takes two boxes as
|
730 | 746 | input below.
|
731 | 747 |
|
|
739 | 755 | class moving_sphere : public hittable {
|
740 | 756 | public:
|
741 | 757 | ...
|
| 758 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
742 | 759 | moving_sphere(point3 c0, point3 c1, double r, shared_ptr<material> m)
|
743 | 760 | : center0(c0), center1(c1), center_vec(c1 - c0), radius(r), mat_ptr(m)
|
744 | 761 | {
|
|
747 | 764 | const aabb box1(center1 - rvec, center1 + rvec);
|
748 | 765 | bbox = aabb(box0, box1);
|
749 | 766 | };
|
| 767 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
750 | 768 |
|
751 | 769 | ...
|
752 | 770 |
|
| 771 | + |
753 | 772 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
|
754 | 773 | bool bounding_box(aabb& output_box) const override {
|
755 | 774 | output_box = bbox;
|
756 | 775 | return true;
|
757 | 776 | }
|
758 | 777 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
|
| 778 | + |
759 | 779 | ...
|
| 780 | + |
| 781 | + public: |
| 782 | + point3 center0, center1; |
| 783 | + vec3 center_vec; |
| 784 | + double radius; |
| 785 | + shared_ptr<material> mat_ptr; |
| 786 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight |
| 787 | + aabb bbox; |
| 788 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
760 | 789 | };
|
761 | 790 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
762 | 791 | [Listing [moving-sphere-bbox]: <kbd>[moving_sphere.h]</kbd> Moving sphere with bounding box]
|
|
1241 | 1270 |
|
1242 | 1271 | scene_desc.cam.vup = vec3(0,1,0);
|
1243 | 1272 | scene_desc.cam.focus_dist = 10.0;
|
1244 |
| - scene_desc.cam.time_start = 0; |
1245 |
| - scene_desc.cam.time_end = 1; |
1246 | 1273 |
|
1247 | 1274 | switch (0) {
|
1248 | 1275 | case 1: random_spheres(scene_desc); break;
|
|
2350 | 2377 |
|
2351 | 2378 | scene_desc.cam.vup = vec3(0,1,0);
|
2352 | 2379 | scene_desc.cam.focus_dist = 10.0;
|
2353 |
| - scene_desc.cam.time_start = 0; |
2354 |
| - scene_desc.cam.time_end = 1; |
2355 | 2380 |
|
2356 | 2381 | ...
|
2357 | 2382 | }
|
|
3275 | 3300 | auto center1 = point3(400, 400, 200);
|
3276 | 3301 | auto center2 = center1 + vec3(30,0,0);
|
3277 | 3302 | auto moving_sphere_material = make_shared<lambertian>(color(0.7, 0.3, 0.1));
|
3278 |
| - world.add(make_shared<moving_sphere>(center1, center2, 50, moving_sphere_material, 0, 1)); |
| 3303 | + world.add(make_shared<moving_sphere>(center1, center2, 50, moving_sphere_material)); |
3279 | 3304 |
|
3280 | 3305 | world.add(make_shared<sphere>(point3(260, 150, 45), 50, make_shared<dielectric>(1.5)));
|
3281 | 3306 | world.add(make_shared<sphere>(
|
|
0 commit comments