Skip to content

Commit 2ee8f9a

Browse files
committed
Reverse ray-sphere direction and calculations
Trevor observed that the equation for the ray-sphere intersection was backwards in that it used (P - C) [center to ray origin] instead of (C - P) [ray origin to sphere center]. That's all well and good, but it ends up rippling all the way back to the fundamental sphere equations presented in the text. Sigh. And I couldn't resist. In flipping everything so it flows without an odd inversion in the middle, I also discovered some annoying minus signs that could be eliminated, plus some bits that needed a tiny bit more explanation. Quite a bit more work than it originally appeared, but I'm pleased with the results. Resolves #1191
1 parent b66de94 commit 2ee8f9a

File tree

5 files changed

+76
-66
lines changed

5 files changed

+76
-66
lines changed

books/RayTracingInOneWeekend.html

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -581,10 +581,10 @@
581581
If you're wondering why we don't just use `aspect_ratio` when computing `viewport_width`, it's
582582
because the value set to `aspect_ratio` is the ideal ratio, it may not be the _actual_ ratio between
583583
`image_width` and `image_height`. If `image_height` was allowed to be real valued--rather than just
584-
an integer--then it would fine to use `aspect_ratio`. But the _actual_ ratio between `image_width`
585-
and `image_height` can vary based on two parts of the code. First, `integer_height` is rounded down
586-
to the nearest integer, which can increase the ratio. Second, we don't allow `integer_height` to be
587-
less than one, which can also change the actual aspect ratio.
584+
an integer--then it would be fine to use `aspect_ratio`. But the _actual_ ratio between
585+
`image_width` and `image_height` can vary based on two parts of the code. First, `image_height` is
586+
rounded down to the nearest integer, which can increase the ratio. Second, we don't allow
587+
`image_height` to be less than one, which can also change the actual aspect ratio.
588588

589589
Note that `aspect_ratio` is an ideal ratio, which we approximate as best as possible with the
590590
integer-based ratio of image width over image height. In order for our viewport proportions to
@@ -777,67 +777,71 @@
777777
If we want to allow the sphere center to be at an arbitrary point $(C_x, C_y, C_z)$, then the
778778
equation becomes a lot less nice:
779779

780-
$$ (x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2 = r^2 $$
780+
$$ (C_x - x)^2 + (C_y - y)^2 + (C_z - z)^2 = r^2 $$
781781

782782
In graphics, you almost always want your formulas to be in terms of vectors so that all the
783783
$x$/$y$/$z$ stuff can be simply represented using a `vec3` class. You might note that the vector
784-
from center $\mathbf{C} = (C_x, C_y, C_z)$ to point $\mathbf{P} = (x,y,z)$ is
785-
$(\mathbf{P} - \mathbf{C})$. If we use the definition of the dot product:
784+
from point $\mathbf{P} = (x,y,z)$ to center $\mathbf{C} = (C_x, C_y, C_z)$ is
785+
$(\mathbf{C} - \mathbf{P})$.
786786

787-
$$ (\mathbf{P} - \mathbf{C}) \cdot (\mathbf{P} - \mathbf{C})
788-
= (x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2
787+
If we use the definition of the dot product:
788+
789+
$$ (\mathbf{C} - \mathbf{P}) \cdot (\mathbf{C} - \mathbf{P})
790+
= (C_x - x)^2 + (C_y - y)^2 + (C_z - z)^2
789791
$$
790792

791793
Then we can rewrite the equation of the sphere in vector form as:
792794

793-
$$ (\mathbf{P} - \mathbf{C}) \cdot (\mathbf{P} - \mathbf{C}) = r^2 $$
795+
$$ (\mathbf{C} - \mathbf{P}) \cdot (\mathbf{C} - \mathbf{P}) = r^2 $$
794796

795797
We can read this as “any point $\mathbf{P}$ that satisfies this equation is on the sphere”. We want
796-
to know if our ray $\mathbf{P}(t) = \mathbf{A} + t\mathbf{b}$ ever hits the sphere anywhere. If it
798+
to know if our ray $\mathbf{P}(t) = \mathbf{Q} + t\mathbf{d}$ ever hits the sphere anywhere. If it
797799
does hit the sphere, there is some $t$ for which $\mathbf{P}(t)$ satisfies the sphere equation. So
798800
we are looking for any $t$ where this is true:
799801

800-
$$ (\mathbf{P}(t) - \mathbf{C}) \cdot (\mathbf{P}(t) - \mathbf{C}) = r^2 $$
802+
$$ (\mathbf{C} - \mathbf{P}(t)) \cdot (\mathbf{C} - \mathbf{P}(t)) = r^2 $$
801803

802804
which can be found by replacing $\mathbf{P}(t)$ with its expanded form:
803805

804-
$$ ((\mathbf{A} + t \mathbf{b}) - \mathbf{C})
805-
\cdot ((\mathbf{A} + t \mathbf{b}) - \mathbf{C}) = r^2 $$
806+
$$ (\mathbf{C} - (\mathbf{Q} + t \mathbf{d}))
807+
\cdot (\mathbf{C} - (\mathbf{Q} + t \mathbf{d})) = r^2 $$
806808

807809
We have three vectors on the left dotted by three vectors on the right. If we solved for the full
808810
dot product we would get nine vectors. You can definitely go through and write everything out, but
809811
we don't need to work that hard. If you remember, we want to solve for $t$, so we'll separate the
810812
terms based on whether there is a $t$ or not:
811813

812-
$$ (t \mathbf{b} + (\mathbf{A} - \mathbf{C}))
813-
\cdot (t \mathbf{b} + (\mathbf{A} - \mathbf{C})) = r^2 $$
814+
$$ (-t \mathbf{d} + (\mathbf{C} - \mathbf{Q})) \cdot (-t \mathbf{d} + (\mathbf{C} - \mathbf{Q}))
815+
= r^2
816+
$$
814817

815818
And now we follow the rules of vector algebra to distribute the dot product:
816819

817-
$$ t^2 \mathbf{b} \cdot \mathbf{b}
818-
+ 2t \mathbf{b} \cdot (\mathbf{A}-\mathbf{C})
819-
+ (\mathbf{A}-\mathbf{C}) \cdot (\mathbf{A}-\mathbf{C}) = r^2
820+
$$ t^2 \mathbf{d} \cdot \mathbf{d}
821+
- 2t \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q})
822+
+ (\mathbf{C} - \mathbf{Q}) \cdot (\mathbf{C} - \mathbf{Q}) = r^2
820823
$$
821824

822825
Move the square of the radius over to the left hand side:
823826

824-
$$ t^2 \mathbf{b} \cdot \mathbf{b}
825-
+ 2t \mathbf{b} \cdot (\mathbf{A}-\mathbf{C})
826-
+ (\mathbf{A}-\mathbf{C}) \cdot (\mathbf{A}-\mathbf{C}) - r^2 = 0
827+
$$ t^2 \mathbf{d} \cdot \mathbf{d}
828+
- 2t \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q})
829+
+ (\mathbf{C} - \mathbf{Q}) \cdot (\mathbf{C} - \mathbf{Q}) - r^2 = 0
827830
$$
828831

829832
It's hard to make out what exactly this equation is, but the vectors and $r$ in that equation are
830833
all constant and known. Furthermore, the only vectors that we have are reduced to scalars by dot
831834
product. The only unknown is $t$, and we have a $t^2$, which means that this equation is quadratic.
832-
You can solve for a quadratic equation by using the quadratic formula:
835+
You can solve for a quadratic equation $ax^2 + bx + c = 0$ by using the quadratic formula:
833836

834837
$$ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$
835838

836-
Where for ray-sphere intersection the $a$/$b$/$c$ values are:
839+
So solving for $t$ in the ray-sphere intersection equation gives us these values for $a$, $b$, and
840+
$c$:
837841

838-
$$ a = \mathbf{b} \cdot \mathbf{b} $$
839-
$$ b = 2 \mathbf{b} \cdot (\mathbf{A}-\mathbf{C}) $$
840-
$$ c = (\mathbf{A}-\mathbf{C}) \cdot (\mathbf{A}-\mathbf{C}) - r^2 $$
842+
$$ a = \mathbf{d} \cdot \mathbf{d} $$
843+
$$ b = -2 \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q}) $$
844+
$$ c = (\mathbf{C} - \mathbf{Q}) \cdot (\mathbf{C} - \mathbf{Q}) - r^2 $$
841845

842846
Using all of the above you can solve for $t$, but there is a square root part that can be either
843847
positive (meaning two real solutions), negative (meaning no real solutions), or zero (meaning one
@@ -854,9 +858,9 @@
854858

855859
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
856860
bool hit_sphere(const point3& center, double radius, const ray& r) {
857-
vec3 oc = r.origin() - center;
861+
vec3 oc = center - r.origin();
858862
auto a = dot(r.direction(), r.direction());
859-
auto b = 2.0 * dot(oc, r.direction());
863+
auto b = -2.0 * dot(r.direction(), oc);
860864
auto c = dot(oc, oc) - radius*radius;
861865
auto discriminant = b*b - 4*a*c;
862866
return (discriminant >= 0);
@@ -936,9 +940,9 @@
936940
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
937941
double hit_sphere(const point3& center, double radius, const ray& r) {
938942
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
939-
vec3 oc = r.origin() - center;
943+
vec3 oc = center - r.origin();
940944
auto a = dot(r.direction(), r.direction());
941-
auto b = 2.0 * dot(oc, r.direction());
945+
auto b = -2.0 * dot(r.direction(), oc);
942946
auto c = dot(oc, oc) - radius*radius;
943947
auto discriminant = b*b - 4*a*c;
944948

@@ -983,9 +987,9 @@
983987

984988
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
985989
double hit_sphere(const point3& center, double radius, const ray& r) {
986-
vec3 oc = r.origin() - center;
990+
vec3 oc = center - r.origin();
987991
auto a = dot(r.direction(), r.direction());
988-
auto b = 2.0 * dot(oc, r.direction());
992+
auto b = -2.0 * dot(r.direction(), oc);
989993
auto c = dot(oc, oc) - radius*radius;
990994
auto discriminant = b*b - 4*a*c;
991995

@@ -1000,35 +1004,41 @@
10001004

10011005
First, recall that a vector dotted with itself is equal to the squared length of that vector.
10021006

1003-
Second, notice how the equation for `b` has a factor of two in it. Consider what happens to the
1004-
quadratic equation if $b = 2h$:
1007+
Second, notice how the equation for `b` has a factor of negative two in it. Consider what happens to
1008+
the quadratic equation if $b = -2h$:
10051009

10061010
$$ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$
10071011

1008-
$$ = \frac{-2h \pm \sqrt{(2h)^2 - 4ac}}{2a} $$
1012+
$$ = \frac{-(-2h) \pm \sqrt{(-2h)^2 - 4ac}}{2a} $$
1013+
1014+
$$ = \frac{2h \pm 2\sqrt{h^2 - ac}}{2a} $$
1015+
1016+
$$ = \frac{h \pm \sqrt{h^2 - ac}}{a} $$
10091017

1010-
$$ = \frac{-2h \pm 2\sqrt{h^2 - ac}}{2a} $$
1018+
This simplifies nicely, so we'll use it. So solving for $h$:
10111019

1012-
$$ = \frac{-h \pm \sqrt{h^2 - ac}}{a} $$
1020+
$$ b = -2 \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q}) $$
1021+
$$ b = -2h $$
1022+
$$ h = \frac{b}{-2} = \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q}) $$
10131023

10141024
<div class='together'>
10151025
Using these observations, we can now simplify the sphere-intersection code to this:
10161026

10171027
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
10181028
double hit_sphere(const point3& center, double radius, const ray& r) {
1019-
vec3 oc = r.origin() - center;
1029+
vec3 oc = center - r.origin();
10201030
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
10211031
auto a = r.direction().length_squared();
1022-
auto half_b = dot(oc, r.direction());
1032+
auto h = dot(r.direction(), oc);
10231033
auto c = oc.length_squared() - radius*radius;
1024-
auto discriminant = half_b*half_b - a*c;
1034+
auto discriminant = h*h - a*c;
10251035
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
10261036

10271037
if (discriminant < 0) {
10281038
return -1.0;
10291039
} else {
10301040
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
1031-
return (-half_b - sqrt(discriminant) ) / a;
1041+
return (h - sqrt(discriminant)) / a;
10321042
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
10331043
}
10341044
}
@@ -1096,19 +1106,19 @@
10961106
sphere(point3 _center, double _radius) : center(_center), radius(_radius) {}
10971107

10981108
bool hit(const ray& r, double ray_tmin, double ray_tmax, hit_record& rec) const override {
1099-
vec3 oc = r.origin() - center;
1109+
vec3 oc = center - r.origin();
11001110
auto a = r.direction().length_squared();
1101-
auto half_b = dot(oc, r.direction());
1111+
auto h = dot(r.direction(), oc);
11021112
auto c = oc.length_squared() - radius*radius;
11031113

1104-
auto discriminant = half_b*half_b - a*c;
1114+
auto discriminant = h*h - a*c;
11051115
if (discriminant < 0) return false;
11061116
auto sqrtd = sqrt(discriminant);
11071117

11081118
// Find the nearest root that lies in the acceptable range.
1109-
auto root = (-half_b - sqrtd) / a;
1119+
auto root = (h - sqrtd) / a;
11101120
if (root <= ray_tmin || ray_tmax <= root) {
1111-
root = (-half_b + sqrtd) / a;
1121+
root = (h + sqrtd) / a;
11121122
if (root <= ray_tmin || ray_tmax <= root)
11131123
return false;
11141124
}
@@ -1621,11 +1631,11 @@
16211631
...
16221632

16231633
// Find the nearest root that lies in the acceptable range.
1624-
auto root = (-half_b - sqrtd) / a;
1634+
auto root = (h - sqrtd) / a;
16251635
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
16261636
if (!ray_t.surrounds(root)) {
16271637
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1628-
root = (-half_b + sqrtd) / a;
1638+
root = (h + sqrtd) / a;
16291639
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
16301640
if (!ray_t.surrounds(root))
16311641
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++

books/RayTracingTheNextWeek.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@
227227
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
228228
point3 center = is_moving ? sphere_center(r.time()) : center1;
229229
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
230-
vec3 oc = r.origin() - center;
230+
vec3 oc = center - r.origin();
231231
auto a = r.direction().length_squared();
232-
auto half_b = dot(oc, r.direction());
232+
auto h = dot(r.direction(), oc);
233233
auto c = oc.length_squared() - radius*radius;
234234
...
235235
}

src/InOneWeekend/sphere.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ class sphere : public hittable {
2222
: center(_center), radius(_radius), mat(_material) {}
2323

2424
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
25-
vec3 oc = r.origin() - center;
25+
vec3 oc = center - r.origin();
2626
auto a = r.direction().length_squared();
27-
auto half_b = dot(oc, r.direction());
27+
auto h = dot(r.direction(), oc);
2828
auto c = oc.length_squared() - radius*radius;
2929

30-
auto discriminant = half_b*half_b - a*c;
30+
auto discriminant = h*h - a*c;
3131
if (discriminant < 0)
3232
return false;
3333

3434
// Find the nearest root that lies in the acceptable range.
3535
auto sqrtd = sqrt(discriminant);
36-
auto root = (-half_b - sqrtd) / a;
36+
auto root = (h - sqrtd) / a;
3737
if (!ray_t.surrounds(root)) {
38-
root = (-half_b + sqrtd) / a;
38+
root = (h + sqrtd) / a;
3939
if (!ray_t.surrounds(root))
4040
return false;
4141
}

src/TheNextWeek/sphere.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,20 @@ class sphere : public hittable {
4040

4141
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
4242
point3 center = is_moving ? sphere_center(r.time()) : center1;
43-
vec3 oc = r.origin() - center;
43+
vec3 oc = center - r.origin();
4444
auto a = r.direction().length_squared();
45-
auto half_b = dot(oc, r.direction());
45+
auto h = dot(r.direction(), oc);
4646
auto c = oc.length_squared() - radius*radius;
4747

48-
auto discriminant = half_b*half_b - a*c;
48+
auto discriminant = h*h - a*c;
4949
if (discriminant < 0)
5050
return false;
5151

5252
// Find the nearest root that lies in the acceptable range.
5353
auto sqrtd = sqrt(discriminant);
54-
auto root = (-half_b - sqrtd) / a;
54+
auto root = (h - sqrtd) / a;
5555
if (!ray_t.surrounds(root)) {
56-
root = (-half_b + sqrtd) / a;
56+
root = (h + sqrtd) / a;
5757
if (!ray_t.surrounds(root))
5858
return false;
5959
}

src/TheRestOfYourLife/sphere.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ class sphere : public hittable {
4141

4242
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
4343
point3 center = is_moving ? sphere_center(r.time()) : center1;
44-
vec3 oc = r.origin() - center;
44+
vec3 oc = center - r.origin();
4545
auto a = r.direction().length_squared();
46-
auto half_b = dot(oc, r.direction());
46+
auto h = dot(r.direction(), oc);
4747
auto c = oc.length_squared() - radius*radius;
4848

49-
auto discriminant = half_b*half_b - a*c;
49+
auto discriminant = h*h - a*c;
5050
if (discriminant < 0)
5151
return false;
5252

5353
// Find the nearest root that lies in the acceptable range.
5454
auto sqrtd = sqrt(discriminant);
55-
auto root = (-half_b - sqrtd) / a;
55+
auto root = (h - sqrtd) / a;
5656
if (!ray_t.surrounds(root)) {
57-
root = (-half_b + sqrtd) / a;
57+
root = (h + sqrtd) / a;
5858
if (!ray_t.surrounds(root))
5959
return false;
6060
}

0 commit comments

Comments
 (0)