From b76285cd624b740b8cee1adbefbb77caef5368ed Mon Sep 17 00:00:00 2001 From: Steve Hollasch Date: Fri, 11 Sep 2020 16:17:56 -0700 Subject: [PATCH] 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 --- CHANGELOG.md | 11 +++--- books/RayTracingInOneWeekend.html | 54 +++++++++++++------------- books/RayTracingTheNextWeek.html | 51 +++++++++++++++++++++--- books/RayTracingTheRestOfYourLife.html | 37 +++++++++++++++++- src/InOneWeekend/material.h | 16 ++++---- src/TheNextWeek/material.h | 16 ++++---- src/TheRestOfYourLife/material.h | 16 ++++---- 7 files changed, 141 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9751ea9c..df52cfd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,18 @@ Change Log -- Ray Tracing in One Weekend ### _In One Weekend_ - Delete: Remove premature `cstdlib` include; not needed until we use `rand()` (#687) - Fix: Replace old anti-alias result image with before-and-after image (#679) - - Fix: Listing 29: Added missing `rtweekend.h` include (#691) - Fix: Undefined `vup` variable in camera definition (#686) + - Fix: Listing 29: Added missing `rtweekend.h` include (#691) + - Fix: Listing 33, 39: Add consistent function signature for `trilinear_interp` (#722) - Fix: Listing 51: Add missing `hittable.h`, `rtweekend.h` includes (#693) + - Fix: Listing 59: ["Full glass material"] Diverged from source - Fix: Fix error in citation section (#721) - - Fix: Listings 33, 39: Add consistent function signature for `trilinear_interp` (#722) ### _The Next Week_ - Change: `bvh_node` no longer reorders the source vector of scene objects; uses local copy instead (#701) - Delete: Remove unused u,v,w variables in initial `perlin::noise()` function (#684) + - Fix: Listing 5: Neglected to add ray time for metal and dielectric materials (#133) - Fix: Listing 15: In `bvh.h`, add missing `hittable_list.h` include (#690) - Fix: Listing 33, 34, 38: Change implicit casts to explicit ones (#692) - Fix: Listing 40: Change `perlin.h` in the caption to `texture.h` (#698) @@ -31,9 +33,8 @@ Change Log -- Ray Tracing in One Weekend ### _The Rest of Your Life_ - Fix: Fix errors in citation section (#721) -### _The Next Week_ - - Change: `bvh_node` no longer reorders the source vector of scene objects; uses local copy - instead (#701) +### _The Rest of Your Life_ + - Add: Listing 36: Add missing updates to dielectric class for updating specular in scatter record ---------------------------------------------------------------------------------------------------- diff --git a/books/RayTracingInOneWeekend.html b/books/RayTracingInOneWeekend.html index c1e9bac5..69d8132a 100644 --- a/books/RayTracingInOneWeekend.html +++ b/books/RayTracingInOneWeekend.html @@ -2395,21 +2395,22 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); scattered = ray(rec.p, refracted); return true; } - double ref_idx; + public: + double ir; // Index of Refraction }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [dielectric]: [material.h] Dielectric material class that always refracts] @@ -2458,7 +2459,7 @@ solution does not exist, the glass cannot refract, and therefore must reflect the ray: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - if (etai_over_etat * sin_theta > 1.0) { + if (refraction_ratio * sin_theta > 1.0) { // Must Reflect ... } else { @@ -2487,7 +2488,7 @@ double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if (etai_over_etat * sin_theta > 1.0) { + if (refraction_ratio * sin_theta > 1.0) { // Must Reflect ... } else { @@ -2505,34 +2506,36 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if (etai_over_etat * sin_theta > 1.0 ) { + + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + + // If the ray cannot refract, then return the reflected path. + if (cannot_refract) { vec3 reflected = reflect(unit_direction, rec.normal); scattered = ray(rec.p, reflected); return true; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); scattered = ray(rec.p, refracted); return true; } public: - double ref_idx; + double ir; // Index of Refraction }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [dielectric]: [material.h] Dielectric material class with reflection] @@ -2580,38 +2583,37 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if (etai_over_etat * sin_theta > 1.0 ) { - vec3 reflected = reflect(unit_direction, rec.normal); - scattered = ray(rec.p, reflected); - return true; - } + + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight - double reflect_prob = schlick(cos_theta, etai_over_etat); - if (random_double() < reflect_prob) - { + // If the ray cannot refract, or if it probabilistically reflects because of its + // grazing angle, then return the reflected path. + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ vec3 reflected = reflect(unit_direction, rec.normal); scattered = ray(rec.p, reflected); return true; } - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); scattered = ray(rec.p, refracted); return true; } public: - double ref_idx; + double ir; // Index of Refraction }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [glass]: [material.h] Full glass material] diff --git a/books/RayTracingTheNextWeek.html b/books/RayTracingTheNextWeek.html index dc098254..9abaf598 100644 --- a/books/RayTracingTheNextWeek.html +++ b/books/RayTracingTheNextWeek.html @@ -271,13 +271,12 @@ Tracking the Time of Ray Intersection --------------------------------------
-Be sure that in the materials you have the scattered rays be at the time of the incident ray. +Now that rays have a time property, we need to update the `material::scatter()` methods to account +for the time of intersection: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ class lambertian : public material { - public: - lambertian(const color& a) : albedo(a) {} - + ... virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { @@ -288,11 +287,51 @@ attenuation = albedo; return true; } + ... + }; + + class metal : public material { + ... + virtual bool scatter( + const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered + ) const override { + vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere(), r_in.time()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + attenuation = albedo; + return (dot(scattered.direction(), rec.normal) > 0); + } + ... + }; - color albedo; + class dielectric : public material { + ... + virtual bool scatter( + const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered + ) const override { + + ... + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { + vec3 reflected = reflect(unit_direction, rec.normal); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + scattered = ray(rec.p, reflected, r_in.time()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + return true; + } + + vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + scattered = ray(rec.p, refracted, r_in.time()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + return true; + } + ... }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - [Listing [lambertian-animate]: [material.h] Lambertian matrial for moving objects] + [Listing [material-time]: [material.h] + Handle ray time in the material::scatter() methods + ]
diff --git a/books/RayTracingTheRestOfYourLife.html b/books/RayTracingTheRestOfYourLife.html index 751d9def..564bd433 100644 --- a/books/RayTracingTheRestOfYourLife.html +++ b/books/RayTracingTheRestOfYourLife.html @@ -2123,7 +2123,7 @@
We have not yet dealt with specular surfaces, nor instances that mess with the surface normal. But this design is clean overall, and those are all fixable. For now, I will just fix `specular`. Metal -is easy to fix. +and dielectric materials are easy to fix. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ class metal : public material { @@ -2146,8 +2146,41 @@ color albedo; double fuzz; }; + + ... + + class dielectric : public material { + public: + ... + virtual bool scatter( + const ray& r_in, const hit_record& rec, scatter_record& srec + ) const override { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + srec.is_specular = true; + srec.pdf_ptr = nullptr; + srec.attenuation = color(1.0, 1.0, 1.0); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; + + ... + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { + vec3 reflected = reflect(unit_direction, rec.normal); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + srec.specular_ray = ray(rec.p, reflected, r_in.time()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + return true; + } + + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + srec.specular_ray = ray(rec.p, refracted, r_in.time()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + return true; + } + ... + }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - [Listing [metal-scatter]: [material.h] The metal class scatter method] + [Listing [material-scatter]: [material.h] The metal and dielectric scatter methods]
Note that if fuzziness is high, this surface isn’t ideally specular, but the implicit sampling works diff --git a/src/InOneWeekend/material.h b/src/InOneWeekend/material.h index 22c23470..5df932e1 100644 --- a/src/InOneWeekend/material.h +++ b/src/InOneWeekend/material.h @@ -71,33 +71,35 @@ class metal : public material { class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = rec.front_face ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if ( (etai_over_etat * sin_theta > 1.0) - || (random_double() < schlick(cos_theta, etai_over_etat)) - ) { + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + + // If the ray cannot refract, or if it probabilistically reflects because of its + // grazing angle, then return the reflected path. + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { vec3 reflected = reflect(unit_direction, rec.normal); scattered = ray(rec.p, reflected); return true; } - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); scattered = ray(rec.p, refracted); return true; } public: - double ref_idx; + double ir; // Index of Refraction }; diff --git a/src/TheNextWeek/material.h b/src/TheNextWeek/material.h index 1dc49ee2..5fd41ff1 100644 --- a/src/TheNextWeek/material.h +++ b/src/TheNextWeek/material.h @@ -76,33 +76,35 @@ class metal : public material { class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered ) const override { attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if ( (etai_over_etat * sin_theta > 1.0) - || (random_double() < schlick(cos_theta, etai_over_etat)) - ) { + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + + // If the ray cannot refract, or if it probabilistically reflects because of its + // grazing angle, then return the reflected path. + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { vec3 reflected = reflect(unit_direction, rec.normal); scattered = ray(rec.p, reflected, r_in.time()); return true; } - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); scattered = ray(rec.p, refracted, r_in.time()); return true; } public: - double ref_idx; + double ir; // Index of Refraction }; diff --git a/src/TheRestOfYourLife/material.h b/src/TheRestOfYourLife/material.h index 2998b73a..c5761658 100644 --- a/src/TheRestOfYourLife/material.h +++ b/src/TheRestOfYourLife/material.h @@ -104,7 +104,7 @@ class metal : public material { class dielectric : public material { public: - dielectric(double ri) : ref_idx(ri) {} + dielectric(double index_of_refraction) : ir(index_of_refraction) {} virtual bool scatter( const ray& r_in, const hit_record& rec, scatter_record& srec @@ -112,27 +112,29 @@ class dielectric : public material { srec.is_specular = true; srec.pdf_ptr = nullptr; srec.attenuation = color(1.0, 1.0, 1.0); - double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : ref_idx; + double refraction_ratio = rec.front_face ? (1.0/ir) : ir; vec3 unit_direction = unit_vector(r_in.direction()); double cos_theta = fmin(dot(-unit_direction, rec.normal), 1.0); double sin_theta = sqrt(1.0 - cos_theta*cos_theta); - if ( (etai_over_etat * sin_theta > 1.0) - || (random_double() < schlick(cos_theta, etai_over_etat)) - ) { + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + + // If the ray cannot refract, or if it probabilistically reflects because of its + // grazing angle, then return the reflected path. + if (cannot_refract || random_double() < schlick(cos_theta, refraction_ratio)) { vec3 reflected = reflect(unit_direction, rec.normal); srec.specular_ray = ray(rec.p, reflected, r_in.time()); return true; } - vec3 refracted = refract(unit_direction, rec.normal, etai_over_etat); + vec3 refracted = refract(unit_direction, rec.normal, refraction_ratio); srec.specular_ray = ray(rec.p, refracted, r_in.time()); return true; } public: - double ref_idx; + double ir; // Index of Refraction };