Skip to content

Commit 62ce2e6

Browse files
authored
Merge pull request #913 from RayTracing/tdb/instance_clarity
Added world space and object space explanation
2 parents 40814b7 + d5525d2 commit 62ce2e6

File tree

3 files changed

+167
-59
lines changed

3 files changed

+167
-59
lines changed

books/RayTracingTheNextWeek.html

Lines changed: 143 additions & 47 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. In this case, our geometric primitive is our
2981+
hittable `box` object, and we want to rotate it. This is especially easy in ray tracing because we
2982+
don’t actually need to move objects in the scene; instead we move the rays in the opposite
2983+
direction. For example, consider a _translation_ (often called a _move_). We could take the pink box
2984+
at the origin and add two to all its x components, or (as we almost always do in ray tracing) leave
2985+
the box where it is, but in its hit routine subtract two 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,83 @@
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+
We need to move the intersection point forward the offset amount so that the intersection is
3000+
actually in the path of the incident ray. If we forgot to move the intersection point forward then
3001+
the intersection would be in the path of the offset ray, which isn't correct. Let's add the code to
3002+
make this happen.
29953003

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

30053041
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))
3042+
// Move the ray backwards by the offset
3043+
ray offset_r(r.origin() - offset, r.direction(), r.time());
3044+
3045+
// Determine where (if any) an intersection occurs along the offset ray
3046+
if (!object->hit(offset_r, ray_t, rec))
30083047
return false;
30093048

3049+
// Move the intersection point forwards by the offset
30103050
rec.p += offset;
3011-
rec.set_face_normal(moved_r, rec.normal);
30123051

30133052
return true;
30143053
}
30153054

3055+
3056+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
30163057
aabb bounding_box() const override { return bbox; }
30173058

30183059
public:
30193060
shared_ptr<hittable> object;
30203061
vec3 offset;
30213062
aabb bbox;
3063+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
30223064
};
30233065
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30243066
[Listing [translate-class]: <kbd>[hittable.h]</kbd> Hittable translation class]
30253067
</div>
30263068

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

30293073
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
30303074
class aabb {
@@ -3101,24 +3145,99 @@
31013145
$$ x' = \cos(\theta) \cdot x + \sin(\theta) \cdot z $$
31023146
$$ z' = -\sin(\theta) \cdot x + \cos(\theta) \cdot z $$
31033147

3104-
And about the x-axis:
3148+
And if we want to rotate about the x-axis:
31053149

31063150
$$ y' = \cos(\theta) \cdot y - \sin(\theta) \cdot z $$
31073151
$$ z' = \sin(\theta) \cdot y + \cos(\theta) \cdot z $$
31083152
</div>
31093153

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.
3154+
Thinking of translation as a simple movement of the initial ray is a fine way to reason about what's
3155+
going on. But, for a more complex operation like a rotation, it can be easy to accidentally get your
3156+
terms crossed (or forget a negative sign), so it's better to consider a rotation as a change of
3157+
coordinates.
3158+
3159+
The pseudocode for the `translate::hit` function above describes the function in terms of _moving_:
3160+
3161+
1. Move the ray backwards by the offset
3162+
2. Determine whether an intersection exists along the offset ray (and if so, where)
3163+
3. Move the intersection point forwards by the offset
3164+
3165+
But this can also be thought of in terms of a _changing of coordinates_:
3166+
3167+
1. Change the ray from world space to object space
3168+
2. Determine whether an intersection exists in object space (and if so, where)
3169+
3. Change the intersection point from object space to world space
3170+
31143171

31153172
<div class='together'>
3116-
For a y-rotation class we have:
3173+
Rotating an object will not only change the point of intersection, but will also change the surface
3174+
normal vector, which will change the direction of reflections and refractions. So we need to change
3175+
the normal as well. Fortunately, the normal will rotate similarly to a vector, so we can use the
3176+
same formulas as above. While normals and vectors may appear identical for an object undergoing
3177+
rotation and translation, an object undergoing scaling requires special attention to keep the
3178+
normals orthogonal to the surface. We won't cover that here, but you should research surface normal
3179+
transformations if you implement scaling.
3180+
3181+
We need to start by changing the ray from world space to object space, which for rotation means
3182+
rotating by $-\theta$.
3183+
3184+
$$ x' = \cos(\theta) \cdot x - \sin(\theta) \cdot z $$
3185+
$$ z' = \sin(\theta) \cdot x + \cos(\theta) \cdot z $$
3186+
3187+
We can now create a class for y-rotation:
31173188

31183189
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31193190
class rotate_y : public hittable {
31203191
public:
3121-
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
3192+
...
3193+
3194+
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
3195+
// Change the ray from world space to object space
3196+
auto origin = r.origin();
3197+
auto direction = r.direction();
3198+
3199+
origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
3200+
origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
3201+
3202+
direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
3203+
direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
3204+
3205+
ray rotated_r(origin, direction, r.time());
3206+
3207+
// Determine where (if any) an intersection occurs in object space
3208+
if (!object->hit(rotated_r, ray_t, rec))
3209+
return false;
3210+
3211+
// Change the intersection point from object space to world space
3212+
auto p = rec.p;
3213+
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
3214+
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
3215+
3216+
// Change the normal from object space to world space
3217+
auto normal = rec.normal
3218+
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
3219+
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
3220+
3221+
rec.p = p;
3222+
rec.normal = normal;
3223+
3224+
return true;
3225+
}
3226+
3227+
...
3228+
};
3229+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3230+
[Listing [rot-y-hit]: <kbd>[hittable.h]</kbd> Hittable rotate-Y hit function]
3231+
3232+
</div>
3233+
... and now for the rest of the class:
3234+
3235+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3236+
class rotate_y : public hittable {
3237+
public:
3238+
3239+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
3240+
rotate_y(shared_ptr<hittable> p, double angle) : object(p) {
31223241
auto radians = degrees_to_radians(angle);
31233242
sin_theta = sin(radians);
31243243
cos_theta = cos(radians);
@@ -3149,48 +3268,25 @@
31493268

31503269
bbox = aabb(min, max);
31513270
}
3271+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31523272

31533273
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;
3274+
...
31813275
}
31823276

3277+
3278+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
31833279
aabb bounding_box() const override { return bbox; }
31843280

31853281
public:
31863282
shared_ptr<hittable> object;
31873283
double sin_theta;
31883284
double cos_theta;
31893285
aabb bbox;
3286+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
31903287
};
31913288
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31923289
[Listing [rot-y]: <kbd>[hittable.h]</kbd> Hittable rotate-Y class]
3193-
</div>
31943290

31953291
<div class='together'>
31963292
And the changes to Cornell are:

src/TheNextWeek/hittable.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ 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 whether an intersection exists along the offset ray (and if so, where)
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);
6467

6568
return true;
6669
}
@@ -109,6 +112,7 @@ class rotate_y : public hittable {
109112
}
110113

111114
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
115+
// Change the ray from world space to object space
112116
auto origin = r.origin();
113117
auto direction = r.direction();
114118

@@ -120,20 +124,22 @@ class rotate_y : public hittable {
120124

121125
ray rotated_r(origin, direction, r.time());
122126

127+
// Determine whether an intersection exists in object space (and if so, where)
123128
if (!object->hit(rotated_r, ray_t, rec))
124129
return false;
125130

131+
// Change the intersection point from object space to world space
126132
auto p = rec.p;
127-
auto normal = rec.normal;
128-
129133
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
130134
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
131135

136+
// Change the normal from object space to world space
137+
auto normal = rec.normal;
132138
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
133139
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
134140

135141
rec.p = p;
136-
rec.set_face_normal(rotated_r, normal);
142+
rec.normal = normal;
137143

138144
return true;
139145
}

src/TheRestOfYourLife/hittable.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,15 @@ 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 whether an intersection exists along the offset ray (and if so, where)
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);
7275

7376
return true;
7477
}
@@ -117,6 +120,7 @@ class rotate_y : public hittable {
117120
}
118121

119122
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
123+
// Change the ray from world space to object space
120124
auto origin = r.origin();
121125
auto direction = r.direction();
122126

@@ -128,20 +132,22 @@ class rotate_y : public hittable {
128132

129133
ray rotated_r(origin, direction, r.time());
130134

135+
// Determine whether an intersection exists in object space (and if so, where)
131136
if (!object->hit(rotated_r, ray_t, rec))
132137
return false;
133138

139+
// Change the intersection point from object space to world space
134140
auto p = rec.p;
135-
auto normal = rec.normal;
136-
137141
p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
138142
p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
139143

144+
// Change the normal from object space to world space
145+
auto normal = rec.normal;
140146
normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
141147
normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
142148

143149
rec.p = p;
144-
rec.set_face_normal(rotated_r, normal);
150+
rec.normal = normal;
145151

146152
return true;
147153
}

0 commit comments

Comments
 (0)