Skip to content

Commit 6102f1e

Browse files
trevordblackhollasch
authored andcommitted
Rearranged diffuse chap. to better motivate gamma
1 parent d6ca90b commit 6102f1e

20 files changed

+130
-92
lines changed

books/RayTracingInOneWeekend.html

Lines changed: 130 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,8 +2208,8 @@
22082208
<div class='together'>
22092209
... Indeed we do get rather nice gray spheres:
22102210

2211-
![<span class='num'>Image 7:</span> Rendering of diffuse spheres with hemispherical scattering
2212-
](../images/img-1.10-rand-hemispherical.png class='pixel')
2211+
![<span class='num'>Image 7:</span> First render of a diffuse sphere
2212+
](../images/img-1.07-first-diffuse.png class='pixel')
22132213

22142214
</div>
22152215

@@ -2269,8 +2269,8 @@
22692269

22702270
if (world.hit(r, interval(0, infinity), rec)) {
22712271
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2272-
point3 target = rec.p + rec.normal + random_in_unit_sphere();
2273-
return 0.5 * ray_color(ray(rec.p, target - rec.p), depth-1, world);
2272+
vec3 direction = random_on_hemisphere(rec.normal);
2273+
return 0.5 * ray_color(ray(rec.p, direction), depth-1, world);
22742274
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
22752275
}
22762276

@@ -2308,75 +2308,12 @@
23082308
<div class='together'>
23092309
For this very simple scene we should get basically the same result:
23102310

2311-
![<span class='num'>Image 8:</span> First render of a diffuse sphere
2311+
![<span class='num'>Image 8:</span> Second render of a diffuse sphere with limited bounces
23122312
](../images/img-1.07-first-diffuse.png class='pixel')
23132313

23142314
</div>
23152315

23162316

2317-
Using Gamma Correction for Accurate Color Intensity
2318-
----------------------------------------------------
2319-
Note the shadowing under the sphere. The picture is very dark, but our spheres only absorb half the
2320-
energy of each bounce, so they are 50% reflectors. The spheres should look pretty bright (in real
2321-
life, a light grey) but they appear to be rather dark. The reason for this is that almost all
2322-
computer programs assume that an image is “gamma corrected” before being written into an image
2323-
file. This means that the 0 to 1 values have some transform applied before being stored as a byte.
2324-
Images with data that are written without being transformed are said to be in _linear space_,
2325-
whereas images that are transformed are said to be in _gamma space_. It is likely that the image
2326-
viewer you are using is expecting an image in gamma space, but we are giving it an image in linear
2327-
space. This is the reason why our image appears inaccurately dark.
2328-
2329-
There are many good reasons for why images should be stored in gamma space, but for our purposes we
2330-
just need to be aware of it. We are going to transform our data into gamma space so that our image
2331-
viewer can more accurately display our image. As a simple approximation, we can use “gamma 2” as our
2332-
transform, which is the power that you use when going from gamma space to linear space. We need to
2333-
go from linear space to gamma space, which means taking the inverse of "gamma 2", so the power
2334-
$1/\mathit{gamma}$, which is just the square-root.
2335-
2336-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2337-
inline double linear_to_gamma(double linear_component)
2338-
{
2339-
return sqrt(linear_component);
2340-
}
2341-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2342-
2343-
void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) {
2344-
auto r = pixel_color.x();
2345-
auto g = pixel_color.y();
2346-
auto b = pixel_color.z();
2347-
2348-
// Divide the color by the number of samples.
2349-
auto scale = 1.0 / samples_per_pixel;
2350-
r *= scale;
2351-
g *= scale;
2352-
b *= scale;
2353-
2354-
2355-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2356-
// Apply the linear to gamma transform.
2357-
r = linear_to_gamma(r);
2358-
g = linear_to_gamma(g);
2359-
b = linear_to_gamma(b);
2360-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2361-
2362-
// Write the translated [0,255] value of each color component.
2363-
static const interval intensity(0.000, 0.999);
2364-
out << static_cast<int>(256 * intensity.clamp(r)) << ' '
2365-
<< static_cast<int>(256 * intensity.clamp(g)) << ' '
2366-
<< static_cast<int>(256 * intensity.clamp(b)) << '\n';
2367-
}
2368-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2369-
[Listing [write-color-gamma]: <kbd>[color.h]</kbd> write_color(), with gamma correction]
2370-
2371-
<div class='together'>
2372-
This yields light grey, which is more accurate:
2373-
2374-
![<span class='num'>Image 9:</span> Diffuse sphere, with gamma correction
2375-
](../images/img-1.08-gamma-correct.png class='pixel')
2376-
2377-
</div>
2378-
2379-
23802317
Fixing Shadow Acne
23812318
-------------------
23822319
There’s also a subtle bug that we need to address. A ray will attempt to accurately calculate the
@@ -2405,8 +2342,8 @@
24052342
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
24062343
if (world.hit(r, interval(0.001, infinity), rec)) {
24072344
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2408-
point3 target = rec.p + rec.normal + random_in_unit_sphere();
2409-
return 0.5 * ray_color(ray(rec.p, target - rec.p), depth-1, world);
2345+
vec3 direction = random_on_hemisphere(rec.normal);
2346+
return 0.5 * ray_color(ray(rec.p, direction), depth-1, world);
24102347
}
24112348

24122349
vec3 unit_direction = unit_vector(r.direction());
@@ -2487,7 +2424,7 @@
24872424
<div class='together'>
24882425
After rendering we get a similar image:
24892426

2490-
![<span class='num'>Image 10:</span> Correct rendering of Lambertian spheres
2427+
![<span class='num'>Image 9:</span> Correct rendering of Lambertian spheres
24912428
](../images/img-1.09-correct-lambertian.png class='pixel')
24922429

24932430
</div>
@@ -2496,7 +2433,7 @@
24962433
spheres is so simple, but you should be able to notice two important visual differences:
24972434

24982435
1. The shadows are more pronounced after the change
2499-
2. Both spheres are darker in appearance after the change
2436+
2. Both spheres are tinted blue from the sky after the change
25002437

25012438
Both of these changes are due to the less uniform scattering of the light rays--more rays are
25022439
scattering toward the normal. This means that for diffuse objects, they will appear _darker_
@@ -2510,6 +2447,107 @@
25102447
insight by understanding the effect of different diffuse methods on the lighting of the scene.
25112448

25122449

2450+
Using Gamma Correction for Accurate Color Intensity
2451+
----------------------------------------------------
2452+
Note the shadowing under the sphere. The picture is very dark, but our spheres only absorb half the
2453+
energy of each bounce, so they are 50% reflectors. The spheres should look pretty bright (in real
2454+
life, a light grey) but they appear to be rather dark. We can see this more clearly if we walk
2455+
through the full brightness gamut for our diffuse material. We start by setting the reflectance of
2456+
the `ray_color` function from `0.5` (50%) to `0.1` (10%):
2457+
2458+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2459+
class camera {
2460+
...
2461+
color ray_color(const ray& r, int depth, const hittable& world) const {
2462+
hit_record rec;
2463+
2464+
// If we've exceeded the ray bounce limit, no more light is gathered.
2465+
if (depth <= 0)
2466+
return color(0,0,0);
2467+
2468+
if (world.hit(r, interval(0.001, infinity), rec)) {
2469+
vec3 direction = rec.normal + random_unit_vector();
2470+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2471+
return 0.1 * ray_color(ray(rec.p, direction), depth-1, world);
2472+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2473+
}
2474+
2475+
vec3 unit_direction = unit_vector(r.direction());
2476+
auto a = 0.5*(unit_direction.y() + 1.0);
2477+
return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
2478+
}
2479+
};
2480+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2481+
[Listing [ray-color-gamut]: <kbd>[camera.h]</kbd> ray_color() with 10% reflectance]
2482+
2483+
We render out at this new 10% reflectance. We then set reflectance to 30% and render again. We
2484+
repeat for 50%, 70%, and finally 90%. We can overlay these images from left to right in the photo
2485+
editor of your choice and you should get a very nice visual representation of the increasing
2486+
brightness of your chosen gamut. This is the one that we've been working with so far:
2487+
2488+
![<span class='num'>Image 10:</span> The gamut of our renderer so far
2489+
](../images/img-1.10-linear-gamut.png class='pixel')
2490+
2491+
If you look closely, or if you use a color picker, you should notice that the 50% reflectance
2492+
render (the one in the middle) is far too dark to be half-way between white and black (middle-gray).
2493+
Indeed, the 70% reflector is closer to middle-gray. The reason for this is that almost all
2494+
computer programs assume that an image is “gamma corrected” before being written into an image
2495+
file. This means that the 0 to 1 values have some transform applied before being stored as a byte.
2496+
Images with data that are written without being transformed are said to be in _linear space_,
2497+
whereas images that are transformed are said to be in _gamma space_. It is likely that the image
2498+
viewer you are using is expecting an image in gamma space, but we are giving it an image in linear
2499+
space. This is the reason why our image appears inaccurately dark.
2500+
2501+
There are many good reasons for why images should be stored in gamma space, but for our purposes we
2502+
just need to be aware of it. We are going to transform our data into gamma space so that our image
2503+
viewer can more accurately display our image. As a simple approximation, we can use “gamma 2” as our
2504+
transform, which is the power that you use when going from gamma space to linear space. We need to
2505+
go from linear space to gamma space, which means taking the inverse of "gamma 2", which means an
2506+
exponent of $1/\mathit{gamma}$, which is just the square-root.
2507+
2508+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2509+
inline double linear_to_gamma(double linear_component)
2510+
{
2511+
return sqrt(linear_component);
2512+
}
2513+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2514+
2515+
void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) {
2516+
auto r = pixel_color.x();
2517+
auto g = pixel_color.y();
2518+
auto b = pixel_color.z();
2519+
2520+
// Divide the color by the number of samples.
2521+
auto scale = 1.0 / samples_per_pixel;
2522+
r *= scale;
2523+
g *= scale;
2524+
b *= scale;
2525+
2526+
2527+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
2528+
// Apply the linear to gamma transform.
2529+
r = linear_to_gamma(r);
2530+
g = linear_to_gamma(g);
2531+
b = linear_to_gamma(b);
2532+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
2533+
2534+
// Write the translated [0,255] value of each color component.
2535+
static const interval intensity(0.000, 0.999);
2536+
out << static_cast<int>(256 * intensity.clamp(r)) << ' '
2537+
<< static_cast<int>(256 * intensity.clamp(g)) << ' '
2538+
<< static_cast<int>(256 * intensity.clamp(b)) << '\n';
2539+
}
2540+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2541+
[Listing [write-color-gamma]: <kbd>[color.h]</kbd> write_color(), with gamma correction]
2542+
2543+
2544+
<div class='together'>
2545+
Using this gamma correction, we now get a much more consistent ramp from darkness to lightness:
2546+
2547+
![<span class='num'>Image 11:</span> The gamma-corrected render of two diffuse spheres
2548+
](../images/img-1.11-gamma-gamut.png class='pixel')
2549+
2550+
</div>
25132551

25142552

25152553
Metal
@@ -2840,8 +2878,8 @@
28402878
<div class='together'>
28412879
Which gives:
28422880

2843-
![<span class='num'>Image 11:</span> Shiny metal
2844-
](../images/img-1.11-metal-shiny.png class='pixel')
2881+
![<span class='num'>Image 12:</span> Shiny metal
2882+
](../images/img-1.12-metal-shiny.png class='pixel')
28452883

28462884
</div>
28472885

@@ -2903,8 +2941,8 @@
29032941
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29042942
[Listing [metal-fuzz-spheres]: <kbd>[main.cc]</kbd> Metal spheres with fuzziness]
29052943

2906-
![<span class='num'>Image 12:</span> Fuzzed metal
2907-
](../images/img-1.12-metal-fuzz.png class='pixel')
2944+
![<span class='num'>Image 13:</span> Fuzzed metal
2945+
](../images/img-1.13-metal-fuzz.png class='pixel')
29082946

29092947
</div>
29102948

@@ -2922,8 +2960,8 @@
29222960
there is a refraction ray at all. For this project, I tried to put two glass balls in our scene, and
29232961
I got this (I have not told you how to do this right or wrong yet, but soon!):
29242962

2925-
![<span class='num'>Image 13:</span> Glass first
2926-
](../images/img-1.13-glass-first.png class='pixel')
2963+
![<span class='num'>Image 14:</span> Glass first
2964+
](../images/img-1.14-glass-first.png class='pixel')
29272965

29282966
Is that right? Glass balls look odd in real life. But no, it isn’t right. The world should be
29292967
flipped upside down and no weird black stuff. I just printed out the ray straight through the middle
@@ -3035,8 +3073,8 @@
30353073
<div class='together'>
30363074
This gives us the following result:
30373075

3038-
![<span class='num'>Image 14:</span> Glass sphere that always refracts
3039-
](../images/img-1.14-glass-always-refract.png class='pixel')
3076+
![<span class='num'>Image 15:</span> Glass sphere that always refracts
3077+
](../images/img-1.15-glass-always-refract.png class='pixel')
30403078

30413079
</div>
30423080

@@ -3155,8 +3193,8 @@
31553193
<div class='together'>
31563194
We get:
31573195

3158-
![<span class='num'>Image 15:</span> Glass sphere that sometimes refracts
3159-
](../images/img-1.15-glass-sometimes-refract.png class='pixel')
3196+
![<span class='num'>Image 16:</span> Glass sphere that sometimes refracts
3197+
](../images/img-1.16-glass-sometimes-refract.png class='pixel')
31603198

31613199
</div>
31623200

@@ -3235,8 +3273,8 @@
32353273
<div class='together'>
32363274
This gives:
32373275

3238-
![<span class='num'>Image 16:</span> A hollow glass sphere
3239-
](../images/img-1.16-glass-hollow.png class='pixel')
3276+
![<span class='num'>Image 17:</span> A hollow glass sphere
3277+
](../images/img-1.17-glass-hollow.png class='pixel')
32403278

32413279
</div>
32423280

@@ -3358,8 +3396,8 @@
33583396
<div class='together'>
33593397
This gives us the rendering:
33603398

3361-
![<span class='num'>Image 17:</span> A wide-angle view
3362-
](../images/img-1.17-wide-view.png class='pixel')
3399+
![<span class='num'>Image 18:</span> A wide-angle view
3400+
](../images/img-1.18-wide-view.png class='pixel')
33633401

33643402
</div>
33653403

@@ -3516,8 +3554,8 @@
35163554
<div class='together'>
35173555
to get:
35183556

3519-
![<span class='num'>Image 18:</span> A distant view
3520-
](../images/img-1.18-view-distant.png class='pixel')
3557+
![<span class='num'>Image 19:</span> A distant view
3558+
](../images/img-1.19-view-distant.png class='pixel')
35213559

35223560
</div>
35233561

@@ -3534,7 +3572,7 @@
35343572
<div class='together'>
35353573
to get:
35363574

3537-
![<span class='num'>Image 19:</span> Zooming in](../images/img-1.19-view-zoom.png class='pixel')
3575+
![<span class='num'>Image 20:</span> Zooming in](../images/img-1.20-view-zoom.png class='pixel')
35383576

35393577
</div>
35403578

@@ -3782,8 +3820,8 @@
37823820
<div class='together'>
37833821
We get:
37843822

3785-
![<span class='num'>Image 20:</span> Spheres with depth-of-field
3786-
](../images/img-1.20-depth-of-field.png class='pixel')
3823+
![<span class='num'>Image 21:</span> Spheres with depth-of-field
3824+
](../images/img-1.21-depth-of-field.png class='pixel')
37873825

37883826
</div>
37893827

@@ -3868,7 +3906,7 @@
38683906
<div class='together'>
38693907
This gives:
38703908

3871-
![<span class='num'>Image 21:</span> Final scene](../images/img-1.21-book1-final.jpg)
3909+
![<span class='num'>Image 22:</span> Final scene](../images/img-1.22-book1-final.jpg)
38723910

38733911
</div>
38743912

images/img-1.07-first-diffuse.png

-1.39 KB
Loading

images/img-1.07-first-diffuse.png~

72.6 KB
Binary file not shown.
7.96 KB
Loading
73.9 KB
Binary file not shown.

images/img-1.10-linear-gamut.png

72.6 KB
Loading
-66.8 KB
Binary file not shown.

images/img-1.11-gamma-gamut.png

69.7 KB
Loading
File renamed without changes.

0 commit comments

Comments
 (0)