Skip to content

Commit 323592f

Browse files
committed
Fix problems with material::scatter() methods
- In book 1, the code for material::dielectric::scatter() divereged from the source code. - In book 2, we neglected to include the code changes to handle ray time for material::metal and material::dielectric. - In book 3, we neglected to include the code changes to handle specular updates to dielectric materials. - Reformulated the dielectric::scatter() method for clarity. Resolves #133
1 parent d7e3745 commit 323592f

File tree

7 files changed

+139
-59
lines changed

7 files changed

+139
-59
lines changed

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Change Log -- Ray Tracing in One Weekend
44
# v3.2.1 (in progress)
55

66
### Common
7+
- Change: Refactored dielectric class for clarity
78
- Fix: Update local Markdeep library (for offline reading) to v1.11. The prior version had
89
incorrect content (#712)
910

@@ -13,13 +14,15 @@ Change Log -- Ray Tracing in One Weekend
1314
- Fix: Listing 29: Added missing `rtweekend.h` include (#691)
1415
- Fix: Undefined `vup` variable in camera definition (#686)
1516
- Fix: Listing 51: Add missing `hittable.h`, `rtweekend.h` includes (#693)
17+
- Fix: Listing 59: ["Full glass material"] Diverged from source
1618
- Fix: Fix error in citation section (#721)
1719
- Fix: Listings 33, 39: Add consistent function signature for `trilinear_interp` (#722)
1820

1921
### _The Next Week_
2022
- Change: `bvh_node` no longer reorders the source vector of scene objects; uses local copy
2123
instead (#701)
2224
- Delete: Remove unused u,v,w variables in initial `perlin::noise()` function (#684)
25+
- Fix: Listing 5: Neglected to add ray time for metal and dielectric materials (#133)
2326
- Fix: Listing 15: In `bvh.h`, add missing `hittable_list.h` include (#690)
2427
- Fix: Listing 33, 34, 38: Change implicit casts to explicit ones (#692)
2528
- Fix: Listing 40: Change `perlin.h` in the caption to `texture.h` (#698)
@@ -30,10 +33,7 @@ Change Log -- Ray Tracing in One Weekend
3033

3134
### _The Rest of Your Life_
3235
- Fix: Fix errors in citation section (#721)
33-
34-
### _The Next Week_
35-
- Change: `bvh_node` no longer reorders the source vector of scene objects; uses local copy
36-
instead (#701)
36+
- Add: Listing 36: Add missing updates to dielectric class for updating specular in scatter record
3737

3838

3939
----------------------------------------------------------------------------------------------------

books/RayTracingInOneWeekend.html

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,21 +2395,22 @@
23952395
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
23962396
class dielectric : public material {
23972397
public:
2398-
dielectric(double ri) : ref_idx(ri) {}
2398+
dielectric(double index_of_refraction) : ir(index_of_refraction) {}
23992399

24002400
virtual bool scatter(
24012401
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
24022402
) const override {
24032403
attenuation = color(1.0, 1.0, 1.0);
2404-
double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx;
2404+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
24052405

24062406
vec3 unit_direction = unit_vector(r_in.direction());
2407-
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
2407+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
24082408
scattered = ray(rec.p, refracted);
24092409
return true;
24102410
}
24112411

2412-
double ref_idx;
2412+
public:
2413+
double ir; // Index of Refraction
24132414
};
24142415
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24152416
[Listing [dielectric]: <kbd>[material.h]</kbd> Dielectric material class that always refracts]
@@ -2458,7 +2459,7 @@
24582459
solution does not exist, the glass cannot refract, and therefore must reflect the ray:
24592460

24602461
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2461-
if (etai_over_etat * sin_theta > 1.0) {
2462+
if (refraction_ratio * sin_theta > 1.0) {
24622463
// Must Reflect
24632464
...
24642465
} else {
@@ -2487,7 +2488,7 @@
24872488
double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
24882489
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
24892490

2490-
if (etai_over_etat * sin_theta > 1.0) {
2491+
if (refraction_ratio * sin_theta > 1.0) {
24912492
// Must Reflect
24922493
...
24932494
} else {
@@ -2505,34 +2506,36 @@
25052506
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
25062507
class dielectric : public material {
25072508
public:
2508-
dielectric(double ri) : ref_idx(ri) {}
2509+
dielectric(double index_of_refraction) : ir(index_of_refraction) {}
25092510

25102511
virtual bool scatter(
25112512
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
25122513
) const override {
25132514
attenuation = color(1.0, 1.0, 1.0);
2514-
double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx;
2515+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
25152516

25162517
vec3 unit_direction = unit_vector(r_in.direction());
2517-
2518-
25192518
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
25202519
double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
25212520
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
2522-
if (etai_over_etat * sin_theta > 1.0 ) {
2521+
2522+
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
2523+
2524+
// If the ray cannot refract, then return the reflected path.
2525+
if (cannot_refract) {
25232526
vec3 reflected = reflect(unit_direction, rec.normal);
25242527
scattered = ray(rec.p, reflected);
25252528
return true;
25262529
}
25272530
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
25282531

2529-
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
2532+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
25302533
scattered = ray(rec.p, refracted);
25312534
return true;
25322535
}
25332536

25342537
public:
2535-
double ref_idx;
2538+
double ir; // Index of Refraction
25362539
};
25372540
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25382541
[Listing [dielectric]: <kbd>[material.h]</kbd> Dielectric material class with reflection]
@@ -2580,38 +2583,37 @@
25802583
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
25812584
class dielectric : public material {
25822585
public:
2583-
dielectric(double ri) : ref_idx(ri) {}
2586+
dielectric(double index_of_refraction) : ir(index_of_refraction) {}
25842587

25852588
virtual bool scatter(
25862589
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
25872590
) const override {
25882591
attenuation = color(1.0, 1.0, 1.0);
2589-
double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx;
2592+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
25902593

25912594
vec3 unit_direction = unit_vector(r_in.direction());
25922595
double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
25932596
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
2594-
if (etai_over_etat * sin_theta > 1.0 ) {
2595-
vec3 reflected = reflect(unit_direction, rec.normal);
2596-
scattered = ray(rec.p, reflected);
2597-
return true;
2598-
}
2597+
2598+
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
2599+
25992600
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2600-
double reflect_prob = schlick(cos_theta, etai_over_etat);
2601-
if (random_double() < reflect_prob)
2602-
{
2601+
// If the ray cannot refract, or if it probabilistically reflects because of its
2602+
// grazing angle, then return the reflected path.
2603+
if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) {
2604+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
26032605
vec3 reflected = reflect(unit_direction, rec.normal);
26042606
scattered = ray(rec.p, reflected);
26052607
return true;
26062608
}
2607-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2608-
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
2609+
2610+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
26092611
scattered = ray(rec.p, refracted);
26102612
return true;
26112613
}
26122614

26132615
public:
2614-
double ref_idx;
2616+
double ir; // Index of Refraction
26152617
};
26162618
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26172619
[Listing [glass]: <kbd>[material.h]</kbd> Full glass material]

books/RayTracingTheNextWeek.html

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,12 @@
271271
Tracking the Time of Ray Intersection
272272
--------------------------------------
273273
<div class='together'>
274-
Be sure that in the materials you have the scattered rays be at the time of the incident ray.
274+
Now that rays have a time property, we need to update the `material::scatter()` methods to account
275+
for the time of intersection:
275276

276277
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
277278
class lambertian : public material {
278-
public:
279-
lambertian(const color& a) : albedo(a) {}
280-
279+
...
281280
virtual bool scatter(
282281
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
283282
) const override {
@@ -288,11 +287,51 @@
288287
attenuation = albedo;
289288
return true;
290289
}
290+
...
291+
};
292+
293+
class metal : public material {
294+
...
295+
virtual bool scatter(
296+
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
297+
) const override {
298+
vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
299+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
300+
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere(), r_in.time());
301+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
302+
attenuation = albedo;
303+
return (dot(scattered.direction(), rec.normal) > 0);
304+
}
305+
...
306+
};
291307

292-
color albedo;
308+
class dielectric : public material {
309+
...
310+
virtual bool scatter(
311+
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
312+
) const override {
313+
314+
...
315+
if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) {
316+
vec3 reflected = reflect(unit_direction, rec.normal);
317+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
318+
scattered = ray(rec.p, reflected, r_in.time());
319+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
320+
return true;
321+
}
322+
323+
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
324+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
325+
scattered = ray(rec.p, refracted, r_in.time());
326+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
327+
return true;
328+
}
329+
...
293330
};
294331
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
295-
[Listing [lambertian-animate]: <kbd>[material.h]</kbd> Lambertian matrial for moving objects]
332+
[Listing [material-time]: <kbd>[material.h]</kbd>
333+
Handle ray time in the material::scatter() methods
334+
]
296335
</div>
297336

298337

books/RayTracingTheRestOfYourLife.html

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,7 +2123,7 @@
21232123
<div class='together'>
21242124
We have not yet dealt with specular surfaces, nor instances that mess with the surface normal. But
21252125
this design is clean overall, and those are all fixable. For now, I will just fix `specular`. Metal
2126-
is easy to fix.
2126+
and dielectric materials are easy to fix.
21272127

21282128
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
21292129
class metal : public material {
@@ -2146,8 +2146,41 @@
21462146
color albedo;
21472147
double fuzz;
21482148
};
2149+
2150+
...
2151+
2152+
class dielectric : public material {
2153+
public:
2154+
...
2155+
virtual bool scatter(
2156+
const ray& r_in, const hit_record& rec, scatter_record& srec
2157+
) const override {
2158+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2159+
srec.is_specular = true;
2160+
srec.pdf_ptr = nullptr;
2161+
srec.attenuation = color(1.0, 1.0, 1.0);
2162+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2163+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
2164+
2165+
...
2166+
if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) {
2167+
vec3 reflected = reflect(unit_direction, rec.normal);
2168+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2169+
srec.specular_ray = ray(rec.p, reflected, r_in.time());
2170+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2171+
return true;
2172+
}
2173+
2174+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
2175+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2176+
srec.specular_ray = ray(rec.p, refracted, r_in.time());
2177+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2178+
return true;
2179+
}
2180+
...
2181+
};
21492182
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2150-
[Listing [metal-scatter]: <kbd>[material.h]</kbd> The metal class scatter method]
2183+
[Listing [material-scatter]: <kbd>[material.h]</kbd> The metal and dielectric scatter methods]
21512184
</div>
21522185

21532186
Note that if fuzziness is high, this surface isn’t ideally specular, but the implicit sampling works

src/InOneWeekend/material.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,35 @@ class metal : public material {
7171

7272
class dielectric : public material {
7373
public:
74-
dielectric(double ri) : ref_idx(ri) {}
74+
dielectric(double index_of_refraction) : ir(index_of_refraction) {}
7575

7676
virtual bool scatter(
7777
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
7878
) const override {
7979
attenuation = color(1.0, 1.0, 1.0);
80-
double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx;
80+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
8181

8282
vec3 unit_direction = unit_vector(r_in.direction());
8383
double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
8484
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
8585

86-
if ( (etai_over_etat * sin_theta > 1.0)
87-
|| (random_double() < schlick(cos_theta, etai_over_etat))
88-
) {
86+
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
87+
88+
// If the ray cannot refract, or if it probabilistically reflects because of its
89+
// grazing angle, then return the reflected path.
90+
if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) {
8991
vec3 reflected = reflect(unit_direction, rec.normal);
9092
scattered = ray(rec.p, reflected);
9193
return true;
9294
}
9395

94-
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
96+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
9597
scattered = ray(rec.p, refracted);
9698
return true;
9799
}
98100

99101
public:
100-
double ref_idx;
102+
double ir; // Index of Refraction
101103
};
102104

103105

src/TheNextWeek/material.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,33 +76,35 @@ class metal : public material {
7676

7777
class dielectric : public material {
7878
public:
79-
dielectric(double ri) : ref_idx(ri) {}
79+
dielectric(double index_of_refraction) : ir(index_of_refraction) {}
8080

8181
virtual bool scatter(
8282
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
8383
) const override {
8484
attenuation = color(1.0, 1.0, 1.0);
85-
double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : ref_idx;
85+
double refraction_ratio = rec.front_face ? (1.0/ir) : ir;
8686

8787
vec3 unit_direction = unit_vector(r_in.direction());
8888
double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0);
8989
double sin_theta = sqrt(1.0 - cos_theta*cos_theta);
9090

91-
if ( (etai_over_etat * sin_theta > 1.0)
92-
|| (random_double() < schlick(cos_theta, etai_over_etat))
93-
) {
91+
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
92+
93+
// If the ray cannot refract, or if it probabilistically reflects because of its
94+
// grazing angle, then return the reflected path.
95+
if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) {
9496
vec3 reflected = reflect(unit_direction, rec.normal);
9597
scattered = ray(rec.p, reflected, r_in.time());
9698
return true;
9799
}
98100

99-
vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat);
101+
vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio);
100102
scattered = ray(rec.p, refracted, r_in.time());
101103
return true;
102104
}
103105

104106
public:
105-
double ref_idx;
107+
double ir; // Index of Refraction
106108
};
107109

108110

0 commit comments

Comments
 (0)