Skip to content

Commit 1da626e

Browse files
committed
lambertian::scatter degenerate direction vec test
In cases where the random unit vector is exactly or very closely equal to the reversed normal vector, the scatter direction vector will be zero or close to it. This can lead to infinite or NaN values after the call. Resolves RayTracing#619
1 parent a7844ad commit 1da626e

File tree

7 files changed

+94
-8
lines changed

7 files changed

+94
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ Change Log -- Ray Tracing in One Weekend
1111
- Fix: Synchronize copies of `hittable_list.h`, `material.h`, `sphere.h`
1212

1313
### In One Weekend
14+
- Fix: Catch cases where `lambertian::scatter()` yields degenerate scatter rays (#619)
1415

1516
### The Next Week
17+
- Fix: Catch cases where `lambertian::scatter()` yields degenerate scatter rays (#619)
1618

1719
### The Rest of Your Life
1820
- Fix: Missing `override` keyword for `xz_rect::pdf_value()` and `xz_rect::random()` methods

books/RayTracingInOneWeekend.html

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,7 @@
20652065
virtual bool scatter(
20662066
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
20672067
) const override {
2068-
vec3 scatter_direction = rec.normal + random_unit_vector();
2068+
auto scatter_direction = rec.normal + random_unit_vector();
20692069
scattered = ray(rec.p, scatter_direction);
20702070
attenuation = albedo;
20712071
return true;
@@ -2081,6 +2081,54 @@
20812081
Note we could just as well only scatter with some probability $p$ and have attenuation be
20822082
$albedo/p$. Your choice.
20832083

2084+
If you read the code above carefully, you'll notice a small chance of mischief. If the random unit
2085+
vector we generate is exactly opposite the normal vector, the two will sum to zero, which will
2086+
result in a zero scatter direction vector. This leads to bad scenarios later on (infinities and
2087+
NaNs), so we need to intercept the condition before we pass it on.
2088+
2089+
<div class='together'>
2090+
In service of this, we'll create a new vector method -- `vec3::near_zero()` -- that returns true if
2091+
the vector is very close to zero in all dimensions.
2092+
2093+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2094+
class vec3 {
2095+
...
2096+
bool near_zero() const {
2097+
// Return true if the vector is close to zero in all dimensions.
2098+
const auto s = 1e-8;
2099+
return (fabs(e[0]) < s) && (fabs(e[1]) < s) && (fabs(e[2]) < s);
2100+
}
2101+
...
2102+
};
2103+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2104+
[Listing [vec3-near-zero]: <kbd>[vec3.h]</kbd> The vec3::near_zero() method]
2105+
2106+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2107+
class lambertian : public material {
2108+
public:
2109+
lambertian(const color& a) : albedo(a) {}
2110+
2111+
virtual bool scatter(
2112+
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
2113+
) const override {
2114+
auto scatter_direction = rec.normal + random_unit_vector();
2115+
2116+
// Catch degenerate scatter direction
2117+
if (scatter_direction.near_zero())
2118+
scatter_direction = rec.normal;
2119+
2120+
scattered = ray(rec.p, scatter_direction);
2121+
attenuation = albedo;
2122+
return true;
2123+
}
2124+
2125+
public:
2126+
color albedo;
2127+
};
2128+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2129+
[Listing [lambertian-catch-zero]: <kbd>[material.h]</kbd> Lambertian scatter, bullet-proof]
2130+
</div>
2131+
20842132

20852133
Mirrored Light Reflection
20862134
--------------------------

books/RayTracingTheNextWeek.html

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,13 @@
280280
virtual bool scatter(
281281
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
282282
) const override {
283-
vec3 scatter_direction = rec.normal + random_unit_vector();
283+
auto scatter_direction = rec.normal + random_unit_vector();
284+
285+
// Catch degenerate scatter direction
286+
if (scatter_direction.near_zero())
287+
scatter_direction = rec.normal;
288+
289+
284290
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
285291
scattered = ray(rec.p, scatter_direction, r_in.time());
286292
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
@@ -1252,7 +1258,12 @@
12521258
virtual bool scatter(
12531259
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
12541260
) const override {
1255-
vec3 scatter_direction = rec.normal + random_unit_vector();
1261+
auto scatter_direction = rec.normal + random_unit_vector();
1262+
1263+
// Catch degenerate scatter direction
1264+
if (scatter_direction.near_zero())
1265+
scatter_direction = rec.normal;
1266+
12561267
scattered = ray(rec.p, scatter_direction, r_in.time());
12571268
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
12581269
attenuation = albedo->value(rec.u, rec.v, rec.p);

books/RayTracingTheRestOfYourLife.html

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -858,15 +858,23 @@
858858
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
859859
class lambertian : public material {
860860
public:
861+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
861862
lambertian(const color& a) : albedo(make_shared<solid_color>(a)) {}
862863
lambertian(shared_ptr<texture> a) : albedo(a) {}
864+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
863865

864-
865-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
866866
virtual bool scatter(
867+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
867868
const ray& r_in, const hit_record& rec, color& alb, ray& scattered, double& pdf
869+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
868870
) const override {
869-
auto direction = rec.normal + random_unit_vector();
871+
auto scatter_direction = rec.normal + random_unit_vector();
872+
873+
// Catch degenerate scatter direction
874+
if (scatter_direction.near_zero())
875+
scatter_direction = rec.normal;
876+
877+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
870878
scattered = ray(rec.p, unit_vector(direction), r_in.time());
871879
alb = albedo->value(rec.u, rec.v, rec.p);
872880
pdf = dot(rec.normal, scattered.direction()) / pi;

src/InOneWeekend/material.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ class lambertian : public material {
3232
virtual bool scatter(
3333
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
3434
) const override {
35-
vec3 scatter_direction = rec.normal + random_unit_vector();
35+
auto scatter_direction = rec.normal + random_unit_vector();
36+
37+
// Catch degenerate scatter direction
38+
if (scatter_direction.near_zero())
39+
scatter_direction = rec.normal;
40+
3641
scattered = ray(rec.p, scatter_direction);
3742
attenuation = albedo;
3843
return true;

src/TheNextWeek/material.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ class lambertian : public material {
3737
virtual bool scatter(
3838
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
3939
) const override {
40-
vec3 scatter_direction = rec.normal + random_unit_vector();
40+
auto scatter_direction = rec.normal + random_unit_vector();
41+
42+
// Catch degenerate scatter direction
43+
if (scatter_direction.near_zero())
44+
scatter_direction = rec.normal;
45+
4146
scattered = ray(rec.p, scatter_direction, r_in.time());
4247
attenuation = albedo->value(rec.u, rec.v, rec.p);
4348
return true;

src/common/vec3.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <iostream>
1616

1717
using std::sqrt;
18+
using std::fabs;
1819

1920
class vec3 {
2021
public:
@@ -55,6 +56,12 @@ class vec3 {
5556
return e[0]*e[0] + e[1]*e[1] + e[2]*e[2];
5657
}
5758

59+
bool near_zero() const {
60+
// Return true if the vector is close to zero in all dimensions.
61+
const auto s = 1e-8;
62+
return (fabs(e[0]) < s) && (fabs(e[1]) < s) && (fabs(e[2]) < s);
63+
}
64+
5865
inline static vec3 random() {
5966
return vec3(random_double(), random_double(), random_double());
6067
}

0 commit comments

Comments
 (0)