Skip to content

Commit 6894a76

Browse files
committed
Added world space and object space explanation
1 parent ed119af commit 6894a76

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
@@ -2975,12 +2975,14 @@
29752975

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

29852987
![Figure [ray-box]: Ray-box intersection with moved ray vs box](../images/fig-2.08-ray-box.jpg)
29862988

@@ -2990,41 +2992,86 @@
29902992
Instance Translation
29912993
---------------------
29922994
<div class='together'>
2993-
Whether you think of this as a move or a change of coordinates is up to you. The code for this, to
2994-
move any underlying hittable is a _translate_ instance.
2995+
Whether you think of this as a move or a change of coordinates is up to you. The way to reason about
2996+
this is to think of moving the incident ray backwards the offset amount, determining if an
2997+
intersection occurs, and then moving that intersection point forward the offset amount.
2998+
2999+
If we have an intersection along the offset ray, then we know that the incident ray will hit the
3000+
translated object. However, our offset ray is not the original ray, so if we just take the
3001+
intersection we found along the offset ray then our intersection will actually be wrong by the
3002+
offset amount, so we have to shift the intersection point back onto the original ray by the offset
3003+
amount. Let's add the code to make this happen.
29953004

29963005
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
29973006
class translate : public hittable {
29983007
public:
3008+
...
3009+
3010+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3011+
// Move the ray backwards by the offset
3012+
ray offset_r(r.origin() - offset, r.direction(), r.time());
3013+
3014+
// Determine where (if any) an intersection occurs along the offset ray
3015+
if (!object->hit(offset_r, ray_t, rec))
3016+
return false;
3017+
3018+
// Move the intersection point forwards by the offset
3019+
rec.p += offset;
3020+
rec.set_face_normal(offset_r, rec.normal);
3021+
3022+
return true;
3023+
}
3024+
3025+
...
3026+
};
3027+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3028+
[Listing [translate-hit]: <kbd>[hittable.h]</kbd> Hittable translation hit function]
3029+
3030+
... and then flesh out the rest of the `translate` class:
3031+
3032+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3033+
class translate : public hittable {
3034+
public:
3035+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
29993036
translate(shared_ptr<hittable> p, const vec3& displacement)
30003037
: object(p), offset(displacement)
30013038
{
30023039
bbox = object->bounding_box() + offset;
30033040
}
3041+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
30043042

30053043
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3006-
ray moved_r(r.origin() - offset, r.direction(), r.time());
3007-
if (!object->hit(moved_r, ray_t, rec))
3044+
// Move the ray backwards by the offset
3045+
ray offset_r(r.origin() - offset, r.direction(), r.time());
3046+
3047+
// Determine where (if any) an intersection occurs along the offset ray
3048+
if (!object->hit(offset_r, ray_t, rec))
30083049
return false;
30093050

3051+
// Move the intersection point forwards by the offset
30103052
rec.p += offset;
3011-
rec.set_face_normal(moved_r, rec.normal);
3053+
rec.set_face_normal(offset_r, rec.normal);
30123054

30133055
return true;
30143056
}
30153057

3058+
3059+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
30163060
aabb bounding_box() const override { return bbox; }
30173061

30183062
public:
30193063
shared_ptr<hittable> object;
30203064
vec3 offset;
30213065
aabb bbox;
3066+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
30223067
};
30233068
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30243069
[Listing [translate-class]: <kbd>[hittable.h]</kbd> Hittable translation class]
30253070
</div>
30263071

3027-
The expression `object->bounding_box() + offset` above requires some additional support.
3072+
We need to remember to offset the bounding box, otherwise the incident ray might be looking in the
3073+
wrong place and trivially reject the intersection. The expression `object->bounding_box() + offset`
3074+
above requires some additional support.
30283075

30293076
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
30303077
class aabb {
@@ -3101,24 +3148,112 @@
31013148
$$ x' = \cos(\theta) \cdot x + \sin(\theta) \cdot z $$
31023149
$$ z' = -\sin(\theta) \cdot x + \cos(\theta) \cdot z $$
31033150

3104-
And about the x-axis:
3151+
And if we want to rotate about the x-axis:
31053152

31063153
$$ y' = \cos(\theta) \cdot y - \sin(\theta) \cdot z $$
31073154
$$ z' = \sin(\theta) \cdot y + \cos(\theta) \cdot z $$
31083155
</div>
31093156

3110-
Unlike the situation with translations, the surface normal vector also changes, so we need to
3111-
transform directions too if we get a hit. Fortunately for rotations, the same formulas apply. If you
3112-
add scales, things get more complicated. See the web page https://in1weekend.blogspot.com/ for links
3113-
to that.
3157+
For a simple operation like a translation, thinking of the operation as a simple movement of the
3158+
initial ray is a fine way to reason about what's going on. But, for a more complex operation like a
3159+
rotation, it can be easy to accidentally get your terms crossed (or forget a negative sign), so it's
3160+
better to consider a rotation as a change of coordinates. The pseudocode for the `translate::hit`
3161+
function above describes the function in terms of _moving_:
3162+
3163+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3164+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3165+
// Move the ray backwards by the offset
3166+
3167+
// Determine where (if any) an intersection occurs along the offset ray
3168+
3169+
// Move the intersection point forwards by the offset
3170+
}
3171+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3172+
[Listing [translate-hit-move]: <kbd>[hittable.h]</kbd> The moving translate hit code]
3173+
3174+
But this can also be thought of in terms of a _changing of coordinates_:
3175+
3176+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3177+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3178+
// Change the ray from world space to object space
3179+
3180+
// Determine where (if any) an intersection occurs in object space
3181+
3182+
// Change the intersection point from object space to world space
3183+
}
3184+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3185+
[Listing [translate-hit-coord]: <kbd>[hittable.h]</kbd> The change of coordinates translate hit code]
31143186

31153187
<div class='together'>
3188+
Rotating an object will not only change the point of intersection, but will also change the surface
3189+
normal vector, which will change the direction of reflections and refractions. So we need to change the
3190+
normal as well. Fortuneatly, we can just treat the normal like a vector, and so use the same
3191+
formulas as above.
3192+
31163193
For a y-rotation class we have:
31173194

31183195
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31193196
class rotate_y : public hittable {
31203197
public:
3121-
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
3198+
...
3199+
3200+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3201+
// Change the ray from world space to object space
3202+
auto origin = r.origin();
3203+
auto direction = r.direction();
3204+
3205+
origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
3206+
origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
3207+
3208+
direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
3209+
direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
3210+
3211+
ray rotated_r(origin, direction, r.time());
3212+
3213+
// Determine where (if any) an intersection occurs in object space
3214+
if (!object->hit(rotated_r, ray_t, rec))
3215+
return false;
3216+
3217+
// Change the intersection point from object space to world space
3218+
auto p = rec.p;
3219+
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
3220+
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
3221+
3222+
// Change the normal from object space to world space
3223+
auto normal = rec.normal
3224+
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
3225+
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
3226+
3227+
rec.p = p;
3228+
rec.set_face_normal(rotated_r, normal);
3229+
3230+
return true;
3231+
}
3232+
3233+
...
3234+
};
3235+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3236+
[Listing [rot-y-hit]: <kbd>[hittable.h]</kbd> Hittable rotate-Y hit function]
3237+
3238+
</div>
3239+
The code for solving from world space to object space might at first appear incorrect because we are
3240+
rotating the opposite way. The negative sign of the `sin_theta` terms are flipped. While we want to
3241+
rotate around the y-axis by $\theta$, the formula used has our world space to object space solving
3242+
for $-\theta$. What we have above is actually correct. It's the exact same thing that we did above
3243+
for translation. Changing from world space to object space required us to change by
3244+
$-\text{offset}$. Changing from world space to object space for rotation requires us to rotate by
3245+
$-\theta$. And, just as before with translation, where the change from object space to world space
3246+
required a change of $+\text{offset}$, the change in rotation from object space to world space
3247+
requires $+\theta$.
3248+
3249+
With all of that said, we can now flesh out the rest of the class:
3250+
3251+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3252+
class rotate_y : public hittable {
3253+
public:
3254+
3255+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
3256+
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
31223257
auto radians = degrees_to_radians(angle);
31233258
sin_theta = sin(radians);
31243259
cos_theta = cos(radians);
@@ -3149,48 +3284,25 @@
31493284

31503285
bbox = aabb(min, max);
31513286
}
3287+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31523288

31533289
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3154-
auto origin = r.origin();
3155-
auto direction = r.direction();
3156-
3157-
origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
3158-
origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
3159-
3160-
direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
3161-
direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
3162-
3163-
ray rotated_r(origin, direction, r.time());
3164-
3165-
if (!object->hit(rotated_r, ray_t, rec))
3166-
return false;
3167-
3168-
auto p = rec.p;
3169-
auto normal = rec.normal;
3170-
3171-
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
3172-
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
3173-
3174-
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
3175-
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
3176-
3177-
rec.p = p;
3178-
rec.set_face_normal(rotated_r, normal);
3179-
3180-
return true;
3290+
...
31813291
}
31823292

3293+
3294+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
31833295
aabb bounding_box() const override { return bbox; }
31843296

31853297
public:
31863298
shared_ptr<hittable> object;
31873299
double sin_theta;
31883300
double cos_theta;
31893301
aabb bbox;
3302+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31903303
};
31913304
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31923305
[Listing [rot-y]: <kbd>[hittable.h]</kbd> Hittable rotate-Y class]
3193-
</div>
31943306

31953307
<div class='together'>
31963308
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
@@ -63,12 +63,16 @@ class translate : public hittable {
6363
}
6464

6565
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
66-
ray moved_r(r.origin() - offset, r.direction(), r.time());
67-
if (!object->hit(moved_r, ray_t, rec))
66+
// Move the ray backwards by the offset
67+
ray offset_r(r.origin() - offset, r.direction(), r.time());
68+
69+
// Determine where (if any) an intersection occurs along the offset ray
70+
if (!object->hit(offset_r, ray_t, rec))
6871
return false;
6972

73+
// Move the intersection point forwards by the offset
7074
rec.p += offset;
71-
rec.set_face_normal(moved_r, rec.normal);
75+
rec.set_face_normal(offset_r, rec.normal);
7276

7377
return true;
7478
}
@@ -117,6 +121,7 @@ class rotate_y : public hittable {
117121
}
118122

119123
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
124+
// Change the ray from world space to object space
120125
auto origin = r.origin();
121126
auto direction = r.direction();
122127

@@ -128,15 +133,17 @@ class rotate_y : public hittable {
128133

129134
ray rotated_r(origin, direction, r.time());
130135

136+
// Determine where (if any) an intersection occurs in object space
131137
if (!object->hit(rotated_r, ray_t, rec))
132138
return false;
133139

140+
// Change the intersection point from object space to world space
134141
auto p = rec.p;
135-
auto normal = rec.normal;
136-
137142
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
138143
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
139144

145+
// Change the normal from object space to world space
146+
auto normal = rec.normal;
140147
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
141148
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
142149

0 commit comments

Comments
 (0)