diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fad42a..a4a7f74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,42 +1,44 @@ Change Log -- Ray Tracing in One Weekend ==================================================================================================== ----------------------------------------------------------------------------------------------------- # v3.1.0 (2020-05-03) +This minor upgrade adds some fixes and changes that are a bit more than just patches. The text now +has subchapter headings to help readers browse content and get a bit more context. We're introducing +new type aliases `point3` and `color` for `vec3` to better indicate the underlying mathematical +types of parameters and variables. Overall, a bunch of small improvements that we'd recommend +adopting, but may warrant comparison with any current projects. + ### Common -- Fix: Scattered improvements to the text - Fix: Include cmath in vec3.h (#501) +- Fix: Scattered improvements to the text - New: Subchapters throughout all three books (#267) - New: Add explanation for padding `aarect` in the zero dimension (#488) - Change: Minor change to use new `point3` and `color` type aliases for `vec3` (#422) - Change: Renamed `constant_texture` to `solid_color`, add RGB constructor (#452) +- Change: Moved `vec3::write_color()` method to utility function in `color.h` header (#502) +- Change: Switch from `ffmin`/`ffmax` to standard `fmin`/`fmax` (#444, #491) +- Change: Math notation to bold uppercase points, bold lowercase no-barb vectors (#412) - Change: Books use Markdeep's image class=pixel for rendered image fidelity (#498) ### _In One Weekend_ +- Fix: Improve image size and aspect ratio calculation to make size changes easier +- Fix: Added `t` parameter back into `hit_record` at correct place +- Fix: image basic vectors off by one - Fix: Update image and size for first PPM image - Fix: Update image and size for blue-to-white gradient image - Fix: Update image and size for simple red sphere render - Fix: Update image and size for sphere with normal-vector coloring -- Fix: Improve image size and aspect ratio calculation to make size changes easier -- Fix: Added `t` parameter back into `hit_record` at correct place -- Fix: image basic vectors off by one -- Change: First image size changed to 256x256 -- Change: Default image sizes changed from 200x100 to 384x216 +- Fix: Correct typo in "What's next?" list to rejoin split paragraph on "Lights." Adjust numbering + in rest of list. - Change: Define image aspect ratio up front, then image height from that and the image width +- Change: Default image sizes changed from 200x100 to 384x216 +- Change: First image size changed to 256x256 ### _The Next Week_ - Change: Large rewrite of the `image_texture` class. Now handles image loading too. (#434) ---------------------------------------------------------------------------------------------------- -# v3.0.3 (in progress) - -### _In One Weekend_ -- Fix: Correct typo in "What's next?" list to rejoin split paragraph on "Lights." Adjust numbering - in rest of list. - - ---------------------------------------------------------------------------------------------------- # v3.0.2 (2020-04-11) diff --git a/books/RayTracingInOneWeekend.html b/books/RayTracingInOneWeekend.html index a9048a01..8e28d8c6 100644 --- a/books/RayTracingInOneWeekend.html +++ b/books/RayTracingInOneWeekend.html @@ -137,11 +137,7 @@ Opening the output file (in `ToyViewer` on my Mac, but try it in your favorite viewer and Google “ppm viewer” if your viewer doesn’t support it) shows this result: -
- - ![First PPM image](../images/img.first-ppm-image.png) - -
+ ![First PPM image](../images/img.first-ppm-image.png class=pixel) @@ -234,8 +230,11 @@ #ifndef VEC3_H #define VEC3_H + #include #include + using std::sqrt; + class vec3 { public: vec3() : e{0,0,0} {} @@ -275,13 +274,6 @@ return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; } - void write_color(std::ostream &out) { - // Write the translated [0,255] value of each color component. - out << static_cast(255.999 * e[0]) << ' ' - << static_cast(255.999 * e[1]) << ' ' - << static_cast(255.999 * e[2]) << '\n'; - } - public: double e[3]; }; @@ -352,10 +344,38 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [vec3-utility]: [vec3.h] vec3 utility functions] + +Color Utility Functions +------------------------ +Using our new `vec3` class, we'll create a utility function to write a single pixel's color out to +the standard output stream. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + #ifndef COLOR_H + #define COLOR_H + + #include "vec3.h" + + #include + + void write_color(std::ostream &out, color pixel_color) { + // Write the translated [0,255] value of each color component. + out << static_cast(255.999 * pixel_color.x()) << ' ' + << static_cast(255.999 * pixel_color.y()) << ' ' + << static_cast(255.999 * pixel_color.z()) << '\n'; + } + + #endif + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + [Listing [color]: [color.h] color utility functions] + + +
Now we can change our main to use this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + #include "color.h" #include "vec3.h" #include @@ -371,7 +391,7 @@ for (int i = 0; i < image_width; ++i) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight color pixel_color(double(i)/(image_width-1), double(j)/(image_height-1), 0.25); - pixel_color.write_color(std::cout); + write_color(std::cout, pixel_color); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ } } @@ -492,7 +512,7 @@ ray r(origin, lower_left_corner + u*horizontal + v*vertical); color pixel_color = ray_color(r); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - pixel_color.write_color(std::cout); + write_color(std::cout, pixel_color); } } @@ -516,11 +536,8 @@ with $t$ going from zero to one. In our case this produces: -
- - ![A blue-to-white gradient depending on ray Y coordinate](../images/img.blue-to-white.png) - -
+ ![A blue-to-white gradient depending on ray Y coordinate + ](../images/img.blue-to-white.png class=pixel)
@@ -634,11 +651,7 @@
What we get is this: -
- - ![A simple red sphere](../images/img.red-sphere.png) - -
+ ![A simple red sphere](../images/img.red-sphere.png class=pixel)
@@ -714,11 +727,7 @@
And that yields this picture: -
- - ![A sphere colored according to its normal vectors](../images/img.normals-sphere.png) - -
+ ![A sphere colored according to its normal vectors](../images/img.normals-sphere.png class=pixel)
@@ -877,7 +886,8 @@ the sphere, the normal will point outward, but if the ray is inside the sphere, the normal will point inward. - ![Figure [normal-directions]: Possible directions for sphere surface-normal geometry](../images/fig.normal-possibilities.jpg) + ![Figure [normal-directions]: Possible directions for sphere surface-normal geometry + ](../images/fig.normal-possibilities.jpg) @@ -1134,6 +1144,7 @@ using std::shared_ptr; using std::make_shared; + using std::sqrt; // Constants @@ -1146,9 +1157,6 @@ return degrees * pi / 180; } - inline double ffmin(double a, double b) { return a <= b ? a : b; } - inline double ffmax(double a, double b) { return a >= b ? a : b; } - // Common Headers #include "ray.h" @@ -1217,7 +1225,7 @@ color pixel_color = ray_color(r, world); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - pixel_color.write_color(std::cout); + write_color(std::cout, pixel_color); } } @@ -1231,11 +1239,8 @@ This yields a picture that is really just a visualization of where the spheres are along with their surface normal. This is often a great way to look at your model for flaws and characteristics. -
- - ![Resulting render of normals-colored sphere with ground](../images/img.normals-sphere-ground.png) - -
+ ![Resulting render of normals-colored sphere with ground + ](../images/img.normals-sphere-ground.png class=pixel) @@ -1344,7 +1349,7 @@ [Listing [camera-initial]: [camera.h] The camera class] -To handle the multi-sampled color computation, we update the `vec3::write_color()` function. Rather +To handle the multi-sampled color computation, we'll update the `write_color()` function. Rather than adding in a fractional contribution each time we accumulate more light to the color, just add the full color each iteration, and then perform a single divide at the end (by the number of samples) when writing out the color. In addition, we'll add a handy utility function to the @@ -1360,12 +1365,16 @@ [Listing [clamp]: [rtweekend.h] The clamp() utility function] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - void write_color(std::ostream &out, int samples_per_pixel) { + void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + // Divide the color total by the number of samples. auto scale = 1.0 / samples_per_pixel; - auto r = scale * e[0]; - auto g = scale * e[1]; - auto b = scale * e[2]; + r *= scale; + g *= scale; + b *= scale; // Write the translated [0,255] value of each color component. out << static_cast(256 * clamp(r, 0.0, 0.999)) << ' ' @@ -1373,7 +1382,7 @@ << static_cast(256 * clamp(b, 0.0, 0.999)) << '\n'; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - [Listing [write-color-clamped]: [vec3.h] The write_color() function] + [Listing [write-color-clamped]: [color.h] The multi-sample write_color() function]
Main is also changed: @@ -1407,7 +1416,7 @@ ray r = cam.get_ray(u, v); pixel_color += ray_color(r, world); } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ } } @@ -1422,11 +1431,7 @@ Zooming into the image that is produced, the big change is in edge pixels that are part background and part foreground: -
- - ![Close-up of antialiased pixels](../images/img.antialias.png) - -
+ ![Close-up of antialiased pixels](../images/img.antialias.png class=pixel)
@@ -1588,7 +1593,7 @@ pixel_color += ray_color(r, world, max_depth); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); } } @@ -1601,11 +1606,7 @@
This gives us: -
- - ![First render of a diffuse sphere](../images/img.first-diffuse.jpg) - -
+ ![First render of a diffuse sphere](../images/img.first-diffuse.jpg class=pixel)
@@ -1623,14 +1624,17 @@ square-root: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - void write_color(std::ostream &out, int samples_per_pixel) { + void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight - // Divide the color total by the number of samples and gamma-correct - // for a gamma value of 2.0. + // Divide the color total by the number of samples and gamma-correct for gamma=2.0. auto scale = 1.0 / samples_per_pixel; - auto r = sqrt(scale * e[0]); - auto g = sqrt(scale * e[1]); - auto b = sqrt(scale * e[2]); + r = sqrt(scale * r); + g = sqrt(scale * g); + b = sqrt(scale * b); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ // Write the translated [0,255] value of each color component. @@ -1639,18 +1643,14 @@ << static_cast(256 * clamp(b, 0.0, 0.999)) << '\n'; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - [Listing [write-color-gamma]: [vec3.h] write_color(), with gamma correction] + [Listing [write-color-gamma]: [color.h] write_color(), with gamma correction]
That yields light grey, as we desire: -
- - ![Diffuse sphere, with gamma correction](../images/img.gamma-correct.jpg) - -
+ ![Diffuse sphere, with gamma correction](../images/img.gamma-correct.jpg class=pixel)
@@ -1730,11 +1730,7 @@
After rendering we get a similar image: -
- - ![Correct rendering of Lambertian spheres](../images/img.correct-lambertian.png) - -
+ ![Correct rendering of Lambertian spheres](../images/img.correct-lambertian.png class=pixel) It's hard to tell the difference between these two diffuse methods, given that our scene of two spheres is so simple, but you should be able to notice two important visual differences: @@ -1813,11 +1809,8 @@ Gives us the following image: -
- - ![Rendering of diffuse spheres with hemispherical scattering](../images/img.rand-hemispherical.png) - -
+ ![Rendering of diffuse spheres with hemispherical scattering + ](../images/img.rand-hemispherical.png class=pixel)
@@ -2127,7 +2120,7 @@ ray r = cam.get_ray(u, v); pixel_color += ray_color(r, world, max_depth); } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); } } @@ -2140,11 +2133,7 @@
Which gives: -
- - ![Shiny metal](../images/img.metal-shiny.png) - -
+ ![Shiny metal](../images/img.metal-shiny.png class=pixel)
@@ -2197,11 +2186,7 @@
We can try that out by adding fuzziness 0.3 and 1.0 to the metals: -
- - ![Fuzzed metal](../images/img.metal-fuzz.png) - -
+ ![Fuzzed metal](../images/img.metal-fuzz.png class=pixel)
@@ -2222,11 +2207,7 @@ there is a refraction ray at all. For this project, I tried to put two glass balls in our scene, and I got this (I have not told you how to do this right or wrong yet, but soon!): -
- - ![Glass first](../images/img.glass-first.png) - -
+ ![Glass first](../images/img.glass-first.png class=pixel) @@ -2330,11 +2311,7 @@ This gives us the following result: -
- - ![Glass sphere that always refracts](../images/img.glass-always-refract.png) - -
+ ![Glass sphere that always refracts](../images/img.glass-always-refract.png class=pixel) Total Internal Reflection @@ -2385,7 +2362,7 @@ $$ \cos\theta = \mathbf{R} \cdot \mathbf{n} $$ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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) { // Must Reflect @@ -2416,7 +2393,7 @@ vec3 unit_direction = unit_vector(r_in.direction()); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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); @@ -2455,11 +2432,7 @@ We get: -
- - ![Glass sphere that sometimes refracts](../images/img.glass-sometimes-refract.png) - -
+ ![Glass sphere that sometimes refracts](../images/img.glass-sometimes-refract.png class=pixel) @@ -2495,7 +2468,7 @@ double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : (ref_idx); vec3 unit_direction = unit_vector(r_in.direction()); - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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); @@ -2544,11 +2517,7 @@
This gives: -
- - ![A hollow glass sphere](../images/img.glass-hollow.png) - -
+ ![A hollow glass sphere](../images/img.glass-hollow.png class=pixel)
@@ -2625,11 +2594,7 @@ gives: -
- - ![A wide-angle view](../images/img.wide-view.png) - -
+ ![A wide-angle view](../images/img.wide-view.png class=pixel) @@ -2713,11 +2678,7 @@ to get: -
- - ![A distant view](../images/img.view-distant.png) - -
+ ![A distant view](../images/img.view-distant.png class=pixel) And we can change field of view: @@ -2728,11 +2689,7 @@ to get: -
- - ![Zooming in](../images/img.view-zoom.png) - -
+ ![Zooming in](../images/img.view-zoom.png class=pixel) @@ -2874,11 +2831,7 @@ We get: -
- - ![Spheres with depth-of-field](../images/img.depth-of-field.png) - -
+ ![Spheres with depth-of-field](../images/img.depth-of-field.png class=pixel) @@ -2955,11 +2908,8 @@
This gives: -
- ![Final scene](../images/img.book1-final.jpg) -
An interesting thing you might note is the glass balls don’t really have shadows which makes them diff --git a/books/RayTracingTheNextWeek.html b/books/RayTracingTheNextWeek.html index d55af51a..eb6aa558 100644 --- a/books/RayTracingTheNextWeek.html +++ b/books/RayTracingTheNextWeek.html @@ -41,12 +41,12 @@ Motion Blur ==================================================================================================== -When you decided to ray trace, you decided visual quality was worth more run-time. In your fuzzy -reflection and defocus blur you needed multiple samples per pixel. Once you have taken a step down -that road, the good news is that almost all effects can be brute-forced. Motion blur is certainly -one of those. In a real camera, the shutter opens and stays open for a time interval, and the camera -and objects may move during that time. Its really an average of what the camera sees over that -interval that we want. +When you decided to ray trace, you decided that visual quality was worth more than run-time. In your +fuzzy reflection and defocus blur you needed multiple samples per pixel. Once you have taken a step +down that road, the good news is that almost all effects can be brute-forced. Motion blur is +certainly one of those. In a real camera, the shutter opens and stays open for a time interval, and +the camera and objects may move during that time. Its really an average of what the camera sees over +that interval that we want. Introduction of SpaceTime Ray Tracing @@ -352,11 +352,7 @@ gives the following result: -
- - ![Bouncing spheres](../images/img.bouncing-spheres.jpg) - -
+ ![Bouncing spheres](../images/img.bouncing-spheres.jpg class=pixel) @@ -584,12 +580,12 @@ bool hit(const ray& r, double tmin, double tmax) const { for (int a = 0; a < 3; a++) { - auto t0 = ffmin((_min[a] - r.origin()[a]) / r.direction()[a], - (_max[a] - r.origin()[a]) / r.direction()[a]); - auto t1 = ffmax((_min[a] - r.origin()[a]) / r.direction()[a], - (_max[a] - r.origin()[a]) / r.direction()[a]); - tmin = ffmax(t0, tmin); - tmax = ffmin(t1, tmax); + auto t0 = fmin((_min[a] - r.origin()[a]) / r.direction()[a], + (_max[a] - r.origin()[a]) / r.direction()[a]); + auto t1 = fmax((_min[a] - r.origin()[a]) / r.direction()[a], + (_max[a] - r.origin()[a]) / r.direction()[a]); + tmin = fmax(t0, tmin); + tmax = fmin(t1, tmax); if (tmax <= tmin) return false; } @@ -603,10 +599,6 @@ [Listing [aabb]: [aabb.h] Axis-aligned bounding box class] -Note that we use the simple custom `ffmax()` function (defined in `rtweekend.h`) instead of the C++ -standard library `fmax()` utility. `ffmax()` is quite a bit faster because it doesn’t worry about -`NaN`s and other exceptions. - An Optimized AABB Hit Method ----------------------------- @@ -722,13 +714,13 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ aabb surrounding_box(aabb box0, aabb box1) { - point3 small(ffmin(box0.min().x(), box1.min().x()), - ffmin(box0.min().y(), box1.min().y()), - ffmin(box0.min().z(), box1.min().z())); + point3 small(fmin(box0.min().x(), box1.min().x()), + fmin(box0.min().y(), box1.min().y()), + fmin(box0.min().z(), box1.min().z())); - point3 big(ffmax(box0.max().x(), box1.max().x()), - ffmax(box0.max().y(), box1.max().y()), - ffmax(box0.max().z(), box1.max().z())); + point3 big(fmax(box0.max().x(), box1.max().x()), + fmax(box0.max().y(), box1.max().y()), + fmax(box0.max().z(), box1.max().z())); return aabb(small,big); } @@ -1058,11 +1050,7 @@ We get: -
- - ![Spheres on checkered ground](../images/img.checker-ground.jpg) - -
+ ![Spheres on checkered ground](../images/img.checker-ground.jpg class=pixel) @@ -1105,11 +1093,7 @@ We get: -
- - ![Checkered spheres](../images/img.checker-spheres.jpg) - -
+ ![Checkered spheres](../images/img.checker-spheres.jpg class=pixel) @@ -1122,19 +1106,11 @@ To get cool looking solid textures most people use some form of Perlin noise. These are named after their inventor Ken Perlin. Perlin texture doesn’t return white noise like this: -
- - ![White noise](../images/img.white-noise.jpg) - -
+ ![White noise](../images/img.white-noise.jpg class=pixel) Instead it returns something similar to blurred white noise: -
- - ![White noise, blurred](../images/img.white-noise-blur.jpg) - -
+ ![White noise, blurred](../images/img.white-noise-blur.jpg class=pixel) @@ -1150,11 +1126,7 @@ We could just tile all of space with a 3D array of random numbers and use them in blocks. You get something blocky where the repeating is clear: -
- - ![Tiled random patterns](../images/img.tile-random.jpg) - -
+ ![Tiled random patterns](../images/img.tile-random.jpg class=pixel) @@ -1284,11 +1256,7 @@
Add the hashing does scramble as hoped: -
- - ![Hashed random texture](../images/img.hash-random.jpg) - -
+ ![Hashed random texture](../images/img.hash-random.jpg class=pixel)
@@ -1343,11 +1311,7 @@
And we get: -
- - ![Perlin texture with trilinear interpolation](../images/img.perlin-trilerp.jpg) - -
+ ![Perlin texture with trilinear interpolation](../images/img.perlin-trilerp.jpg class=pixel)
@@ -1385,11 +1349,8 @@
This gives a smoother looking image: -
- - ![Perlin texture, trilinearly interpolated, smoothed](../images/img.perlin-tlerp-smooth.jpg) - -
+ ![Perlin texture, trilinearly interpolated, smoothed + ](../images/img.perlin-tlerp-smooth.jpg class=pixel)
@@ -1420,11 +1381,7 @@ which gives: -
- - ![Perlin texture, higher frequency](../images/img.perlin-hifreq.jpg) - -
+ ![Perlin texture, higher frequency](../images/img.perlin-hifreq.jpg class=pixel) @@ -1564,11 +1521,7 @@
This finally gives something more reasonable looking: -
- - ![Perlin texture, shifted off integer values](../images/img.perlin-shift.jpg) - -
+ ![Perlin texture, shifted off integer values](../images/img.perlin-shift.jpg class=pixel)
@@ -1607,11 +1560,7 @@
Used directly, turbulence gives a sort of camouflage netting appearance: -
- - ![Perlin texture with turbulence](../images/img.perlin-turb.jpg) - -
+ ![Perlin texture with turbulence](../images/img.perlin-turb.jpg class=pixel)
@@ -1646,11 +1595,7 @@ Which yields: -
- - ![Perlin noise, marbled texture](../images/img.perlin-marble.jpg) - -
+ ![Perlin noise, marbled texture](../images/img.perlin-marble.jpg class=pixel) @@ -1827,11 +1772,7 @@
I just grabbed a random earth map from the web -- any standard projection will do for our purposes. -
- - ![earthmap.jpg](../images/earthmap.jpg) - -
+ ![earthmap.jpg](../images/earthmap.jpg class=pixel)
@@ -1857,11 +1798,7 @@ To test this, assign it to a sphere, and then temporarily cripple the `ray_color()` function in main to just return attenuation. You should get something like: -
- - ![Earth-mapped sphere](../images/img.earth-sphere.jpg) - -
+ ![Earth-mapped sphere](../images/img.earth-sphere.jpg class=pixel) @@ -2084,11 +2021,7 @@
We get: -
- - ![Scene with rectangle light source](../images/img.rect-light.jpg) - -
+ ![Scene with rectangle light source](../images/img.rect-light.jpg class=pixel)
@@ -2097,11 +2030,7 @@
Fool around with making some spheres lights too. -
- - ![Scene with rectangle and sphere light sources](../images/img.rect-sph-light.jpg) - -
+ ![Scene with rectangle and sphere light sources](../images/img.rect-sph-light.jpg class=pixel)
@@ -2252,11 +2181,7 @@
We get: -
- - ![Empty Cornell box](../images/img.cornell-first.jpg) - -
+ ![Empty Cornell box](../images/img.cornell-first.jpg class=pixel)
@@ -2330,11 +2255,7 @@
And voila: -
- - ![Empty Cornell box with fixed walls](../images/img.cornell-empty.jpg) - -
+ ![Empty Cornell box with fixed walls](../images/img.cornell-empty.jpg class=pixel)
@@ -2402,11 +2323,7 @@
This gives: -
- - ![Cornell box with two blocks](../images/img.cornell-blocks.jpg) - -
+ ![Cornell box with two blocks](../images/img.cornell-blocks.jpg class=pixel)
@@ -2564,8 +2481,8 @@ vec3 tester(newx, y, newz); for (int c = 0; c < 3; c++) { - min[c] = ffmin(min[c], tester[c]); - max[c] = ffmax(max[c], tester[c]); + min[c] = fmin(min[c], tester[c]); + max[c] = fmax(max[c], tester[c]); } } } @@ -2634,11 +2551,7 @@
Which yields: -
- - ![Standard Cornell box scene](../images/img.cornell-box.jpg) - -
+ ![Standard Cornell box scene](../images/img.cornell-box.jpg class=pixel)
@@ -2832,11 +2745,7 @@
We get: -
- - ![Cornell box with blocks of smoke](../images/img.cornell-smoke.jpg) - -
+ ![Cornell box with blocks of smoke](../images/img.cornell-smoke.jpg class=pixel)
@@ -2924,12 +2833,8 @@
Running it with 10,000 rays per pixel yields: -
- ![Final scene](../images/img.book2-final.jpg) -
-
Now go off and make a really cool image of your own! See https://in1weekend.blogspot.com/ for diff --git a/books/RayTracingTheRestOfYourLife.html b/books/RayTracingTheRestOfYourLife.html index 25f3f0f6..98c99d68 100644 --- a/books/RayTracingTheRestOfYourLife.html +++ b/books/RayTracingTheRestOfYourLife.html @@ -798,11 +798,7 @@
At 500×500 my code produces this image in 10min on 1 core of my Macbook: -
- - ![Cornell box, refactored](../images/img.cornell-refactor1.jpg) - -
+ ![Cornell box, refactored](../images/img.cornell-refactor1.jpg class=pixel) Reducing that noise is our goal. We’ll do that by constructing a PDF that sends more rays to the light. @@ -945,11 +941,7 @@
And again I _should_ get the same picture except with different variance, but I don’t! -
- - ![Cornell box, with different sampling strategy](../images/img.cornell-refactor2.jpg) - -
+ ![Cornell box, with different sampling strategy](../images/img.cornell-refactor2.jpg class=pixel)
@@ -1317,11 +1309,8 @@
Which produces: -
- - ![Cornell box, with orthonormal basis scatter function](../images/img.cornell-ortho.jpg) - -
+ ![Cornell box, with orthonormal basis scatter function + ](../images/img.cornell-ortho.jpg class=pixel) Is that right? We still don’t know for sure. Tracking down bugs is hard in the absence of reliable reference solutions. Let’s table that for now and get rid of some of that noise. @@ -1426,11 +1415,8 @@
With 10 samples per pixel this yields: -
- - ![Cornell box, sampling only the light, 10 samples per pixel](../images/img.cornell-samplight.jpg) - -
+ ![Cornell box, sampling only the light, 10 samples per pixel + ](../images/img.cornell-samplight.jpg class=pixel)
@@ -1461,11 +1447,8 @@
We also need to flip the light so its normals point in the -y direction. This gives us: -
- - ![Cornell box, light emitted only in the downward direction](../images/img.cornell-lightdown.jpg) - -
+ ![Cornell box, light emitted only in the downward direction + ](../images/img.cornell-lightdown.jpg class=pixel)
@@ -1614,11 +1597,7 @@ This yields an apparently matching result so all we’ve done so far is refactor where `pdf` is computed: -
- - ![Cornell box with a cosine density _pdf_](../images/img.cornell-cospdf.jpg) - -
+ ![Cornell box with a cosine density _pdf_](../images/img.cornell-cospdf.jpg class=pixel)
@@ -1746,11 +1725,8 @@
At 10 samples per pixel we get: -
- - ![Cornell box, sampling a hittable light, 10 samples per pixel](../images/img.cornell-samphit.jpg) - -
+ ![Cornell box, sampling a hittable light, 10 samples per pixel + ](../images/img.cornell-samphit.jpg class=pixel)
@@ -1841,11 +1817,8 @@
1000 samples per pixel yields: -
- - ![Cornell box, mixture density of cosine and light sampling](../images/img.cornell-coslight.jpg) - -
+ ![Cornell box, mixture density of cosine and light sampling + ](../images/img.cornell-coslight.jpg class=pixel)
@@ -2176,11 +2149,7 @@ The resulting image has a noisy reflection on the ceiling because the directions toward the box are not sampled with more density. -
- - ![Cornell box with arbitrary PDF functions](../images/img.cornell-flexpdf.jpg) - -
+ ![Cornell box with arbitrary PDF functions](../images/img.cornell-flexpdf.jpg class=pixel)
@@ -2325,11 +2294,8 @@ sampling the light did for my code. This is probably because those rays that hit the glass are expensive! -
- - ![Cornell box with glass sphere, using new PDF functions](../images/img.cornell-glass.jpg) - -
+ ![Cornell box with glass sphere, using new PDF functions + ](../images/img.cornell-glass.jpg class=pixel) @@ -2375,11 +2341,8 @@
And we get a decent image with 1000 samples as before: -
- - ![Cornell box, using a mixture of glass & light PDFs](../images/img.cornell-glasslight.jpg) - -
+ ![Cornell box, using a mixture of glass & light PDFs + ](../images/img.cornell-glasslight.jpg class=pixel)
@@ -2404,24 +2367,28 @@ So big decision: sweep this bug under the rug and check for `NaN`s, or just kill `NaN`s and hope this doesn't come back to bite us later. I will always opt for the lazy strategy, especially when I know floating point is hard. First, how do we check for a `NaN`? The one thing I always remember -for `NaN`s is that a `NaN` does not equal itself. Using this trick, we update the -`vec3::write_color()` function to replace any NaN components with zero: +for `NaN`s is that a `NaN` does not equal itself. Using this trick, we update the `write_color()` +function to replace any NaN components with zero: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - void write_color(std::ostream &out, int samples_per_pixel) { + void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight - // Replace NaN component values with zero. - if (e[0] != e[0]) e[0] = 0.0; - if (e[1] != e[1]) e[1] = 0.0; - if (e[2] != e[2]) e[2] = 0.0; + // Replace NaN components with zero. See explanation in Ray Tracing: The Rest of Your Life. + if (r != r) r = 0.0; + if (g != g) g = 0.0; + if (b != b) b = 0.0; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ - // Divide the color total by the number of samples and gamma-correct - // for a gamma value of 2.0. + // Divide the color by the number of samples and gamma-correct for gamma=2.0. auto scale = 1.0 / samples_per_pixel; - auto r = sqrt(scale * e[0]); - auto g = sqrt(scale * e[1]); - auto b = sqrt(scale * e[2]); + r = sqrt(scale * r); + g = sqrt(scale * g); + b = sqrt(scale * b); // Write the translated [0,255] value of each color component. out << static_cast(256 * clamp(r, 0.0, 0.999)) << ' ' @@ -2429,17 +2396,13 @@ << static_cast(256 * clamp(b, 0.0, 0.999)) << '\n'; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - [Listing [write-color-nan]: [vec3.h] NaN-tolerant write_color function] + [Listing [write-color-nan]: [color.h] NaN-tolerant write_color function]
Happily, the black specks are gone: -
- - ![Cornell box with anti-acne color function](../images/img.book3-final.jpg) - -
+ ![Cornell box with anti-acne color function](../images/img.book3-final.jpg class=pixel)
diff --git a/src/InOneWeekend/main.cc b/src/InOneWeekend/main.cc index 8b9db1b7..86f271b1 100644 --- a/src/InOneWeekend/main.cc +++ b/src/InOneWeekend/main.cc @@ -12,6 +12,7 @@ #include "rtweekend.h" #include "camera.h" +#include "color.h" #include "hittable_list.h" #include "material.h" #include "sphere.h" @@ -118,7 +119,7 @@ int main() { ray r = cam.get_ray(u, v); pixel_color += ray_color(r, world, max_depth); } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); } } diff --git a/src/InOneWeekend/material.h b/src/InOneWeekend/material.h index 3a2ba8b2..9b1c4ef6 100644 --- a/src/InOneWeekend/material.h +++ b/src/InOneWeekend/material.h @@ -43,7 +43,7 @@ class dielectric : public material { double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : (ref_idx); vec3 unit_direction = unit_vector(r_in.direction()); - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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); diff --git a/src/TheNextWeek/hittable.h b/src/TheNextWeek/hittable.h index 06a838d9..5cffe4ae 100644 --- a/src/TheNextWeek/hittable.h +++ b/src/TheNextWeek/hittable.h @@ -149,8 +149,8 @@ rotate_y::rotate_y(shared_ptr p, double angle) : ptr(p) { vec3 tester(newx, y, newz); for (int c = 0; c < 3; c++) { - min[c] = ffmin(min[c], tester[c]); - max[c] = ffmax(max[c], tester[c]); + min[c] = fmin(min[c], tester[c]); + max[c] = fmax(max[c], tester[c]); } } } diff --git a/src/TheNextWeek/main.cc b/src/TheNextWeek/main.cc index d5da711c..f371c572 100644 --- a/src/TheNextWeek/main.cc +++ b/src/TheNextWeek/main.cc @@ -14,6 +14,7 @@ #include "box.h" #include "bvh.h" #include "camera.h" +#include "color.h" #include "constant_medium.h" #include "hittable_list.h" #include "material.h" @@ -447,7 +448,7 @@ int main() { ray r = cam.get_ray(u, v); pixel_color += ray_color(r, background, world, max_depth); } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); } } diff --git a/src/TheNextWeek/material.h b/src/TheNextWeek/material.h index 730dd1d8..3470c759 100644 --- a/src/TheNextWeek/material.h +++ b/src/TheNextWeek/material.h @@ -47,7 +47,7 @@ class dielectric : public material { double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : (ref_idx); vec3 unit_direction = unit_vector(r_in.direction()); - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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); diff --git a/src/TheRestOfYourLife/hittable.h b/src/TheRestOfYourLife/hittable.h index ee68c009..aeec5f82 100644 --- a/src/TheRestOfYourLife/hittable.h +++ b/src/TheRestOfYourLife/hittable.h @@ -157,8 +157,8 @@ rotate_y::rotate_y(shared_ptr p, double angle) : ptr(p) { vec3 tester(newx, y, newz); for (int c = 0; c < 3; c++) { - min[c] = ffmin(min[c], tester[c]); - max[c] = ffmax(max[c], tester[c]); + min[c] = fmin(min[c], tester[c]); + max[c] = fmax(max[c], tester[c]); } } } diff --git a/src/TheRestOfYourLife/main.cc b/src/TheRestOfYourLife/main.cc index 7a120ea0..34e675fe 100644 --- a/src/TheRestOfYourLife/main.cc +++ b/src/TheRestOfYourLife/main.cc @@ -14,6 +14,7 @@ #include "aarect.h" #include "box.h" #include "camera.h" +#include "color.h" #include "hittable_list.h" #include "material.h" #include "sphere.h" @@ -127,7 +128,7 @@ int main() { ray r = cam.get_ray(u, v); pixel_color += ray_color(r, background, world, lights, max_depth); } - pixel_color.write_color(std::cout, samples_per_pixel); + write_color(std::cout, pixel_color, samples_per_pixel); } } diff --git a/src/TheRestOfYourLife/material.h b/src/TheRestOfYourLife/material.h index ca1d01ea..42a077f9 100644 --- a/src/TheRestOfYourLife/material.h +++ b/src/TheRestOfYourLife/material.h @@ -68,7 +68,7 @@ class dielectric : public material { double etai_over_etat = (rec.front_face) ? (1.0 / ref_idx) : (ref_idx); vec3 unit_direction = unit_vector(r_in.direction()); - double cos_theta = ffmin(dot(-unit_direction, rec.normal), 1.0); + 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); diff --git a/src/common/aabb.h b/src/common/aabb.h index b81fbc16..d126668f 100644 --- a/src/common/aabb.h +++ b/src/common/aabb.h @@ -24,12 +24,12 @@ class aabb { bool hit(const ray& r, double tmin, double tmax) const { for (int a = 0; a < 3; a++) { - auto t0 = ffmin((_min[a] - r.origin()[a]) / r.direction()[a], - (_max[a] - r.origin()[a]) / r.direction()[a]); - auto t1 = ffmax((_min[a] - r.origin()[a]) / r.direction()[a], - (_max[a] - r.origin()[a]) / r.direction()[a]); - tmin = ffmax(t0, tmin); - tmax = ffmin(t1, tmax); + auto t0 = fmin((_min[a] - r.origin()[a]) / r.direction()[a], + (_max[a] - r.origin()[a]) / r.direction()[a]); + auto t1 = fmax((_min[a] - r.origin()[a]) / r.direction()[a], + (_max[a] - r.origin()[a]) / r.direction()[a]); + tmin = fmax(t0, tmin); + tmax = fmin(t1, tmax); if (tmax <= tmin) return false; } @@ -61,13 +61,13 @@ class aabb { }; aabb surrounding_box(aabb box0, aabb box1) { - vec3 small(ffmin(box0.min().x(), box1.min().x()), - ffmin(box0.min().y(), box1.min().y()), - ffmin(box0.min().z(), box1.min().z())); + vec3 small(fmin(box0.min().x(), box1.min().x()), + fmin(box0.min().y(), box1.min().y()), + fmin(box0.min().z(), box1.min().z())); - vec3 big (ffmax(box0.max().x(), box1.max().x()), - ffmax(box0.max().y(), box1.max().y()), - ffmax(box0.max().z(), box1.max().z())); + vec3 big (fmax(box0.max().x(), box1.max().x()), + fmax(box0.max().y(), box1.max().y()), + fmax(box0.max().z(), box1.max().z())); return aabb(small,big); } diff --git a/src/common/color.h b/src/common/color.h new file mode 100644 index 00000000..41a75f46 --- /dev/null +++ b/src/common/color.h @@ -0,0 +1,42 @@ +#ifndef COLOR_H +#define COLOR_H +//============================================================================================== +// Originally written in 2020 by Peter Shirley +// +// To the extent possible under law, the author(s) have dedicated all copyright and related and +// neighboring rights to this software to the public domain worldwide. This software is +// distributed without any warranty. +// +// You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication +// along with this software. If not, see . +//============================================================================================== + +#include "vec3.h" + +#include + + +void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) { + auto r = pixel_color.x(); + auto g = pixel_color.y(); + auto b = pixel_color.z(); + + // Replace NaN components with zero. See explanation in Ray Tracing: The Rest of Your Life. + if (r != r) r = 0.0; + if (g != g) g = 0.0; + if (b != b) b = 0.0; + + // Divide the color by the number of samples and gamma-correct for gamma=2.0. + auto scale = 1.0 / samples_per_pixel; + r = sqrt(scale * r); + g = sqrt(scale * g); + b = sqrt(scale * b); + + // Write the translated [0,255] value of each color component. + out << static_cast(256 * clamp(r, 0.0, 0.999)) << ' ' + << static_cast(256 * clamp(g, 0.0, 0.999)) << ' ' + << static_cast(256 * clamp(b, 0.0, 0.999)) << '\n'; +} + + +#endif diff --git a/src/common/rtweekend.h b/src/common/rtweekend.h index 6cda44c8..0bd5a2f7 100644 --- a/src/common/rtweekend.h +++ b/src/common/rtweekend.h @@ -19,6 +19,7 @@ using std::shared_ptr; using std::make_shared; +using std::sqrt; // Constants @@ -31,9 +32,6 @@ inline double degrees_to_radians(double degrees) { return degrees * pi / 180.0; } -inline double ffmin(double a, double b) { return a <= b ? a : b; } -inline double ffmax(double a, double b) { return a >= b ? a : b; } - inline double clamp(double x, double min, double max) { if (x < min) return min; if (x > max) return max; diff --git a/src/common/vec3.h b/src/common/vec3.h index 8a770aa5..689a8424 100644 --- a/src/common/vec3.h +++ b/src/common/vec3.h @@ -11,8 +11,10 @@ // along with this software. If not, see . //============================================================================================== +#include #include +using std::sqrt; class vec3 { public: @@ -173,7 +175,7 @@ vec3 reflect(const vec3& v, const vec3& n) { } vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) { - auto cos_theta = ffmin(dot(-uv, n), 1.0); + auto cos_theta = fmin(dot(-uv, n), 1.0); vec3 r_out_parallel = etai_over_etat * (uv + cos_theta*n); vec3 r_out_perp = -sqrt(1.0 - r_out_parallel.length_squared()) * n; return r_out_parallel + r_out_perp; diff --git a/style/book.css b/style/book.css index 80da808d..f6996431 100644 --- a/style/book.css +++ b/style/book.css @@ -152,10 +152,6 @@ div.indented { width: 72ex; } -.md div.render img { - image-rendering: pixelated; -} - .md div.image { margin-bottom: 1em; }