Skip to content

Commit 3cec7d6

Browse files
committed
Added world space and object space explanation
1 parent df5807a commit 3cec7d6

File tree

3 files changed

+182
-56
lines changed

3 files changed

+182
-56
lines changed

books/RayTracingTheNextWeek.html

Lines changed: 158 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,12 +2778,14 @@
27782778

27792779
<div class='together'>
27802780
Now that we have boxes, we need to rotate them a bit to have them match the _real_ Cornell box. In
2781-
ray tracing, this is usually done with an _instance_. An instance is a geometric primitive that has
2782-
been moved or rotated somehow. This is especially easy in ray tracing because we don’t move
2783-
anything; instead we move the rays in the opposite direction. For example, consider a _translation_
2784-
(often called a _move_). We could take the pink box at the origin and add 2 to all its x components,
2785-
or (as we almost always do in ray tracing) leave the box where it is, but in its hit routine
2786-
subtract 2 off the x-component of the ray origin.
2781+
ray tracing, this is usually done with an _instance_. An instance is a copy of a geometric
2782+
primitive that has been placed into the scene. This instance is entirely independent of the other
2783+
copies of the primitive and can be moved or rotated around. In this case our geometric primitive is
2784+
our box hittable, and we want to rotate it somehow. This is especially easy in ray tracing because
2785+
we don’t move anything; instead we move the rays in the opposite direction. For example, consider a
2786+
_translation_ (often called a _move_). We could take the pink box at the origin and add 2 to all its
2787+
x components, or (as we almost always do in ray tracing) leave the box where it is, but in its hit
2788+
routine subtract 2 off the x-component of the ray origin.
27872789

27882790
![Figure [ray-box]: Ray-box intersection with moved ray vs box](../images/fig-2.06-ray-box.jpg)
27892791

@@ -2793,41 +2795,86 @@
27932795
Instance Translation
27942796
---------------------
27952797
<div class='together'>
2796-
Whether you think of this as a move or a change of coordinates is up to you. The code for this, to
2797-
move any underlying hittable is a _translate_ instance.
2798+
Whether you think of this as a move or a change of coordinates is up to you. The way to reason about
2799+
this is to think of moving the incident ray backwards the offset amount, determining if an
2800+
intersection occurs, and then moving that intersection point forward the offset amount.
2801+
2802+
If we have an intersection along the offset ray, then we know that the incident ray will hit the
2803+
translated object. However, our offset ray is not the original ray, so if we just take the
2804+
intersection we found along the offset ray then our intersection will actually be wrong by the
2805+
offset amount, so we have to shift the intersection point back onto the original ray by the offset
2806+
amount. Let's add the code to make this happen.
27982807

27992808
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
28002809
class translate : public hittable {
28012810
public:
2811+
...
2812+
2813+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2814+
// Move the ray backwards by the offset
2815+
ray offset_r(r.origin() - offset, r.direction(), r.time());
2816+
2817+
// Determine where (if any) an intersection occurs along the offset ray
2818+
if (!object->hit(offset_r, ray_t, rec))
2819+
return false;
2820+
2821+
// Move the intersection point forwards by the offset
2822+
rec.p += offset;
2823+
rec.set_face_normal(offset_r, rec.normal);
2824+
2825+
return true;
2826+
}
2827+
2828+
...
2829+
};
2830+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2831+
[Listing [translate-hit]: <kbd>[hittable.h]</kbd> Hittable translation hit function]
2832+
2833+
... and then flesh out the rest of the `translate` class:
2834+
2835+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2836+
class translate : public hittable {
2837+
public:
2838+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
28022839
translate(shared_ptr<hittable> p, const vec3& displacement)
28032840
: object(p), offset(displacement)
28042841
{
28052842
bbox = object->bounding_box() + offset;
28062843
}
2844+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
28072845

28082846
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2809-
ray moved_r(r.origin() - offset, r.direction(), r.time());
2810-
if (!object->hit(moved_r, ray_t, rec))
2847+
// Move the ray backwards by the offset
2848+
ray offset_r(r.origin() - offset, r.direction(), r.time());
2849+
2850+
// Determine where (if any) an intersection occurs along the offset ray
2851+
if (!object->hit(offset_r, ray_t, rec))
28112852
return false;
28122853

2854+
// Move the intersection point forwards by the offset
28132855
rec.p += offset;
2814-
rec.set_face_normal(moved_r, rec.normal);
2856+
rec.set_face_normal(offset_r, rec.normal);
28152857

28162858
return true;
28172859
}
28182860

2861+
2862+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
28192863
aabb bounding_box() const override { return bbox; }
28202864

28212865
public:
28222866
shared_ptr<hittable> object;
28232867
vec3 offset;
28242868
aabb bbox;
2869+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
28252870
};
28262871
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28272872
[Listing [translate-class]: <kbd>[hittable.h]</kbd> Hittable translation class]
28282873
</div>
28292874

2830-
The expression `object->bounding_box() + offset` above requires some additional support.
2875+
We need to remember to offset the bounding box, otherwise the incident ray might be looking in the
2876+
wrong place and trivially reject the intersection. The expression `object->bounding_box() + offset`
2877+
above requires some additional support.
28312878

28322879
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
28332880
class aabb {
@@ -2904,24 +2951,112 @@
29042951
$$ x' = \cos(\theta) \cdot x + \sin(\theta) \cdot z $$
29052952
$$ z' = -\sin(\theta) \cdot x + \cos(\theta) \cdot z $$
29062953

2907-
And about the x-axis:
2954+
And if we want to rotate about the x-axis:
29082955

29092956
$$ y' = \cos(\theta) \cdot y - \sin(\theta) \cdot z $$
29102957
$$ z' = \sin(\theta) \cdot y + \cos(\theta) \cdot z $$
29112958
</div>
29122959

2913-
Unlike the situation with translations, the surface normal vector also changes, so we need to
2914-
transform directions too if we get a hit. Fortunately for rotations, the same formulas apply. If you
2915-
add scales, things get more complicated. See the web page https://in1weekend.blogspot.com/ for links
2916-
to that.
2960+
For a simple operation like a translation, thinking of the operation as a simple movement of the
2961+
initial ray is a fine way to reason about what's going on. But, for a more complex operation like a
2962+
rotation, it can be easy to accidentally get your terms crossed (or forget a negative sign), so it's
2963+
better to consider a rotation as a change of coordinates. The pseudocode for the `translate::hit`
2964+
function above describes the function in terms of _moving_:
2965+
2966+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2967+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2968+
// Move the ray backwards by the offset
2969+
2970+
// Determine where (if any) an intersection occurs along the offset ray
2971+
2972+
// Move the intersection point forwards by the offset
2973+
}
2974+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2975+
[Listing [translate-hit-move]: <kbd>[hittable.h]</kbd> The moving translate hit code]
2976+
2977+
But this can also be thought of in terms of a _changing of coordinates_:
2978+
2979+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2980+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2981+
// Change the ray from world space to object space
2982+
2983+
// Determine where (if any) an intersection occurs in object space
2984+
2985+
// Change the intersection point from object space to world space
2986+
}
2987+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2988+
[Listing [translate-hit-coord]: <kbd>[hittable.h]</kbd> The change of coordinates translate hit code]
29172989

29182990
<div class='together'>
2991+
Rotating an object will not only change the point of intersection, but will also change the surface
2992+
normal vector, which will change the direction of reflections and refractions. So we need to change the
2993+
normal as well. Fortuneatly, we can just treat the normal like a vector, and so use the same
2994+
formulas as above.
2995+
29192996
For a y-rotation class we have:
29202997

29212998
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
29222999
class rotate_y : public hittable {
29233000
public:
2924-
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
3001+
...
3002+
3003+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3004+
// Change the ray from world space to object space
3005+
auto origin = r.origin();
3006+
auto direction = r.direction();
3007+
3008+
origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
3009+
origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
3010+
3011+
direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
3012+
direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
3013+
3014+
ray rotated_r(origin, direction, r.time());
3015+
3016+
// Determine where (if any) an intersection occurs in object space
3017+
if (!object->hit(rotated_r, ray_t, rec))
3018+
return false;
3019+
3020+
// Change the intersection point from object space to world space
3021+
auto p = rec.p;
3022+
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
3023+
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
3024+
3025+
// Change the normal from object space to world space
3026+
auto normal = rec.normal
3027+
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
3028+
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
3029+
3030+
rec.p = p;
3031+
rec.set_face_normal(rotated_r, normal);
3032+
3033+
return true;
3034+
}
3035+
3036+
...
3037+
};
3038+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3039+
[Listing [rot-y-hit]: <kbd>[hittable.h]</kbd> Hittable rotate-Y hit function]
3040+
3041+
</div>
3042+
The code for solving from world space to object space might at first appear incorrect because we are
3043+
rotating the opposite way. The negative sign of the `sin_theta` terms are flipped. While we want to
3044+
rotate around the y-axis by $\theta$, the formula used has our world space to object space solving
3045+
for $-\theta$. What we have above is actually correct. It's the exact same thing that we did above
3046+
for translation. Changing from world space to object space required us to change by
3047+
$-\text{offset}$. Changing from world space to object space for rotation requires us to rotate by
3048+
$-\theta$. And, just as before with translation, where the change from object space to world space
3049+
required a change of $+\text{offset}$, the change in rotation from object space to world space
3050+
requires $+\theta$.
3051+
3052+
With all of that said, we can now flesh out the rest of the class:
3053+
3054+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3055+
class rotate_y : public hittable {
3056+
public:
3057+
3058+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
3059+
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
29253060
auto radians = degrees_to_radians(angle);
29263061
sin_theta = sin(radians);
29273062
cos_theta = cos(radians);
@@ -2952,48 +3087,25 @@
29523087

29533088
bbox = aabb(min, max);
29543089
}
3090+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
29553091

29563092
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2957-
auto origin = r.origin();
2958-
auto direction = r.direction();
2959-
2960-
origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
2961-
origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
2962-
2963-
direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
2964-
direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
2965-
2966-
ray rotated_r(origin, direction, r.time());
2967-
2968-
if (!object->hit(rotated_r, ray_t, rec))
2969-
return false;
2970-
2971-
auto p = rec.p;
2972-
auto normal = rec.normal;
2973-
2974-
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
2975-
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
2976-
2977-
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
2978-
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
2979-
2980-
rec.p = p;
2981-
rec.set_face_normal(rotated_r, normal);
2982-
2983-
return true;
3093+
...
29843094
}
29853095

3096+
3097+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
29863098
aabb bounding_box() const override { return bbox; }
29873099

29883100
public:
29893101
shared_ptr<hittable> object;
29903102
double sin_theta;
29913103
double cos_theta;
29923104
aabb bbox;
3105+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
29933106
};
29943107
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29953108
[Listing [rot-y]: <kbd>[hittable.h]</kbd> Hittable rotate-Y class]
2996-
</div>
29973109

29983110
<div class='together'>
29993111
And the changes to Cornell are:

src/TheNextWeek/hittable.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ class translate : public hittable {
5555
}
5656

5757
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
58-
ray moved_r(r.origin() - offset, r.direction(), r.time());
59-
if (!object->hit(moved_r, ray_t, rec))
58+
// Move the ray backwards by the offset
59+
ray offset_r(r.origin() - offset, r.direction(), r.time());
60+
61+
// Determine where (if any) an intersection occurs along the offset ray
62+
if (!object->hit(offset_r, ray_t, rec))
6063
return false;
6164

65+
// Move the intersection point forwards by the offset
6266
rec.p += offset;
63-
rec.set_face_normal(moved_r, rec.normal);
67+
rec.set_face_normal(offset_r, rec.normal);
6468

6569
return true;
6670
}
@@ -109,6 +113,7 @@ class rotate_y : public hittable {
109113
}
110114

111115
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
116+
// Change the ray from world space to object space
112117
auto origin = r.origin();
113118
auto direction = r.direction();
114119

@@ -120,15 +125,17 @@ class rotate_y : public hittable {
120125

121126
ray rotated_r(origin, direction, r.time());
122127

128+
// Determine where (if any) an intersection occurs in object space
123129
if (!object->hit(rotated_r, ray_t, rec))
124130
return false;
125131

132+
// Change the intersection point from object space to world space
126133
auto p = rec.p;
127-
auto normal = rec.normal;
128-
129134
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
130135
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
131136

137+
// Change the normal from object space to world space
138+
auto normal = rec.normal;
132139
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
133140
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
134141

src/TheRestOfYourLife/hittable.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,16 @@ class translate : public hittable {
8282
}
8383

8484
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
85-
ray moved_r(r.origin() - offset, r.direction(), r.time());
86-
if (!object->hit(moved_r, ray_t, rec))
85+
// Move the ray backwards by the offset
86+
ray offset_r(r.origin() - offset, r.direction(), r.time());
87+
88+
// Determine where (if any) an intersection occurs along the offset ray
89+
if (!object->hit(offset_r, ray_t, rec))
8790
return false;
8891

92+
// Move the intersection point forwards by the offset
8993
rec.p += offset;
90-
rec.set_face_normal(moved_r, rec.normal);
94+
rec.set_face_normal(offset_r, rec.normal);
9195

9296
return true;
9397
}
@@ -136,6 +140,7 @@ class rotate_y : public hittable {
136140
}
137141

138142
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
143+
// Change the ray from world space to object space
139144
auto origin = r.origin();
140145
auto direction = r.direction();
141146

@@ -147,15 +152,17 @@ class rotate_y : public hittable {
147152

148153
ray rotated_r(origin, direction, r.time());
149154

155+
// Determine where (if any) an intersection occurs in object space
150156
if (!object->hit(rotated_r, ray_t, rec))
151157
return false;
152158

159+
// Change the intersection point from object space to world space
153160
auto p = rec.p;
154-
auto normal = rec.normal;
155-
156161
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
157162
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
158163

164+
// Change the normal from object space to world space
165+
auto normal = rec.normal;
159166
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
160167
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
161168

0 commit comments

Comments
 (0)