Skip to content

Commit 1bedb8b

Browse files
committed
Implement bubbles with IOR, not negative radii
We were using negative radii on spheres to invert the sense of front vs back faces of spheres, and using this to get an inverted index of refraction (IOR) for bubbles. This method was used to model a hollow glass sphere. Over the past couple of years we've encountered a number of bugs induced by negative radii spheres (see the list in the description for issue 1420). Instead, this change interprets the dielectric parameter not as an IOR relative to surrounding air, but instead as the ratio of the IOR of the object material over the IOR of the surrounding medium. Interpreting the parameter this way lets you model a sphere of air enclosed in glass -- just what we need to model a hollow glass sphere purely with the proper IORs, and without resorting to inverted geometry with negative radii spheres. We also end up using this method to properly demonstrate total internal/external reflection in another section. Resolves #1420
1 parent 30f76aa commit 1bedb8b

File tree

5 files changed

+38
-40
lines changed

5 files changed

+38
-40
lines changed

books/RayTracingInOneWeekend.html

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,7 @@
11141114

11151115
class sphere : public hittable {
11161116
public:
1117-
sphere(const point3& center, double radius) : center(center), radius(radius) {}
1117+
sphere(const point3& center, double radius) : center(center), radius(fmax(0,radius)) {}
11181118

11191119
bool hit(const ray& r, double ray_tmin, double ray_tmax, hit_record& rec) const override {
11201120
vec3 oc = center - r.origin();
@@ -2795,7 +2795,7 @@
27952795
public:
27962796
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
27972797
sphere(const point3& center, double radius, shared_ptr<material> mat)
2798-
: center(center), radius(radius), mat(mat) {}
2798+
: center(center), radius(fmax(0,radius)), mat(mat) {}
27992799
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
28002800

28012801
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
@@ -3477,50 +3477,47 @@
34773477

34783478
Modeling a Hollow Glass Sphere
34793479
-------------------------------
3480-
An interesting and easy trick with dielectric spheres is to note that if you use a negative radius,
3481-
the geometry is unaffected, but the surface normal points inward.
3480+
Let's model a hollow glass sphere. This is a sphere of some thickness with another sphere of air
3481+
inside it. If you think about the path of a ray going through such an object, it will hit the outer
3482+
sphere, refract, hit the inner sphere (assuming we do hit it), refract a second time, and travel
3483+
through the air inside. Then it will continue on, hit the inside surface of the inner sphere,
3484+
refract back, then hit the inside surface of the outer sphere, and finally refract and exit back
3485+
into the scene atmosphere.
34823486

3483-
However, properly handling negative radii can be tricky. Recall the line from `sphere::hit()` in
3484-
listing [sphere-material] that calculates the outward normal:
3487+
The outer sphere is just modeled with a standard glass sphere, with an index of refraction of around
3488+
1.50. The inner sphere is a bit different, because _its_ index of refraction will now be relative to
3489+
the "glass atmosphere" of the outer sphere. This is actually simple to specify, as our
3490+
index-of-refraction parameter to the dielectric material can be interpreted as the ratio of the
3491+
index of refraction of the object over the index of refraction of the enclosing medium.
34853492

3486-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3487-
vec3 outward_normal = (rec.p - center) / radius;
3488-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3489-
[Listing [proper-invert-sphere-normal]: Proper normal handling for spheres with negative radii]
3490-
3491-
In your own implementation, you might have been tempted to instead do something like this:
3492-
3493-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3494-
vec3 outward_normal = (rec.p - center).unit_vector();
3495-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3496-
[Listing [improper-invert-sphere-normal]:
3497-
Problematic normal calculation for spheres with negative radii
3498-
]
3499-
3500-
If you do that, spheres with negative radii won't work properly. Since a sphere with a negative
3501-
radius is a _bubble_, its interior is the infinite space outside the sphere. Its exterior is the
3502-
finite bubble inside the sphere, so the outward normal needs to point toward the sphere center.
3503-
Dividing by the (negative) radius flips the normal as we want. If you implmented your code like the
3504-
second example above, you'll want to fix that now.
3493+
In this case, the inner sphere would have an index of refraction of air (the inner sphere material)
3494+
over the index of refraction of glass (the enclosing medium), or $1.00/1.50 = 0.67$.
35053495

3506-
Let's use this hollow sphere hack to model the interior of a sphere with a given thickness. To do
3507-
this, add a second _inverted_ glass sphere inside the original glass sphere:
3496+
Here's the code:
35083497

35093498
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
35103499
...
3500+
auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
3501+
auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5));
3502+
auto material_left = make_shared<dielectric>(1.50);
3503+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
3504+
auto material_bubble = make_shared<dielectric>(0.67);
3505+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
3506+
auto material_right = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0);
3507+
35113508
world.add(make_shared<sphere>(point3( 0.0, -100.5, -1.0), 100.0, material_ground));
35123509
world.add(make_shared<sphere>(point3( 0.0, 0.0, -1.0), 0.5, material_center));
35133510
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.5, material_left));
35143511
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
3515-
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), -0.4, material_left));
3512+
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.4, material_bubble));
35163513
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
35173514
world.add(make_shared<sphere>(point3( 1.0, 0.0, -1.0), 0.5, material_right));
35183515
...
35193516
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35203517
[Listing [scene-hollow-glass]: <kbd>[main.cc]</kbd> Scene with hollow glass sphere]
35213518

35223519
<div class='together'>
3523-
This gives:
3520+
And here's the result:
35243521

35253522
![<span class='num'>Image 18:</span> A hollow glass sphere
35263523
](../images/img-1.18-glass-hollow.png class='pixel')
@@ -3772,13 +3769,14 @@
37723769
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
37733770
auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
37743771
auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5));
3775-
auto material_left = make_shared<dielectric>(1.5);
3772+
auto material_left = make_shared<dielectric>(1.50);
3773+
auto material_bubble = make_shared<dielectric>(0.67);
37763774
auto material_right = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0);
37773775

37783776
world.add(make_shared<sphere>(point3( 0.0, -100.5, -1.0), 100.0, material_ground));
37793777
world.add(make_shared<sphere>(point3( 0.0, 0.0, -1.0), 0.5, material_center));
37803778
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.5, material_left));
3781-
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), -0.4, material_left));
3779+
world.add(make_shared<sphere>(point3(-1.0, 0.0, -1.0), 0.4, material_bubble));
37823780
world.add(make_shared<sphere>(point3( 1.0, 0.0, -1.0), 0.5, material_right));
37833781
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
37843782

books/RayTracingTheNextWeek.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,12 @@
179179
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
180180
// Stationary Sphere
181181
sphere(const point3& center, double radius, shared_ptr<material> mat)
182-
: center1(center), radius(radius), mat(mat), is_moving(false) {}
182+
: center1(center), radius(fmax(0,radius)), mat(mat), is_moving(false) {}
183183

184184
// Moving Sphere
185185
sphere(const point3& center1, const point3& center2, double radius,
186186
shared_ptr<material> mat)
187-
: center1(center1), radius(radius), mat(mat), is_moving(true)
187+
: center1(center1), radius(fmax(0,radius)), mat(mat), is_moving(true)
188188
{
189189
center_vec = center2 - center1;
190190
}
@@ -724,7 +724,7 @@
724724
// Stationary Sphere
725725
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
726726
sphere(const point3& center, double radius, shared_ptr<material> mat)
727-
: center1(center), radius(radius), mat(mat), is_moving(false)
727+
: center1(center), radius(fmax(0,radius)), mat(mat), is_moving(false)
728728
{
729729
auto rvec = vec3(radius, radius, radius);
730730
bbox = aabb(center1 - rvec, center1 + rvec);
@@ -760,7 +760,7 @@
760760
// Moving Sphere
761761
sphere(const point3& center1, const point3& center2, double radius,
762762
shared_ptr<material> mat)
763-
: center1(center1), radius(radius), mat(mat), is_moving(true)
763+
: center1(center1), radius(fmax(0,radius)), mat(mat), is_moving(true)
764764
{
765765
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
766766
auto rvec = vec3(radius, radius, radius);

src/InOneWeekend/sphere.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class sphere : public hittable {
2020
public:
2121
sphere(const point3& center, double radius, shared_ptr<material> mat)
22-
: center(center), radius(radius), mat(mat) {}
22+
: center(center), radius(fmax(0,radius)), mat(mat) {}
2323

2424
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
2525
vec3 oc = center - r.origin();

src/TheNextWeek/sphere.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class sphere : public hittable {
2020
public:
2121
// Stationary Sphere
2222
sphere(const point3& center, double radius, shared_ptr<material> mat)
23-
: center1(center), radius(radius), mat(mat), is_moving(false)
23+
: center1(center), radius(fmax(0,radius)), mat(mat), is_moving(false)
2424
{
2525
auto rvec = vec3(radius, radius, radius);
2626
bbox = aabb(center1 - rvec, center1 + rvec);
@@ -29,7 +29,7 @@ class sphere : public hittable {
2929
// Moving Sphere
3030
sphere(const point3& center1, const point3& center2, double radius,
3131
shared_ptr<material> mat)
32-
: center1(center1), radius(radius), mat(mat), is_moving(true)
32+
: center1(center1), radius(fmax(0,radius)), mat(mat), is_moving(true)
3333
{
3434
auto rvec = vec3(radius, radius, radius);
3535
aabb box1(center1 - rvec, center1 + rvec);

src/TheRestOfYourLife/sphere.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class sphere : public hittable {
2121
public:
2222
// Stationary Sphere
2323
sphere(const point3& center, double radius, shared_ptr<material> mat)
24-
: center1(center), radius(radius), mat(mat), is_moving(false)
24+
: center1(center), radius(fmax(0,radius)), mat(mat), is_moving(false)
2525
{
2626
auto rvec = vec3(radius, radius, radius);
2727
bbox = aabb(center1 - rvec, center1 + rvec);
@@ -30,7 +30,7 @@ class sphere : public hittable {
3030
// Moving Sphere
3131
sphere(const point3& center1, const point3& center2, double radius,
3232
shared_ptr<material> mat)
33-
: center1(center1), radius(radius), mat(mat), is_moving(true)
33+
: center1(center1), radius(fmax(0,radius)), mat(mat), is_moving(true)
3434
{
3535
auto rvec = vec3(radius, radius, radius);
3636
aabb box1(center1 - rvec, center1 + rvec);

0 commit comments

Comments
 (0)