Skip to content

Commit

Permalink
Fix problems with material::scatter() methods
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
hollasch committed Sep 30, 2020
1 parent d7e3745 commit b76285c
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 60 deletions.
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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


----------------------------------------------------------------------------------------------------
Expand Down
54 changes: 28 additions & 26 deletions books/RayTracingInOneWeekend.html
Original file line number Diff line number Diff line change
Expand Up @@ -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]: <kbd>[material.h]</kbd> Dielectric material class that always refracts]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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]: <kbd>[material.h]</kbd> Dielectric material class with reflection]
Expand Down Expand Up @@ -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]: <kbd>[material.h]</kbd> Full glass material]
Expand Down
51 changes: 45 additions & 6 deletions books/RayTracingTheNextWeek.html
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,12 @@
Tracking the Time of Ray Intersection
--------------------------------------
<div class='together'>
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 {
Expand All @@ -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]: <kbd>[material.h]</kbd> Lambertian matrial for moving objects]
[Listing [material-time]: <kbd>[material.h]</kbd>
Handle ray time in the material::scatter() methods
]
</div>


Expand Down
37 changes: 35 additions & 2 deletions books/RayTracingTheRestOfYourLife.html
Original file line number Diff line number Diff line change
Expand Up @@ -2123,7 +2123,7 @@
<div class='together'>
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 {
Expand All @@ -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]: <kbd>[material.h]</kbd> The metal class scatter method]
[Listing [material-scatter]: <kbd>[material.h]</kbd> The metal and dielectric scatter methods]
</div>

Note that if fuzziness is high, this surface isn’t ideally specular, but the implicit sampling works
Expand Down
16 changes: 9 additions & 7 deletions src/InOneWeekend/material.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};


Expand Down
16 changes: 9 additions & 7 deletions src/TheNextWeek/material.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};


Expand Down
Loading

0 comments on commit b76285c

Please sign in to comment.