├── CHANGELOG.md
├── COPYING.txt
├── README.md
├── assets
├── RTNextWeek.jpg
├── fig2-01.jpg
├── fig2-02.jpg
├── fig2-03.jpg
├── fig2-04.jpg
├── fig6-01.jpg
├── fig7-01.jpg
├── fig7-02.jpg
├── fig8-01.jpg
├── img1-01.jpg
├── img3-01.jpg
├── img3-02.jpg
├── img4-01.jpg
├── img4-02.jpg
├── img4-03.jpg
├── img4-04.jpg
├── img4-05.jpg
├── img4-06.jpg
├── img4-07.jpg
├── img4-08.jpg
├── img4-09.jpg
├── img4-10.jpg
├── img5-01.jpg
├── img6-01.jpg
├── img6-02.jpg
├── img6-03.jpg
├── img6-04.jpg
├── img7-01.jpg
├── img7-02.jpg
├── img8-01.jpg
└── img9-01.jpg
├── book
├── ch00.md.html
├── ch01.md.html
├── ch02.md.html
├── ch03.md.html
├── ch04.md.html
├── ch05.md.html
├── ch06.md.html
├── ch07.md.html
├── ch08.md.html
└── ch09.md.html
├── index.html
└── src
├── aabb.h
├── aarect.h
├── box.h
├── bvh.h
├── camera.h
├── constant_medium.h
├── hitable.h
├── hitable_list.h
├── main.cc
├── material.h
├── moving_sphere.h
├── perlin.h
├── ray.h
├── sphere.h
├── stb_image.h
├── stb_image_write.h
├── surface_texture.h
├── texture.h
└── vec3.h
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log — _Ray Tracing: The Next Week_
2 | ================================================================================
3 |
4 | v1.42.0 (2018-08-26)
5 | ----------------------
6 | - The _Ray Tracing in One Weekend_ book series is now free!
7 | - First GitHub release of the book, bundled with source code.
8 |
9 |
--------------------------------------------------------------------------------
/COPYING.txt:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | *WE'VE MOVED*
2 | ====================================================================================================
3 |
4 | We have consolidated our books into a single repository: [`raytracing.github.io`][].
5 |
6 | Please update your watches, stars, forks and links.
7 |
8 |
9 |
10 | [`raytracing.github.io`]: https://github.com/RayTracing/raytracing.github.io
11 |
--------------------------------------------------------------------------------
/assets/RTNextWeek.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/RTNextWeek.jpg
--------------------------------------------------------------------------------
/assets/fig2-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig2-01.jpg
--------------------------------------------------------------------------------
/assets/fig2-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig2-02.jpg
--------------------------------------------------------------------------------
/assets/fig2-03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig2-03.jpg
--------------------------------------------------------------------------------
/assets/fig2-04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig2-04.jpg
--------------------------------------------------------------------------------
/assets/fig6-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig6-01.jpg
--------------------------------------------------------------------------------
/assets/fig7-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig7-01.jpg
--------------------------------------------------------------------------------
/assets/fig7-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig7-02.jpg
--------------------------------------------------------------------------------
/assets/fig8-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/fig8-01.jpg
--------------------------------------------------------------------------------
/assets/img1-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img1-01.jpg
--------------------------------------------------------------------------------
/assets/img3-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img3-01.jpg
--------------------------------------------------------------------------------
/assets/img3-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img3-02.jpg
--------------------------------------------------------------------------------
/assets/img4-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-01.jpg
--------------------------------------------------------------------------------
/assets/img4-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-02.jpg
--------------------------------------------------------------------------------
/assets/img4-03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-03.jpg
--------------------------------------------------------------------------------
/assets/img4-04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-04.jpg
--------------------------------------------------------------------------------
/assets/img4-05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-05.jpg
--------------------------------------------------------------------------------
/assets/img4-06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-06.jpg
--------------------------------------------------------------------------------
/assets/img4-07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-07.jpg
--------------------------------------------------------------------------------
/assets/img4-08.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-08.jpg
--------------------------------------------------------------------------------
/assets/img4-09.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-09.jpg
--------------------------------------------------------------------------------
/assets/img4-10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img4-10.jpg
--------------------------------------------------------------------------------
/assets/img5-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img5-01.jpg
--------------------------------------------------------------------------------
/assets/img6-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img6-01.jpg
--------------------------------------------------------------------------------
/assets/img6-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img6-02.jpg
--------------------------------------------------------------------------------
/assets/img6-03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img6-03.jpg
--------------------------------------------------------------------------------
/assets/img6-04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img6-04.jpg
--------------------------------------------------------------------------------
/assets/img7-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img7-01.jpg
--------------------------------------------------------------------------------
/assets/img7-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img7-02.jpg
--------------------------------------------------------------------------------
/assets/img8-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img8-01.jpg
--------------------------------------------------------------------------------
/assets/img9-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RayTracing/TheNextWeek/8d4be567f0d5e6343fff866df60680783d0e23f2/assets/img9-01.jpg
--------------------------------------------------------------------------------
/book/ch00.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Ray Tracing: The Next Week**
10 | Peter Shirley
11 | Version 1.41
12 |
13 | Copyright 2018. Peter Shirley. All rights reserved.
14 |
15 | Pay What You Want. ptrshrl@gmail.com at PayPal
16 | 50% of all payments donated to programming education related not-for-profits
17 |
18 |
19 | Chapter 0: Overview
20 | ====================
21 |
22 | In Ray Tracing In One Weekend, you built a simple brute force path tracer. In this installment we’ll
23 | add textures, volumes (like fog), rectangles, instances, lights, and support for lots of objects
24 | using a BVH. When done, you’ll have a “real” ray tracer.
25 |
26 | A heuristic in ray tracing that many people--including me--believe, is that most optimizations
27 | complicate the code without delivering much speedup. What I will do in this mini-book is go with the
28 | simplest approach in each design decision I make. Check http://www.in1weekend.com/ for readings and
29 | references to a more sophisticated approach. However, I strongly encourage you to do no premature
30 | optimization; if it doesn’t show up high in the execution time profile, it doesn’t need optimization
31 | until all the features are supported!
32 |
33 | The two hardest parts of this book are the BVH and the Perlin textures. This is why the title
34 | suggests you take a week rather than a weekend for this endeavor. But you can save those for last if
35 | you want a weekend project. Order is not very important for the concepts presented in this book, and
36 | without BVH and Perlin texture you will still get a Cornell Box!
37 |
38 |
39 | Acknowledgments
40 | ----------------
41 | Thanks to Becker for his many helpful comments on the draft and to Matthew Heimlich for spotting a
42 | critical motion blur error. Thanks to Andrew Kensler, Thiago Ize, and Ingo Wald for advice on
43 | ray-AABB tests. Thanks to David Hart and Grue Debry for help with a bunch of the details. Thanks to
44 | Jean Buckley for editing. Thanks to Dan Drummond for code fixes. Thanks to Steve Hollasch and Trevor
45 | David Black for getting the book translated to Markdeep and moved to the web.
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/book/ch01.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 1: Motion Blur**
10 |
11 | When you decided to ray trace, you decided visual quality was worth more run-time. In your fuzzy
12 | reflection and defocus blur you needed multiple samples per pixel. Once you have taken a step down
13 | that road, the good news is that almost all effects can be brute-forced. Motion blur is certainly
14 | one of those. In a real camera, the shutter opens and stays open for a time interval, and the camera
15 | and objects may move during that time. Its really an average of what the camera sees over that
16 | interval that we want. We can get a random estimate by sending each ray at some random time when the
17 | shutter is open. As long as the objects are where they should be at that time, we can get the right
18 | average answer with a ray that is at exactly a single time. This is fundamentally why random ray
19 | tracing tends to be simple.
20 |
21 | The basic idea is to generate rays at random times while the shutter is open and intersect the model
22 | at that one time. The way it is usually done is to have the camera move and the objects move, but
23 | have each ray exist at exactly one time. This way the “engine” of the ray tracer can just make sure
24 | the objects are where they need to be for the ray, and the intersection guts don’t change much.
25 |
26 | For this we will first need to have a ray store the time it exists at:
27 |
28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
29 | class ray
30 | {
31 | public:
32 | ray() {}
33 | ray(const vec3& a, const vec3& b, float ti = 0.0) { A = a; B = b; _time = ti;}
34 | vec3 origin() const { return A; }
35 | vec3 direction() const { return B; }
36 | float time() const { return _time; }
37 | vec3 point_at_parameter(float t) const { return A + t*B; }
38 |
39 | vec3 A;
40 | vec3 B;
41 | float _time;
42 | };
43 |
44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45 |
46 | Now we need to modify the camera to generate rays at a random time between `time1` and `time2`.
47 | Should the camera keep track of `time1` and `time2` or should that be up to the user of camera when
48 | a ray is created? When in doubt, I like to make constructors complicated if it makes calls simple,
49 | so I will make the camera keep track, but that’s a personal preference. Not many changes are needed
50 | to camera because for now it is not allowed to move; it just sends out rays over a time period.
51 |
52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
53 | class camera {
54 | public:
55 | // new: add t0 and t1
56 | camera(
57 | vec3 lookfrom,
58 | vec3 lookat,
59 | vec3 vup,
60 | float vfov, // vfov is top to bottom in degrees
61 | float aspect,
62 | float aperture,
63 | float focus_dist,
64 | float t0,
65 | float t1) {
66 |
67 | time0 = t0;
68 | time1 = t1;
69 | lens_radius = aperture / 2;
70 | float theta = vfov*M_PI/180;
71 | float half_height = tan(theta/2);
72 | float half_width = aspect * half_height;
73 | origin = lookfrom;
74 | w = unit_vector(lookfrom - lookat);
75 | u = unit_vector(cross(vup, w));
76 | v = cross(w, u);
77 | lower_left_corner = origin
78 | - half_width*focus_dist*u
79 | - half_height*focus_dist*v
80 | - focus_dist*w;
81 | horizontal = 2*half_width*focus_dist*u;
82 | vertical = 2*half_height*focus_dist*v;
83 | }
84 |
85 | // new: add time to construct ray
86 | ray get_ray(float s, float t) {
87 | vec3 rd = lens_radius*random_in_unit_disk();
88 | vec3 offset = u * rd.x() + v * rd.y();
89 | float time = time0 + drand48()*(time1-time0);
90 | return ray(
91 | origin + offset,
92 | lower_left_corner + s*horizontal + t*vertical - origin - offset,
93 | time);
94 | }
95 |
96 | vec3 origin;
97 | vec3 lower_left_corner;
98 | vec3 horizontal;
99 | vec3 vertical;
100 | vec3 u, v, w;
101 | float time0, time1; // new variables for shutter open/close times
102 | float lens_radius;
103 | };
104 |
105 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106 |
107 | We also need a moving object. I’ll create a sphere class that has its center move linearly from
108 | `center0` at `time0` to `center1` at `time1`. Outside that time interval it continues on, so those
109 | times need not match up with the camera aperture open close.
110 |
111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
112 | class moving_sphere: public hitable {
113 | public:
114 | moving_sphere() {}
115 | moving_sphere(vec3 cen0, vec3 cen1, float t0, float t1, float r, material *m)
116 | : center0(cen0), center1(cen1), time0(t0),time1(t1), radius(r), mat_ptr(m)
117 | {};
118 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
119 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
120 | vec3 center(float time) const;
121 | vec3 center0, center1;
122 | float time0, time1;
123 | float radius;
124 | material *mat_ptr;
125 | };
126 |
127 | vec3 moving_sphere::center(float time) const{
128 | return center0 + ((time - time0) / (time1 - time0))*(center1 - center0);
129 | }
130 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131 |
132 | An alternative to making a new moving sphere class is to just make them all move and have the
133 | stationary ones have the same begin and end point. I’m on the fence about that trade-off between
134 | fewer classes and more efficient stationary spheres, so let your design taste guide you. The
135 | intersection code barely needs a change: `center` just needs to become a function `center(time)`:
136 |
137 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
138 | // replace "center" with "center(r.time())"
139 | bool moving_sphere::hit(
140 | const ray& r, float t_min, float t_max, hit_record& rec) const {
141 |
142 | vec3 oc = r.origin() - center(r.time());
143 | float a = dot(r.direction(), r.direction());
144 | float b = dot(oc, r.direction());
145 | float c = dot(oc, oc) - radius*radius;
146 | float discriminant = b*b - a*c;
147 | if (discriminant > 0) {
148 | float temp = (-b - sqrt(discriminant))/a;
149 | if (temp < t_max && temp > t_min) {
150 | rec.t = temp;
151 | rec.p = r.point_at_parameter(rec.t);
152 | rec.normal = (rec.p - center(r.time())) / radius;
153 | rec.mat_ptr = mat_ptr;
154 | return true;
155 | }
156 | temp = (-b + sqrt(discriminant))/a;
157 | if (temp < t_max && temp > t_min) {
158 | rec.t = temp;
159 | rec.p = r.point_at_parameter(rec.t);
160 | rec.normal = (rec.p - center(r.time())) / radius;
161 | rec.mat_ptr = mat_ptr;
162 | return true;
163 | }
164 | }
165 | return false;
166 | }
167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
168 |
169 | Be sure that in the materials you have the scattered rays be at the time of the incident ray.
170 |
171 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
172 | class lambertian : public material {
173 | public:
174 | lambertian(const vec3& a) : albedo(a) {}
175 |
176 | virtual bool scatter(const ray& r_in, const hit_record& rec,
177 | vec3& attenuation, ray& scattered) const {
178 |
179 | vec3 target = rec.p + rec.normal + random_in_unit_sphere();
180 | scattered = ray(rec.p, target-rec.p, r_in.time());
181 | attenuation = albedo;
182 | return true;
183 | }
184 |
185 | vec3 albedo;
186 | };
187 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
188 |
189 | If we take the example diffuse spheres from scene at the end of the last book and make them move
190 | from their centers at `time==0`, to `center + vec3(0, 0.5*drand48(), 0)` at `time==1`, with the
191 | camera aperture open over that frame.
192 |
193 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
194 | hitable *random_scene() {
195 | int n = 50000;
196 | hitable **list = new hitable*[n+1];
197 | list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(checker));
198 | int i = 1;
199 | for (int a = -10; a < 10; a++) {
200 | for (int b = -10; b < 10; b++) {
201 | float choose_mat = drand48();
202 | vec3 center(a+0.9*drand48(),0.2,b+0.9*drand48());
203 | if ((center-vec3(4,0.2,0)).length() > 0.9) {
204 | if (choose_mat < 0.8) { // diffuse
205 | list[i++] = new moving_sphere(
206 | center,
207 | center+vec3(0, 0.5*drand48(), 0),
208 | 0.0, 1.0, 0.2,
209 | new lambertian(
210 | vec3(drand48()*drand48(),
211 | drand48()*drand48(),
212 | drand48()*drand48())
213 | )
214 | );
215 | }
216 | else if (choose_mat < 0.95) { // metal
217 | list[i++] = new sphere(
218 | center, 0.2,
219 | new metal(
220 | vec3(0.5*(1 + drand48()),
221 | 0.5*(1 + drand48()),
222 | 0.5*(1 + drand48())),
223 | 0.5*drand48()
224 | )
225 | );
226 | }
227 | else { // glass
228 | list[i++] = new sphere(center, 0.2, new dielectric(1.5));
229 | }
230 | }
231 | }
232 | }
233 |
234 | list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
235 | list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
236 | list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));
237 |
238 | return new hitable_list(list,i);
239 | }
240 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
241 |
242 | And with these viewing parameters gives:
243 |
244 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
245 | vec3 lookfrom(13,2,3);
246 | vec3 lookat(0,0,0);
247 | float dist_to_focus = 10.0;
248 | float aperture = 0.0;
249 |
250 | camera cam(
251 | lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny), aperture,
252 | dist_to_focus, 0.0, 1.0
253 | );
254 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
255 |
256 | 
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
--------------------------------------------------------------------------------
/book/ch02.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 2: Bounding Volume Hierarchies**
10 |
11 | This part is by far the most difficult and involved part of the ray tracer we are working on. I am
12 | sticking it in Chapter 2 so the code can run faster, and because it refactors `hitable` a little,
13 | and when I add rectangles and boxes we won't have to go back and refactor them.
14 |
15 | The ray-object intersection is the main time-bottleneck in a ray tracer, and the time is linear with
16 | the number of objects. But it’s a repeated search on the same model, so we ought to be able to make
17 | it a logarithmic search in the spirit of binary search. Because we are sending millions to billions
18 | of rays on the same model, we can do an analog of sorting the model and then each ray intersection
19 | can be a sublinear search. The two most common families of sorting are to 1) divide the space, and
20 | 2) divide the objects. The latter is usually much easier to code up and just as fast to run for most
21 | models.
22 |
23 | The key idea of a bounding volume over a set of primitives is to find a volume that fully encloses
24 | (bounds) all the objects. For example, suppose you computed a bounding sphere of 10 objects. Any ray
25 | that misses the bounding sphere definitely misses all ten objects. If the ray hits the bounding
26 | sphere, then it might hit one of the ten objects. So the bounding code is always of the form:
27 |
28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29 | if (ray hits bounding object)
30 | return whether ray hits bounded objects
31 | else
32 | return false
33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34 |
35 | A key thing is we are dividing objects into subsets. We are not dividing the screen or the volume.
36 | Any object is in just one bounding volume, but bounding volumes can overlap.
37 |
38 | To make things sub-linear we need to make the bounding volumes hierarchical. For example, if we
39 | divided a set of objects into two groups, red and blue, and used rectangular bounding volumes, we’d
40 | have:
41 |
42 | 
43 |
44 | Note that the blue and red bounding volumes are contained in the purple one, but they might
45 | overlap, and they are not ordered -- they are just both inside. So the tree shown on the right has
46 | no concept of ordering in the left and right children; they are simply inside. The code would be:
47 |
48 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
49 | if (hits purple)
50 | hit0 = hits blue enclosed objects
51 | hit1 = hits red enclosed objects
52 | if (hit0 or hit1)
53 | return true and info of closer hit
54 | return false
55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 |
57 | To get that all to work we need a way to make good divisions, rather than bad ones, and a way to
58 | intersect a ray with a bounding volume. A ray bounding volume intersection needs to be fast, and
59 | bounding volumes need to be pretty compact. In practice for most models, axis-aligned boxes work
60 | better than the alternatives, but this design choice is always something to keep in mind if you
61 | encounter unusual types of models.
62 |
63 | From now on we will call axis-aligned bounding rectangular parallelepiped (really, that is what they
64 | need to be called if precise) axis-aligned bounding boxes, or AABBs. Any method you want to use to
65 | intersect a ray with an AABB is fine. And all we need to know is whether or not we hit it; we don’t
66 | need hit points or normals or any of that stuff that we need for an object we want to display.
67 |
68 | Most people use the “slab” method. This is based on the observation that an n-dimensional AABB is
69 | just the intersection of n axis-aligned intervals, often called “slabs” An interval is just
70 | the points between two endpoints, _e.g._, $x$ such that $3 < x < 5$, or more succinctly $x$ in
71 | $(3,5)$. In 2D, two intervals overlapping makes a 2D AABB (a rectangle):
72 |
73 | 
74 |
75 | For a ray to hit one interval we first need to figure out whether the ray hits the boundaries. For
76 | example, again in 2D, this is the ray parameters $t_0$ and $t_1$. (If the ray is parallel to the
77 | plane those will be undefined.)
78 |
79 | 
80 |
81 | In 3D, those boundaries are planes. The equations for the planes are $x = x_0$, and $x = x_1$. Where
82 | does the ray hit that plane? Recall that the ray can be thought of as just a function that given a
83 | $t$ returns a location $p(t)$:
84 |
85 | $$ p(t) = A + t \cdot B $$
86 |
87 | That equation applies to all three of the x/y/z coordinates. For example $x(t) = A_x + t \cdot B_x$.
88 | This ray hits the plane $x = x_0$ at the $t$ that satisfies this equation:
89 |
90 | $$ x_0 = A_x + t_0 \cdot B_x $$
91 |
92 | Thus $t$ at that hitpoint is:
93 |
94 | $$ t_0 = \frac{x_0 - A_x}{B_x} $$
95 |
96 | We get the similar expression for $x_1$:
97 |
98 | $$ t_1 = \frac{x_1 - A_x}{B_x} $$
99 |
100 | The key observation to turn that 1D math into a hit test is that for a hit, the $t$-intervals need
101 | to overlap. For example, in 2D the green and blue overlapping only happens if there is a hit:
102 |
103 | 
104 |
105 | What “do the t intervals in the slabs overlap?” would like in code is something like:
106 |
107 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
108 | compute (tx0, tx1)
109 | compute (ty0, ty1)
110 | return overlap?( (tx0, tx1), (ty0, ty1))
111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112 |
113 | That is awesomely simple, and the fact that the 3D version also works is why people love the
114 | slab method:
115 |
116 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117 | compute (tx0, tx1)
118 | compute (ty0, ty1)
119 | compute (tz0, tz1)
120 | return overlap?( (tx0, tx1), (ty0, ty1), (tz0, tz1))
121 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
122 |
123 | There are some caveats that make this less pretty than it first appears. First, suppose the ray is
124 | travelling in the negative $x$ direction. The interval $(t_{x0}, t_{x1})$ as computed above might be
125 | reversed, _e.g._ something like $(7, 3)$. Second, the divide in there could give us infinities. And
126 | if the ray origin is on one of the slab boundaries, we can get a `NaN`. There are many ways these
127 | issues are dealt with in various ray tracers’ AABB. (There are also vectorization issues like SIMD
128 | which we will not discuss here. Ingo Wald’s papers are a great place to start if you want to go the
129 | extra mile in vectorization for speed.) For our purposes, this is unlikely to be a major bottleneck
130 | as long as we make it reasonably fast, so let’s go for simplest, which is often fastest anyway!
131 | First let’s look at computing the intervals:
132 |
133 | $$ t_{x0} = \frac{x_0 - A_x}{B_x} $$
134 | $$ t_{x1} = \frac{x_1 - A_x}{B_x} $$
135 |
136 | One troublesome thing is that perfectly valid rays will have $B_x = 0$, causing division by zero.
137 | Some of those rays are inside the slab, and some are not. Also, the zero will have a ± sign under
138 | IEEE floating point. The good news for $B_x = 0$ is that $t_{x0}$ and $t_{x1}$ will both be +∞ or
139 | both be -∞ if not between $x_0$ and $x_1$. So, using min and max should get us the right answers:
140 |
141 | $$ t_{x0} = min(\frac{x_0 - A_x}{B_x}, \frac{x_1 - A_x}{B_x}) $$
142 | $$ t_{x1} = max(\frac{x_0 - A_x}{B_x}, \frac{x_1 - A_x}{B_x}) $$
143 |
144 | The remaining troublesome case if we do that is if $B_x = 0$ and either $x_0 - A_x = 0$ or $x_1-A_x
145 | = 0$ so we get a `NaN`. In that case we can probably accept either hit or no hit answer, but we’ll
146 | revisit that later.
147 |
148 | Now, let’s look at that overlap function. Suppose we can assume the intervals are not reversed (so
149 | the first value is less than the second value in the interval) and we want to return true in that
150 | case. The boolean overlap that also computes the overlap interval $(f, F)$ of intervals $(d, D)$ and
151 | $(e, E)$ would be:
152 |
153 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
154 | bool overlap(d, D, e, E, f, F)
155 | f = max(d, e)
156 | F = min(D, E)
157 | return (f < F)
158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159 |
160 | If there are any `NaN`s running around there, the compare will return false so we need to be sure
161 | our bounding boxes have a little padding if we care about grazing cases (and we probably should
162 | because in a ray tracer all cases come up eventually). With all three dimensions in a loop and
163 | passing in the interval $t_{min}$, $t_{max}$ we get:
164 |
165 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
166 | inline float ffmin(float a, float b) { return a < b ? a : b; }
167 | inline float ffmax(float a, float b) { return a > b ? a : b; }
168 |
169 | class aabb {
170 | public:
171 | aabb() {}
172 | aabb(const vec3& a, const vec3& b) { _min = a; _max = b;}
173 |
174 | vec3 min() const {return _min; }
175 | vec3 max() const {return _max; }
176 |
177 | bool hit(const ray& r, float tmin, float tmax) const {
178 | for (int a = 0; a < 3; a++) {
179 | float t0 = ffmin((_min[a] - r.origin()[a]) / r.direction()[a],
180 | (_max[a] - r.origin()[a]) / r.direction()[a]);
181 | float t1 = ffmax((_min[a] - r.origin()[a]) / r.direction()[a],
182 | (_max[a] - r.origin()[a]) / r.direction()[a]);
183 | tmin = ffmax(t0, tmin);
184 | tmax = ffmin(t1, tmax);
185 | if (tmax <= tmin)
186 | return false;
187 | }
188 | return true;
189 | }
190 |
191 | vec3 _min;
192 | vec3 _max;
193 | };
194 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195 |
196 | Note that the built-in `fmax()` is replaced by `ffmax()` which is quite a bit faster because it
197 | doesn’t worry about `NaN`s and other exceptions. In reviewing this intersection method, Andrew
198 | Kensler at Pixar tried some experiments and has proposed this version of the code which works
199 | extremely well on many compilers, and I have adopted it as my go-to method:
200 |
201 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
202 | inline bool aabb::hit(const ray& r, float tmin, float tmax) const {
203 | for (int a = 0; a < 3; a++) {
204 | float invD = 1.0f / r.direction()[a];
205 | float t0 = (min()[a] - r.origin()[a]) * invD;
206 | float t1 = (max()[a] - r.origin()[a]) * invD;
207 | if (invD < 0.0f)
208 | std::swap(t0, t1);
209 | tmin = t0 > tmin ? t0 : tmin;
210 | tmax = t1 < tmax ? t1 : tmax;
211 | if (tmax <= tmin)
212 | return false;
213 | }
214 | return true;
215 | }
216 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
217 |
218 | We now need to add a function to compute bounding boxes to all of the hitables. Then we will make a
219 | hierarchy of boxes over all the primitives and the individual primitives, like spheres, will live at
220 | the leaves. That function returns a bool because not all primitives have bounding boxes (_e.g._,
221 | infinite planes). In addition, objects move so it takes `time1` and `time2` for the interval of the
222 | frame and the bounding box will bound the object moving through that interval.
223 |
224 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
225 | class hitable {
226 | public:
227 | virtual bool hit(
228 | const ray& r, float t_min, float t_max, hit_record& rec) const = 0;
229 | virtual bool bounding_box(float t0, float t1, aabb& box) const = 0;
230 | };
231 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
232 |
233 | For a sphere, that `bounding_box` function is easy:
234 |
235 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
236 | bool sphere::bounding_box(float t0, float t1, aabb& box) const {
237 | box = aabb(center - vec3(radius, radius, radius),
238 | center + vec3(radius, radius, radius));
239 | return true;
240 | }
241 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
242 |
243 | For `moving sphere`, we can take the box of the sphere at $t_0$, and the box of the sphere at $t_1$,
244 | and compute the box of those two boxes:
245 |
246 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
247 | bool moving_sphere::bounding_box(float t0, float t1, aabb& box) const {
248 | aabb box0(center(t0) - vec3(radius, radius, radius),
249 | center(t0) + vec3(radius, radius, radius));
250 | aabb box1(center(t1) - vec3(radius, radius, radius),
251 | center(t1) + vec3(radius, radius, radius));
252 | box = surrounding_box(box0, box1);
253 | return true;
254 | }
255 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
256 |
257 | For lists you can store the bounding box at construction, or compute it on the fly. I like doing it
258 | the fly because it is only usually called at BVH construction.
259 |
260 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
261 | bool hitable_list::bounding_box(float t0, float t1, aabb& box) const {
262 | if (list_size < 1) return false;
263 | aabb temp_box;
264 | bool first_true = list[0]->bounding_box(t0, t1, temp_box);
265 | if (!first_true)
266 | return false;
267 | else
268 | box = temp_box;
269 | for (int i = 1; i < list_size; i++) {
270 | if(list[i]->bounding_box(t0, t1, temp_box)) {
271 | box = surrounding_box(box, temp_box);
272 | }
273 | else
274 | return false;
275 | }
276 | return true;
277 | }
278 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
279 |
280 | This requires the `surrounding_box` function for `aabb` which computes the bounding box of two
281 | boxes:
282 |
283 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
284 | aabb surrounding_box(aabb box0, aabb box1) {
285 | vec3 small( ffmin(box0.min().x(), box1.min().x()),
286 | ffmin(box0.min().y(), box1.min().y()),
287 | ffmin(box0.min().z(), box1.min().z()));
288 | vec3 big ( ffmax(box0.max().x(), box1.max().x()),
289 | ffmax(box0.max().y(), box1.max().y()),
290 | ffmax(box0.max().z(), box1.max().z()));
291 | return aabb(small,big);
292 | }
293 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
294 |
295 | A BVH is also going to be a `hitable` -- just like lists of `hitable`s. It’s really a container, but
296 | it can respond to the query “does this ray hit you?”. One design question is whether we have two
297 | classes, one for the tree, and one for the nodes in the tree; or do we have just one class and have
298 | the root just be a node we point to. I am a fan of the one class design when feasible. Here is such
299 | a class:
300 |
301 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
302 | class bvh_node : public hitable {
303 | public:
304 | bvh_node() {}
305 | bvh_node(hitable **l, int n, float time0, float time1);
306 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
307 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
308 | hitable *left;
309 | hitable *right;
310 | aabb box;
311 | };
312 |
313 |
314 | bool bvh_node::bounding_box(float t0, float t1, aabb& b) const {
315 | b = box;
316 | return true;
317 | }
318 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
319 |
320 | Note that the children pointers are to generic hitables. They can be other `bvh_nodes`, or
321 | `spheres`, or any other `hitable`.
322 |
323 | The `hit` function is pretty straightforward: check whether the box for the node is hit, and if so,
324 | check the children and sort out any details:
325 |
326 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
327 | bool bvh_node::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
328 | if (box.hit(r, t_min, t_max)) {
329 | hit_record left_rec, right_rec;
330 | bool hit_left = left->hit(r, t_min, t_max, left_rec);
331 | bool hit_right = right->hit(r, t_min, t_max, right_rec);
332 | if (hit_left && hit_right) {
333 | if (left_rec.t < right_rec.t)
334 | rec = left_rec;
335 | else
336 | rec = right_rec;
337 | return true;
338 | }
339 | else if (hit_left) {
340 | rec = left_rec;
341 | return true;
342 | }
343 | else if (hit_right) {
344 | rec = right_rec;
345 | return true;
346 | }
347 | else
348 | return false;
349 | }
350 | else return false;
351 | }
352 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
353 |
354 | The most complicated part of any efficiency structure, including the BVH, is building it. We do this
355 | in the constructor. A cool thing about BVHs is that as long as the list of objects in a `bvh_node`
356 | gets divided into two sub-lists, the hit function will work. It will work best if the division is
357 | done well, so that the two children have smaller bounding boxes than their parent’s bounding box,
358 | but that is for speed not correctness. I’ll choose the middle ground, and at each node split the
359 | list along one axis. I’ll go for simplicity:
360 |
361 | 1. randomly choose an axis
362 | 2. sort the primitives using library qsort
363 | 3. put half in each subtree
364 |
365 | I used the old-school C `qsort` rather than the C++ sort because I need a different compare operator
366 | depending on axis, and `qsort` takes a compare function rather than using the less-than operator. I
367 | pass in a pointer to pointer -- this is just C for “array of pointers” because a pointer in C can
368 | also just be a pointer to the first element of an array.
369 |
370 | When the list coming in is two elements, I put one in each subtree and end the recursion. The
371 | traverse algorithm should be smooth and not have to check for null pointers, so if I just have one
372 | element I duplicate it in each subtree. Checking explicitly for three elements and just following
373 | one recursion would probably help a little, but I figure the whole method will get optimized later.
374 | This yields:
375 |
376 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
377 | bvh_node::bvh_node(hitable **l, int n, float time0, float time1) {
378 | int axis = int(3*drand48());
379 | if (axis == 0)
380 | qsort(l, n, sizeof(hitable *), box_x_compare);
381 | else if (axis == 1)
382 | qsort(l, n, sizeof(hitable *), box_y_compare);
383 | else
384 | qsort(l, n, sizeof(hitable *), box_z_compare);
385 | if (n == 1) {
386 | left = right = l[0];
387 | }
388 | else if (n == 2) {
389 | left = l[0];
390 | right = l[1];
391 | }
392 | else {
393 | left = new bvh_node(l, n/2, time0, time1);
394 | right = new bvh_node(l + n/2, n - n/2, time0, time1);
395 | }
396 | aabb box_left, box_right;
397 | if (!left->bounding_box(time0,time1, box_left) ||
398 | !right->bounding_box(time0,time1, box_right)
399 | ) {
400 | std::cerr << "no bounding box in bvh_node constructor\n";
401 | }
402 | box = surrounding_box(box_left, box_right);
403 | }
404 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
405 |
406 | The check for whether there is a bounding box at all is in case you sent in something like an
407 | infinite plane that doesn’t have a bounding box. We don’t have any of those primitives, so it
408 | shouldn’t happen until you add such a thing.
409 |
410 | The compare function has to take void pointers which you cast. This is old-school C and reminded me
411 | why C++ was invented. I had to really mess with this to get all the pointer junk right. If you like
412 | this part, you have a future as a systems person!
413 |
414 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
415 | int box_x_compare (const void * a, const void * b) {
416 | aabb box_left, box_right;
417 | hitable *ah = *(hitable**)a;
418 | hitable *bh = *(hitable**)b;
419 |
420 | if (!ah->bounding_box(0,0, box_left) || !bh->bounding_box(0,0, box_right))
421 | std::cerr << "no bounding box in bvh_node constructor\n";
422 |
423 | if (box_left.min().x() - box_right.min().x() < 0.0)
424 | return -1;
425 | else
426 | return 1;
427 | }
428 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
--------------------------------------------------------------------------------
/book/ch03.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 3 Solid Textures**
10 |
11 | A texture in graphics usually means a function that makes the colors on a surface procedural. This
12 | procedure can be synthesis code, or it could be an image lookup, or a combination of both. We will
13 | first make all colors a texture. Most programs keep constant rgb colors and textures different
14 | classes so feel free to do something different, but I am a big believer in this architecture because
15 | being able to make any color a texture is great.
16 |
17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
18 | class texture (
19 | public:
20 | virtual vec3 value(float u, float v, const vec3& p) const = O;
21 | };
22 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 |
24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
25 | class constant_texture : public texture {
26 | public:
27 | constant_texture() ( }
28 | constant_texture(vec3 c) : color(c) { }
29 | virtual vec3 value(float u, float v, const vec3& p) const {
30 | return color;
31 | }
32 | vec3 color;
33 | };
34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35 |
36 | Now we can make textured materials by replacing the vec3 color with a texture pointer:
37 |
38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
39 | class lambertian : public material {
40 | public:
41 | lambertian(texture *a) : albedo(a) {}
42 | virtual bool scatter(const ray& r_in, const hit_record& rec,
43 | vec3& attenuation, ray& scattered) const (
44 | vec3 target = rec.p + rec.normal + random_in_unit_sphere();
45 | scattered = ray(rec.p, target - rec.p);
46 | attenuation = albedo->value(0, 0, rec.p);
47 | return true;
48 | }
49 | texture *albedo;
50 | };
51 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52 |
53 | where you used to have new lambertian(vec3(0.5, 0.5, 0.5))) now you should replace the vec3(...)
54 | with new constant_texture(vec3(...)) new lambertian(new constant_texture(vec3(0.5, 0.5, 0.5))))
55 |
56 | We can create a checker texture by noting that the sign of sine and cosine just alternates in a
57 | regular way and if we multiply trig functions in all three dimensions, the sign of that product
58 | forms a 3D checker pattern.
59 |
60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
61 | class checker_texture : public texture {
62 | public:
63 | checker_texture() { }
64 | checker_texture(texture *t0, texture *tl): even(t0), odd(t1) { }
65 | virtual vec3 value(float u, float v, const vec3& p) const {
66 | float sines = sin(10*p.x())*sin(10*p.y())*sin(10*p.z());
67 | if (sines < 0)
68 | return odd—>value(u, v, p);
69 | else
70 | return even->value(u, v, p);
71 | }
72 | texture *odd;
73 | texture *even;
74 | }
75 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76 |
77 | Those checker odd/even pointers can be to a constant texture or to some other procedural texture.
78 | This is in the spirit of shader networks introduced by Pat Hanrahan back in the 1980s.
79 |
80 | If we add this to our random_scene() function’s base sphere:
81 |
82 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
83 | texture *checker = new checker_texture(
84 | new constant_texture(vec3(0.2, 0.3, 0.1)),
85 | new constant_texture(vec3(0.9, 0.9, 0.9))
86 | );
87 | list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(checker));
88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89 |
90 | We get:
91 |
92 | 
93 |
94 | If we add a new scene:
95 |
96 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
97 | hitable *two_spheres() {
98 | texture *checker = new checker_texture(
99 | new constant_texture(vec3(0.2, 0.3, 0.1)),
100 | new constant_texture(vec3(0.9, 0.9, 0.9))
101 | );
102 | int n = 50;
103 | hitable **list = new hitable*[n+1];
104 | list[0] = new sphere(vec3(0,-10, 0), 10, new lambertian(checker)
105 | 1ist[1] = new sphere(vec3(0, 10, 0), 10, new lambertian(checker)
106 | return new hitable_list(list,2);
107 | }
108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 |
110 | With camera:
111 |
112 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
113 | vec3 lookfrom(13,2,3);
114 | vec3 lookat(0,0,0);
115 | float dist_to_focus = 10.0;
116 | float aperture = 0.0;
117 |
118 | camera cam(lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny),
119 | aperture, dist_to_focus, 0.0, 1.0);
120 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121 |
122 | We get:
123 |
124 | 
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/book/ch04.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 4 Perlin Noise**
10 |
11 | To get cool looking solid textures most people use some form of Perlin noise. These are named after
12 | their inventor Ken Perlin. Perlin texture doesn’t return white noise like this:
13 |
14 | 
15 |
16 | Instead it returns something similar to blurred white noise:
17 |
18 | 
19 |
20 | A key part of Perlin noise is that it is repeatable: it takes a 3D point as input and always returns
21 | the same randomish number. Nearby points return similar numbers. Another important part of Perlin
22 | noise is that it be simple and fast, so it’s usually done as a hack. I’ll build that hack up
23 | incrementally based on Andrew Kensler’s description.
24 |
25 | We could just tile all of space with a 3D array of random numbers and use them in blocks. You get
26 | something blocky where the repeating is clear:
27 |
28 | 
29 |
30 | Let’s just use some sort of hashing to scramble this, instead of tiling. This has a bit of support
31 | code to make it all happen:
32 |
33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
34 | class perlin {
35 | public:
36 | float noise(const vec3& p) const {
37 | float u = p.x() - floor(p.x());
38 | float v = p.y() - floor(p.y());
39 | float w = p.z() - floor(p.z());
40 | int i = floor(p.x());
41 | int j = floor(p.y());
42 | int k = floor(p.z());
43 | return ranfloat[perm_x[i] ^ perm_y[j] ^ perm_z[k]];
44 | }
45 | static float *ranfloat;
46 | static int *perm_x;
47 | static int *perm_y;
48 | static int *perm_z;
49 | };
50 |
51 | static vec3* perlin_generate() {
52 | float * p = new float[256];
53 | for (int i = 0; i < 256; ++i)
54 | p[i] = drand48();
55 | return p;
56 | }
57 |
58 | void permute(int *p, int n) {
59 | for (int i = n-1; i > 0; i--) {
60 | int target = int(drand48()*(i+1));
61 | int tmp = p[i];
62 | p[i] = p[target];
63 | p[target] = tmp;
64 | }
65 | return;
66 | }
67 |
68 | static int* perlin_generate_perm() {
69 | int * p = new int[256];
70 | for (int i = 0; i < 256; i++)
71 | p[i] = i;
72 | permute(p, 256);
73 | return p;
74 | }
75 |
76 | float *perlin::ranfloat = perlin_generate();
77 | int *perlin::perm_x = perlin_generate_perm();
78 | int *perlin::perm_y = perlin_generate_perm();
79 | int *perlin::perm_z = perlin_generate_perm();
80 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81 |
82 | Now if we create an actual texture that takes these floats between 0 and 1 and creates grey
83 | colors:
84 |
85 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
86 | class noise_texture : public texture {
87 | public:
88 | noise_texture() {}
89 | virtual vec3 value(float u, float v, const vec3& p) const {
90 | return vec3(1,1,1) * noise.noise(p);
91 | }
92 | perlin noise;
93 | };
94 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95 |
96 | And we can use that one some spheres:
97 |
98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
99 | hitable *two_perlin_spheres() (
100 | texture *pertext = new noise_texture();
101 | hitable **list = new hitable*[2];
102 | list[0] = new sphere(vec3(0,-1000, 0), 1000, new lambertian(pertext));
103 | list[1] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));
104 | return new hitable_list(list, 2);
105 | }
106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107 |
108 | With the same camera as before:
109 |
110 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
111 | vec3 lookfrom(13,2,3);
112 | vec3 lookat(0,0,0);
113 | float dist_to_focus = 10.0;
114 | float aperture = 0.0;
115 | camera cam(lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny),
116 | aperture, dist_to_focus, 0.0, 1.0);
117 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
118 |
119 | Add the hashing does scramble as hoped:
120 |
121 | 
122 |
123 | To make it smooth, we can linearly interpolate:
124 |
125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
126 | inline float trilinear_interp(float c[Z][2][2], float u, float v, float w) {
127 | float accum = 0;
128 | for (int i=0; i < 2; 1++)
129 | for (int j=0; j < 2; j++)
130 | for (int k=0; k < 2; k++)
131 | accum += (i*u + (1-i)*(1-u))*
132 | (j*v + (1-j)*(1-v))*
133 | (k*w + (1-k)*(1-w))*c[i][j][k];
134 |
135 | return accum;
136 | }
137 |
138 | class perlin {
139 | public:
140 | float noise(const vec3& p) const {
141 | float u = p.x() - floor(p.x());
142 | float v = p.y() - floor(p.y());
143 | float w = p.z() - floor(p.z());
144 | int i = floor(p.x());
145 | int j = floor(p.y());
146 | int k = floor(p.z());
147 | float c[2][2][2];
148 | for (int di=0; di < 2; di++)
149 | for (int dj=0; dj < 2; dj++)
150 | for (int dk=0; dk < 2; dk++)
151 | c[di][dj][dk] = ranfloat[
152 | perm_x[(i+di) & 255] ^
153 | perm_y[(j+dj) & 255] ^
154 | pexm_z[(k+dk) & 255];
155 | ];
156 | return trilinear_interp(c, u, v, w);
157 | }
158 | static int *perm_x;
159 | static int *perm_y;
160 | static int *perm_z;
161 | }
162 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
163 |
164 | And we get:
165 |
166 | 
167 |
168 | Better, but there are obvious grid features in there. Some of it is Mach bands, a known perceptual
169 | artifact of linear interpolation of color. A standard trick is to use a hermite cubic to round off
170 | the interpolation:
171 |
172 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
173 | class perlin (
174 | public:
175 | float noise(const vec3& p) const {
176 | float u = p.x() - floor(p.x());
177 | float v = p.y() - floor(p.y());
178 | float w = p.z() - floor(p.z());
179 |
180 | u = u*u*(3-2*u);
181 | v = v*v*(3-2*v);
182 | w = w*w*(3-2*w);
183 |
184 | int i = floor(p.x());
185 | int j = floor(p.y());
186 | int k = floor(p.z());
187 | ...
188 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189 |
190 | This gives a smoother looking image:
191 |
192 | 
193 |
194 | It is also a bit low frequency. We can scale the input point to make it vary more quickly:
195 |
196 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
197 | class noise_texture : public texture {
198 | public:
199 | noise_texture() {}
200 | noise_texture(float sc) : scale(sc) {}
201 | virtual vec3 value(float u, float v, const vec3& p) const {
202 | return vec3(1,1,1) * noise.noise(scale * p);
203 | }
204 | perlin noise;
205 | float scale;
206 | };
207 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
208 |
209 | which gives:
210 |
211 | 
212 |
213 | This is still a bit grid blocky looking, probably because the min and max of the pattern always
214 | lands exactly on the integer x/y/z. Ken Perlin’s very clever trick was to instead put random unit
215 | vectors (instead of just floats) on the lattice points, and use a dot product to move the min and
216 | max off the lattice. So, first we need to change the random floats to random vectors:
217 |
218 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
219 | vec3 *perlin::ranvec = perlin_generate();
220 | int *perlin::perm_x = perlin_generate_perm();
221 | int *perlin::perm_y = perlin_generate_perm();
222 | int *perlin::perm_z = perlin_generate_perm();
223 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224 |
225 | These vectors are any reasonable set of irregular directions, and I won't bother to make them
226 | exactly uniform:
227 |
228 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
229 | static vec3* perlin_generate() {
230 | vec3 * p = new vec3[256];
231 | for ( int i = 0; i < 256; ++i )
232 | p[i] = unit_vector(vec3(-1 + 2*drand48(), -1 + 2*drand48(), -1 + 2*drand48()));
233 | return p;
234 | }
235 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
236 |
237 | The Perlin class is now:
238 |
239 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
240 | class perlin {
241 | public:
242 | float noise(const vec3& p) const {
243 | float u = p.x() - floor(p.x());
244 | float v = p.y() - floor(p.y());
245 | float w = p.z() - floor(p.z());
246 | int i = floor(p.x());
247 | int j = floor(p.y());
248 | int k = floor(p.z());
249 | vec3 c[2][2][2];
250 | for (int di=0; di < 2; di++)
251 | for (int dj=0; dj < 2; dj++)
252 | for (int dk=0; dk < 2; dk++)
253 | c[di][dj][dk] = ranvec[
254 | perm_x[(i+di) & 255] ^
255 | perm_y[(j+dj) & 255] ^
256 | pexm_z[(k+dk) & 255];
257 | ];
258 | return perlin_interp(c, u, v, w);
259 | }
260 | static vec3 *ranvec;
261 | static int *perm_x;
262 | static int *perm_y;
263 | static int *perm_z;
264 | }
265 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266 |
267 | And the interpolation becomes a bit more complicated:
268 |
269 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
270 | inline float perlin_interp(vec3 c[2][2][2], float u, float v, float w) {
271 | float uu = u*u*(3-2*u);
272 | float vv = v*v*(3-2*v);
273 | float ww = w*w*(3-2*w);
274 | float accum = 0;
275 | for (int i=0; i < 2; i++)
276 | for (int j=0; j < 2; j++)
277 | for (int k=0; k < 2; k++) {
278 | vec3 weight_v(u-i, v-j, w-k);
279 | accum += (i*uu + (1-i)*(1-uu))*
280 | (j*vv + (1-j)*(1-vv))*
281 | (k*ww + (1-k)*(1-ww))*dot(c[i][j][k], weight_v);
282 | }
283 | return accum;
284 | }
285 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
286 |
287 | This finally gives something more reasonable looking:
288 |
289 | 
290 |
291 | Very often, a composite noise that has multiple summed frequencies is used. This is usually called
292 | turbulence and is a sum of repeated calls to noise:
293 |
294 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
295 | float turb(const vec3& p, int depth=7) const {
296 | float accum = 0;
297 | vec3 temp_p = p;
298 | float weight = 1.0;
299 | for (int i = 0; i < depth; i++) {
300 | accum += weight*noise(temp_p);
301 | weight *= 0.5;
302 | temp_p *= 2;
303 | }
304 | return fabs(accum);
305 | }
306 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307 |
308 | Here fabs() is the math.h absolute value function.
309 |
310 | Used directly, turbulence gives a sort of camouflage netting appearance:
311 |
312 | 
313 |
314 | However, usually turbulence is used indirectly. For example, the “hello world” of procedural solid
315 | textures is a simple marble-like texture. The basic idea is to make color proportional to something
316 | like a sine function, and use turbulence to adjust the phase (so it shifts x in sin( x )) which
317 | makes the stripes undulate. Commenting out straight noise and turbulence, and giving a marble-like
318 | effect is:
319 |
320 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
321 | class noise_texture : public texture {
322 | public:
323 | noise_texture() {}
324 | noise_texture(float sc) : scale(sc) {}
325 | virtual vec3 value(float u, float v, const vec3& p) const {
326 | // return vec3(1,1,1)*0.5*(1 + noise.turb(scale * p));
327 | // return vec3(1,1,1)*noise.turb(scale * p);
328 | return vec3(1,1,1)*0.5*(1 + sin(scale*p.z() + 10*noise.turb(p))) ;
329 | }
330 | perlin noise;
331 | float scale;
332 | };
333 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334 |
335 | Which yields:
336 |
337 | 
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
--------------------------------------------------------------------------------
/book/ch05.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 5: Image Texture Mapping**
10 |
11 | We used the hitpoint p before to index a procedure solid texture like marble. We can also read in an
12 | image and use a 2D $(u,v)$ texture coordinate to index into the image.
13 |
14 | A direct way to use scaled $(u,v)$ in an image is to round the u and v to integers, and use that as
15 | $(i,j)$ pixels. This is awkward, because we don’t want to have to change the code when we change
16 | image resolution. So instead, one of the the most universal unofficial standards in graphics is to
17 | use texture coordinates instead of image pixel coordinates. These are just some form of fractional
18 | position in the image. For example, for pixel $(i,j)$ in an nx by ny image, the image texture
19 | position is:
20 |
21 | $$ u = i/(nx-1) $$
22 | $$ v = j/(nx-1) $$
23 |
24 | This is just a fractional position. For a hitable, we need to also return a u and v in the hit
25 | record. For spheres, this is usually based on some form of longitude and latitude, _i.e._, spherical
26 | coordinates. So if we have a $(\theta,\phi)$ in spherical coordinates we just need to scale $\theta$
27 | and $\phi$ to fractions. If $\theta$ is the angle down from the pole, and $\phi$ is the angle around
28 | the axis through the poles, the normalization to $[0,1]$ would be:
29 |
30 | $$ u = \phi / (2\pi) $$
31 | $$ v = \theta / \pi $$
32 |
33 | To compute $\theta$ and $\phi$, for a given hitpoint, the formula for spherical coordinates of a
34 | unit radius sphere on the origin is:
35 |
36 | $$ x = cos(\phi) cos(\theta) $$
37 | $$ y = sin(\phi) cos(\theta) $$
38 | $$ z = sin(\theta) $$
39 |
40 | We need to invert that. Because of the lovely math.h function atan2() which takes any number
41 | proportional to sine and cosine and returns the angle, we can pass in x and y (the cos(theta)
42 | cancel):
43 |
44 | $$ \phi = atan2(y, x) $$
45 |
46 | The atan2 returns in the range $-\pi$ to $\pi$ so we need to take a little care there. The $\theta$
47 | is more straightforward:
48 |
49 | $$ \theta = asin(z) $$
50 |
51 | which returns numbers in the range $-\pi/2$ to $\pi/2$.
52 |
53 | So for a sphere, the $(u,v)$ coord computation is accomplished by a utility function that expects
54 | things on the unit sphere centered at the origin. The call inside sphere::hit should be:
55 |
56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
57 | get_sphere_uv((rec.p-center)/radius, rec.u, rec.v);
58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59 |
60 | The utility function is:
61 |
62 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
63 | void get_sphere_uv(const vec3& p, float& u, float& v) {
64 | float phi = atan2(p.z(), p.x());
65 | float theta = asin(p.y());
66 | u = 1-(phi + M_PI) / (2*M_PI);
67 | v = (theta + M_PI/2) / M_PI;
68 | }
69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70 |
71 | Now we also need to create a texture class that holds an image. I am going to use my favorite image
72 | utility `stb_image`. It reads in an image into a big array of unsigned char. These are just packed
73 | RGBs that each range 0..255 for black to fully-on.
74 |
75 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
76 | class image_texture : public texture {
77 | public:
78 | image_texture() {}
79 | image_texture(unsigned char *pixels, int A, int B)
80 | : data(pixels), nx(A), ny(B) {}
81 | virtual vec3 value(float u, float v, const vec3& p) const;
82 | unsigned char *data;
83 | int nx, ny;
84 | };
85 |
86 | vec3 image_texture::value(float u, float v, const vec3& p) const {
87 | int i = ( u) * nx;
88 | int j = (1-v) * ny - 0.001;
89 | if (i < 0) i = 0;
90 | if (j < 0) j = 0;
91 | if (i > nx-1) i = nx-1;
92 | if (j > ny-1) j = ny-1;
93 | float r = int(data[3*i + 3*nx*j] ) / 255.0;
94 | float g = int(data[3*i + 3*nx*j+1]) / 255.0;
95 | float b = int(data[3*i + 3*nx*j+2]) / 255.0;
96 | return vec3(r, g, b);
97 | }
98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99 |
100 | The representation of a packed array in that order is pretty standard. Thankfully, the `stb_image`
101 | package makes that super simple -- just include the header in main.h with a #define:
102 |
103 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
104 | #define STB_IMAGE_IMPLEMENTATION
105 | #include "stb_image.h"
106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107 |
108 | To read an image from a file eathmap.jpg (I just grabbed a random earth map from the web -- any
109 | standard projection will do for our purposes), and then assign it to a diffuse material, the code
110 | is:
111 |
112 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
113 | int nx, ny, nn;
114 | unsigned char *tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
115 | material *mat = new lambertian(new image_texture(tex_data, nx, ny));
116 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117 |
118 | We start to see some of the power of all colors being textures -- we can assign any kind of texture
119 | to the lambertian material, and lambertian doesn’t need to be aware of it.
120 |
121 | To test this, assign it to a sphere, and then temporarily cripple the color() function in main to
122 | just return attenuation. You should get something like:
123 |
124 | 
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/book/ch06.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 6 Rectangles and Lights**
10 |
11 | First, let’s make a light emitting material. We need to add an emitted function (we could also add
12 | it to hit_record instead -- that’s a matter of design taste). Like the background, it just tells the
13 | ray what color it is and performs no reflection. It’s very simple:
14 |
15 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
16 | class diffuse_light : public material {
17 | public:
18 | diffuse_light(texture *a) : emit(a) {}
19 | virtual bool scatter(const ray& r_in, const hit_record& rec,
20 | vec3& attenuation, ray& scattered) const { return false; }
21 | virtual vec3 emitted(float u, float v, const vec3& p) const {
22 | return emit->value(u, v, p);
23 | }
24 | texture *emit;
25 | };
26 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 |
28 | So that I don’t have to make all the non-emitting materials implement emitted(), I have the base
29 | class return black:
30 |
31 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
32 | class material {
33 | public:
34 | virtual bool scatter(const ray& r_in, const hit_record& rec,
35 | vec3& attenuation, ray& scattered) const = 0;
36 | virtual vec3 emitted(float u, float v, const vec3& p) const {
37 | return vec3(0,0,0);
38 | }
39 | };
40 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 |
42 | Next, let’s make the background black in our color function, and pay attention to emitted:
43 |
44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
45 | vec3 color(const ray& r, hitable *world, int depth) {
46 | hit_record rec;
47 | if (world->hit(r, 0.001, MAXFLOAT, rec)) {
48 | ray scattered;
49 | vec3 attenuation;
50 | vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
51 | if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
52 | return emitted + attenuation*color(scattered, world, depth+1);
53 | else
54 | return emitted;
55 | }
56 | else
57 | return vec3(0,0,0);
58 | }
59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60 |
61 | Now, let’s make some rectangles. Rectangles are often convenient for modelling man-made
62 | environments. I’m a fan of doing axis-aligned rectangles because they are easy. (We’ll get to
63 | instancing so we can rotate them later.)
64 |
65 | First, here is a rectangle in an xy plane. Such a plane is defined by its z value. For example, $z =
66 | k$. An axis-aligned rectangle is defined by lines $x=x_0$ , $x=x_1$ , $y=y_0$ , $y=y_1$.
67 |
68 | 
69 |
70 | To determine whether a ray hits such a rectangle, we first determine where the ray hits the plane.
71 | Recall that a ray $p(t) = a + t \cdot b$ has its z component defined by $z(t) = a_z + t \cdot b_z$.
72 | Rearranging those terms we can solve for what the t is where $z=k$.
73 |
74 | $$ t = (k-a_z) / b_z $$
75 |
76 | Once we have t, we can plug that into the equations for x and y:
77 |
78 | $$ x = a_x + t*b_x $$
79 | $$ y = a_y + t*b_y $$
80 |
81 | It is a hit if $x_0 < x < x_1$ and $y_0 < y < y_1$.
82 |
83 | The actual xy_rect class is thus:
84 |
85 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
86 | class xy_rect: public hitable {
87 | public:
88 | xy_rect() {}
89 | xy_rect(float _x0, float _x1, float _y0, float _y1, float _k, material *mat)
90 | : x0(_x0), x1(_x1), y0(_y0), y1(_y1), k(_k), mp(mat) {};
91 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
92 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
93 | box = aabb(vec3(x0,y0, k-0.0001), vec3(x1, y1, k+0.0001));
94 | return true;
95 | }
96 | material *mp;
97 | float x0, x1, y0, y1, k;
98 | };
99 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100 |
101 | And the hit function is:
102 |
103 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
104 | bool xy_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
105 | float t = (k-r.origin().z()) / r.direction().z();
106 | if (t < t0 || t > t1)
107 | return false;
108 | float x = r.origin().x() + t*r.direction().x();
109 | float y = r.origin().y() + t*r.direction().y();
110 | if (x < x0 || x > x1 || y < y0 || y > y1)
111 | return false;
112 | rec.u = (x-x0)/(x1-x0);
113 | rec.v = (y-y0)/(y1-y0);
114 | rec.t = t;
115 | rec.mat_ptr = mp;
116 | rec.p = r.point_at_parameter(t);
117 | rec.normal = vec3(0, 0, 1);
118 | return true;
119 | }
120 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121 |
122 | If we set up a rectangle as a light:
123 |
124 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
125 | hitable *simple_light() {
126 | texture *pertext = new noise_texture(4);
127 | hitable **list = new hitable*[4];
128 | list[0] = new sphere(vec3(0,-1000, 0), 1000, new lambertian(pertext));
129 | list[1] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));
130 | list[2] = new sphere(vec3(0, 7, 0), 2,
131 | new diffuse_light(new constant_texture(vec3(4,4,4))));
132 | list[3] = new xy_rect(3, 5, 1, 3, -2,
133 | new diffuse_light(new constant_texture(vec3(4,4,4))));
134 | return new hitable_list(list,4);
135 | }
136 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
137 |
138 | We get:
139 |
140 | 
141 |
142 | Note that the light is brighter than $(1,1,1)$. This allows it to be bright enough to light things.
143 |
144 | Fool around with making some spheres lights too.
145 |
146 | 
147 |
148 | Now let’s add the other two axes and the famous Cornell Box.
149 |
150 | This is yz and xz.
151 |
152 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
153 | class xz_rect: public hitable {
154 | public:
155 | xz_rect() {}
156 | xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat)
157 | : x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
158 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
159 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
160 | box = aabb(vec3(x0,k-0.0001,z0), vec3(x1, k+0.0001, z1));
161 | return true;
162 | }
163 | material *mp;
164 | float x0, x1, z0, z1, k;
165 | };
166 |
167 | class yz_rect: public hitable {
168 | public:
169 | yz_rect() {}
170 | yz_rect(float _y0, float _y1, float _z0, float _z1, float _k, material *mat)
171 | : y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
172 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
173 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
174 | box = aabb(vec3(k-0.0001, y0, z0), vec3(k+0.0001, y1, z1));
175 | return true;
176 | }
177 | material *mp;
178 | float y0, y1, z0, z1, k;
179 | };
180 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181 |
182 | With unsurprising hit functions:
183 |
184 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
185 | bool xz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
186 | float t = (k-r.origin().y()) / r.direction().y();
187 | if (t < t0 || t > t1)
188 | return false;
189 | float x = r.origin().x() + t*r.direction().x();
190 | float z = r.origin().z() + t*r.direction().z();
191 | if (x < x0 || x > x1 || z < z0 || z > z1)
192 | return false;
193 | rec.u = (x-x0)/(x1-x0);
194 | rec.v = (z-z0)/(z1-z0);
195 | rec.t = t;
196 | rec.mat_ptr = mp;
197 | rec.p = r.point_at_parameter(t);
198 | rec.normal = vec3(0, 1, 0);
199 | return true;
200 | }
201 |
202 | bool yz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
203 | float t = (k-r.origin().x()) / r.direction().x();
204 | if (t < t0 || t > t1)
205 | return false;
206 | float y = r.origin().y() + t*r.direction().y();
207 | float z = r.origin().z() + t*r.direction().z();
208 | if (y < y0 || y > y1 || z < z0 || z > z1)
209 | return false;
210 | rec.u = (y-y0)/(y1-y0);
211 | rec.v = (z-z0)/(z1-z0);
212 | rec.t = t;
213 | rec.mat_ptr = mp;
214 | rec.p = r.point_at_parameter(t);
215 | rec.normal = vec3(1, 0, 0);
216 | return true;
217 | }
218 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
219 |
220 | Let’s make the 5 walls and the light of the box:
221 |
222 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
223 | hitable *cornell_box() {
224 | hitable **list = new hitable*[8];
225 | int i = 0;
226 | material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
227 | material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
228 | material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
229 | material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
230 |
231 | list[i++] = new yz_rect(0, 555, 0, 555, 555, green);
232 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
233 | list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
234 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
235 | list[i++] = new xy_rect(0, 555, 0, 555, 555, white);
236 |
237 | return new hitable_list(list,i);
238 | }
239 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
240 |
241 | And the view info:
242 |
243 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
244 | vec3 lookfrom(278, 278, -800);
245 | vec3 lookat(278,278,0);
246 | float dist_to_focus = 10.0;
247 | float aperture = 0.0;
248 | float vfov = 40.0;
249 |
250 | camera cam(lookfrom, lookat, vec3(0,1,0), vfov, float(nx)/float(ny),
251 | aperture, dist_to_focus, 0.0, 1.0);
252 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253 |
254 | We get:
255 |
256 | 
257 |
258 | This is very noisy because the light is small. But why are the other walls missing? They are facing
259 | the wrong way. We need outward facing normals. Let’s make a hitable that does nothing but hold
260 | another hitable, but reverses the normals:
261 |
262 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
263 | class flip_normals : public hitable {
264 | public:
265 | flip_normals(hitable *p) : ptr(p) {}
266 |
267 | virtual bool hit(
268 | const ray& r, float t_min, float t_max, hit_record& rec) const {
269 |
270 | if (ptr->hit(r, t_min, t_max, rec)) {
271 | rec.normal = -rec.normal;
272 | return true;
273 | }
274 | else
275 | return false;
276 | }
277 |
278 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
279 | return ptr->bounding_box(t0, t1, box);
280 | }
281 |
282 | hitable *ptr;
283 | };
284 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
285 |
286 | This makes Cornell:
287 |
288 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
289 | hitable *cornell_box() {
290 | hitable **list = new hitable*[6];
291 | int i = 0;
292 | material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
293 | material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
294 | material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
295 | material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
296 |
297 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
298 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
299 | list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
300 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
301 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
302 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
303 |
304 | return new hitable_list(list,i);
305 | }
306 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307 |
308 | And voila:
309 |
310 | 
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
--------------------------------------------------------------------------------
/book/ch07.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 7 Instances**
10 |
11 | The Cornell Box usually has two blocks in it. These are rotated relative to the walls. First, let’s
12 | make an axis-aligned block primitive that holds 6 rectangles:
13 |
14 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
15 | class box: public hitable {
16 | public:
17 | box() {}
18 | box(const vec3& p0, const vec3& p1, material *ptr);
19 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
20 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
21 | box = aabb(pmin, pmax);
22 | return true;
23 | }
24 | vec3 pmin, pmax;
25 | hitable *list_ptr;
26 | };
27 |
28 | box::box(const vec3& p0, const vec3& p1, material *ptr) {
29 | pmin = p0;
30 | pmax = p1;
31 | hitable **list = new hitable*[6];
32 | list[0] = new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p1.z(), ptr);
33 | list[1] = new flip_normals(
34 | new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p0.z(), ptr));
35 | list[2] = new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p1.y(), ptr);
36 | list[3] = new flip_normals(
37 | new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p0.y(), ptr));
38 | list[4] = new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p1.x(), ptr);
39 | list[5] = new flip_normals(
40 | new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p0.x(), ptr));
41 | list_ptr = new hitable_list(list,6);
42 | }
43 |
44 | bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const {
45 | return list_ptr->hit(r, t0, t1, rec);
46 | }
47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48 |
49 | Now we can add two blocks (but not rotated)
50 |
51 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
52 | list[i++] = new box(vec3(130, 0, 65), vec3(295, 165, 230), white);
53 | list[i++] = new box(vec3(265, 0, 295), vec3(430, 330, 460), white);
54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 |
56 | This gives:
57 |
58 | 
59 |
60 | Now that we have boxes, we need to rotate them a bit to have them match the _real_ Cornell box. In
61 | ray tracing, this is usually done with an _instance_. An instance is a geometric primitive that has
62 | been moved or rotated somehow. This is especially easy in ray tracing because we don’t move
63 | anything; instead we move the rays in the opposite direction. For example, consider a _translation_
64 | (often called a _move_). We could take the pink box at the origin and add 2 to all its x components,
65 | or (as we almost always do in ray tracing) leave the box where it is, but in its hit routine
66 | subtract 2 off the x-component of the ray origin.
67 |
68 | 
69 |
70 | Whether you think of this as a move or a change of coordinates is up to you. The code for this, to
71 | move any underlying hitable is a _translate_ instance.
72 |
73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
74 | class translate : public hitable {
75 | public:
76 | translate(hitable *p, const vec3& displacement)
77 | : ptr(p), offset(displacement) {}
78 | virtual bool hit(
79 | const ray& r, float t_min, float t_max, hit_record& rec) const;
80 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
81 | hitable *ptr;
82 | vec3 offset;
83 | };
84 |
85 | bool translate::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
86 | ray moved_r(r.origin() - offset, r.direction(), r.time());
87 | if (ptr->hit(moved_r, t_min, t_max, rec)) {
88 | rec.p += offset;
89 | return true;
90 | }
91 | else
92 | return false;
93 | }
94 |
95 | bool translate::bounding_box(float t0, float t1, aabb& box) const {
96 | if (ptr->bounding_box(t0, t1, box)) {
97 | box = aabb(box.min() + offset, box.max() + offset);
98 | return true;
99 | }
100 | else
101 | return false;
102 | }
103 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104 |
105 | Rotation isn’t quite as easy to understand or generate the formulas for. A common graphics tactic is
106 | to apply all rotations about the x, y, and z axes. These rotations are in some sense axis-aligned.
107 | First, let’s rotate by theta about the z-axis. That will be changing only x and y, and in ways that
108 | don’t depend on z.
109 |
110 | 
111 |
112 | This involves some basic trigonometry that uses formulas that I will not cover here. That gives you
113 | the correct impression it’s a little involved, but it is straightforward, and you can find it in any
114 | graphics text and in many lecture notes. The result for rotating counter-clockwise about z is:
115 |
116 | $$ x\prime = cos(\theta) \cdot x - sin(\theta) \cdot y $$
117 | $$ y\prime = sin(\theta) \cdot x + cos(\theta) \cdot y $$
118 |
119 | The great thing is that it works for any theta and doesn’t need any cases for quadrants or anything
120 | like that. The inverse transform is the opposite geometric operation: rotate by -theta. Here, recall
121 | that cos(theta) = cos(-theta) and sin(-theta) = -sin(theta), so the formulas are very simple.
122 |
123 | Similarly, for rotating about y (as we want to do for the blocks in the box) the formulas are:
124 |
125 | $$ x\prime = cos(\theta) \cdot x + sin(\theta) \cdot z $$
126 | $$ z\prime = -sin(\theta) \cdot x + cos(\theta) \cdot z $$
127 |
128 | And about the x-axis:
129 |
130 | $$ y\prime = cos(\theta) \cdot y - sin(\theta) \cdot z $$
131 | $$ z\prime = sin(\theta) \cdot y + cos(\theta) \cdot z $$
132 |
133 | Unlike the situation with translations, the surface normal vector also changes, so we need to
134 | transform directions too if we get a hit. Fortunately for rotations, the same formulas apply. If you
135 | add scales, things get more complicated. See the web page www.in1weekend.com for links to that.
136 |
137 | For a y-rotation class we have:
138 |
139 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
140 | class rotate_y : public hitable {
141 | public:
142 | rotate_y(hitable *p, float angle);
143 | virtual bool hit(
144 | const ray& r, float t_min, float t_max, hit_record& rec) const;
145 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
146 | box = bbox; return hasbox;
147 | }
148 | hitable *ptr;
149 | float sin_theta;
150 | float cos_theta;
151 | bool hasbox;
152 | aabb bbox;
153 | };
154 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
155 |
156 | With constructor:
157 |
158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
159 | rotate_y::rotate_y(hitable *p, float angle) : ptr(p) {
160 | float radians = (M_PI / 180.) * angle;
161 | sin_theta = sin(radians);
162 | cos_theta = cos(radians);
163 | hasbox = ptr->bounding_box(0, 1, bbox);
164 | vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
165 | vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
166 | for (int i = 0; i < 2; i++) {
167 | for (int j = 0; j < 2; j++) {
168 | for (int k = 0; k < 2; k++) {
169 | float x = i*bbox.max().x() + (1-i)*bbox.min().x();
170 | float y = j*bbox.max().y() + (1-j)*bbox.min().y();
171 | float z = k*bbox.max().z() + (1-k)*bbox.min().z();
172 | float newx = cos_theta*x + sin_theta*z;
173 | float newz = -sin_theta*x + cos_theta*z;
174 | vec3 tester(newx, y, newz);
175 | for ( int c = 0; c < 3; c++ )
176 | {
177 | if ( tester[c] > max[c] )
178 | max[c] = tester[c];
179 | if ( tester[c] < min[c] )
180 | min[c] = tester[c];
181 | }
182 | }
183 | }
184 | }
185 | bbox = aabb(min, max);
186 | }
187 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
188 |
189 | And the hit function:
190 |
191 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
192 | bool rotate_y::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
193 | vec3 origin = r.origin();
194 | vec3 direction = r.direction();
195 | origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
196 | origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
197 | direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
198 | direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
199 | ray rotated_r(origin, direction, r.time());
200 | if (ptr->hit(rotated_r, t_min, t_max, rec)) {
201 | vec3 p = rec.p;
202 | vec3 normal = rec.normal;
203 | p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
204 | p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
205 | normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
206 | normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
207 | rec.p = p;
208 | rec.normal = normal;
209 | return true;
210 | }
211 | else
212 | return false;
213 | }
214 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215 |
216 | And the changes to Cornell is:
217 |
218 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
219 | list[i++] = new translate(
220 | new rotate_y(new box(vec3(0,0,0), vec3(165,165,165), white), -18),
221 | vec3(130,0,65)
222 | );
223 | list[i++] = new translate(
224 | new rotate_y(new box(vec3(0,0,0), vec3(165,330,165), white), 15),
225 | vec3(265,,0,295)
226 | );
227 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
228 |
229 | Which yields:
230 |
231 | 
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
--------------------------------------------------------------------------------
/book/ch08.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 8 Volumes**
10 |
11 | One thing it’s nice to add to a ray tracer is smoke/fog/mist. These are sometimes called _volumes_
12 | or _participating media_. Another feature that is nice to add is subsurface scattering, which is
13 | sort of like dense fog inside an object. This usually adds software architectural mayhem because
14 | volumes are a different animal than surfaces. But a cute technique is to make a volume a random
15 | surface. A bunch of smoke can be replaced with a surface that probabilistically might or might not
16 | be there at every point in the volume. This will make more sense when you see the code.
17 |
18 | First, let’s start with a volume of constant density. A ray going through there can either scatter
19 | inside the volume, or it can make it all the way through like the middle ray in the figure. More
20 | thin transparent volumes, like a light fog, are more likely to have rays like the middle one. How
21 | far the ray has to travel through the volume also determines how likely it is for the ray to make it
22 | through.
23 |
24 | 
25 |
26 | As the ray passes through the volume, it may scatter at any point. The denser the volume, the more
27 | likely that is. The probability that the ray scatters in any small distance $\delta L$ is:
28 |
29 | $$ probability = C \cdot \delta L $$
30 |
31 | where $C$ is proportional to the optical density of the volume. If you go through all the differential
32 | equations, for a random number you get a distance where the scattering occurs. If that distance is
33 | outside the volume, then there is no “hit”. For a constant volume we just need the density $C$ and the
34 | boundary. I’ll use another hitable for the boundary. The resulting class is:
35 |
36 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
37 | class constant_medium : public hitable {
38 | public:
39 | constant_medium(hitable *b, float d, texture *a)
40 | : boundary(b), density(d) {
41 |
42 | phase_function = new isotropic(a);
43 | }
44 | virtual bool hit(
45 | const ray& r, float t_min, float t_max, hit_record& rec) const;
46 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
47 | return boundary->bounding_box(t0, t1, box);
48 | }
49 | hitable *boundary;
50 | float density;
51 | material *phase_function;
52 | };
53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 |
55 | The scattering function of isotropic picks a uniform random direction:
56 |
57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
58 | class isotropic : public material {
59 | public:
60 | isotropic(texture *a) : albedo(a) {}
61 | virtual bool scatter(
62 | const ray& r_in,
63 | const hit_record& rec,
64 | vec3& attenuation,
65 | ray& scattered) const {
66 |
67 | scattered = ray(rec.p, random_in_unit_sphere());
68 | attenuation = albedo->value(rec.u, rec.v, rec.p);
69 | return true;
70 | }
71 | texture *albedo;
72 | };
73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74 |
75 | And the hit function is:
76 |
77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
78 | bool constant_medium::hit(const ray& r, float t_min, float t_max, hit_record& rec)
79 | const {
80 |
81 | // Print occasional samples when debugging. To enable, set enableDebug true.
82 | const enableDebug = false;
83 | bool debugging = enableDebug && drand48() < 0.00001;
84 |
85 | hit_record rec1, rec2;
86 |
87 | if (boundary->hit(r, -FLT_MAX, FLT_MAX, rec1)) {
88 | if (boundary->hit(r, rec1.t+0.0001, FLT_MAX, rec2)) {
89 |
90 | if (debugging) std::cerr << "\nt0 t1 " << rec1.t << " " << rec2.t << '\n';
91 |
92 | if (rec1.t < t_min) rec1.t = t_min;
93 | if (rec2.t > t_max) rec2.t = t_max;
94 |
95 | if (rec1.t >= rec2.t)
96 | return false;
97 | if (rec1.t < 0)
98 | rec1.t = 0;
99 |
100 | float distance_inside_boundary = (rec2.t - rec1.t)*r.direction().length();
101 | float hit_distance = -(1/density) * log(drand48());
102 |
103 | if (hit_distance < distance_inside_boundary) {
104 |
105 | rec.t = rec1.t + hit_distance / r.direction().length();
106 | rec.p = r.point_at_parameter(rec.t);
107 |
108 | if (debugging) {
109 | std::cerr << "hit_distance = " << hit_distance << '\n'
110 | << "rec.t = " << rec.t << '\n'
111 | << "rec.p = " << rec.p << '\n';
112 | }
113 |
114 | rec.normal = vec3(1,0,0); // arbitrary
115 | rec.mat_ptr = phase_function;
116 | return true;
117 | }
118 | }
119 | }
120 | return false;
121 | }
122 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123 |
124 | The reason we have to be so careful about the logic around the boundary is we need to make sure this
125 | works for ray origins inside the volume. In clouds, things bounce around a lot so that is a common
126 | case.
127 |
128 | If we replace the two blocks with smoke and fog (dark and light particles) and make the light bigger
129 | (and dimmer so it doesn’t blow out the scene) for faster convergence:
130 |
131 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
132 | hitable *cornell_smoke() {
133 | hitable **list = new hitable*[8];
134 | int i = 0;
135 | material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
136 | material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
137 | material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
138 | material *light = new diffuse_light(new constant_texture(vec3(7, 7, 7)));
139 |
140 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
141 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
142 | list[i++] = new xz_rect(113, 443, 127, 432, 554, light);
143 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
144 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
145 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
146 |
147 | hitable *b1 = new translate(
148 | new rotate_y(new box(vec3(0, 0, 0), vec3(165, 165, 165), white), -18),
149 | vec3(130,0,65));
150 | hitable *b2 = new translate(
151 | new rotate_y(new box(vec3(0, 0, 0), vec3(165, 330, 165), white), 15),
152 | vec3(265,0,295));
153 |
154 | list[i++] = new constant_medium(
155 | b1, 0.01, new constant_texture(vec3(1.0, 1.0, 1.0)));
156 | list[i++] = new constant_medium(
157 | b2, 0.01, new constant_texture(vec3(0.0, 0.0, 0.0)));
158 |
159 | return new hitable_list(list,i);
160 | }
161 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162 |
163 | We get:
164 |
165 | 
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/book/ch09.md.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 |
9 | **Chapter 9: A Scene Testing All New Features**
10 |
11 | Let’s put it all together, with a big thin mist covering everything, and a blue subsurface
12 | reflection sphere (we didn’t implement that explicitly, but a volume inside a dielectric is what a
13 | subsurface material is). The biggest limitation left in the renderer is no shadow rays, but that is
14 | why we get caustics and subsurface for free. It’s a double-edged design decision.
15 |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
17 | hitable *final() {
18 | int nb = 20;
19 | hitable **list = new hitable*[30];
20 | hitable **boxlist = new hitable*[10000];
21 | hitable **boxlist2 = new hitable*[10000];
22 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)));
23 | material *ground = new lambertian( new constant_texture(vec3(0.48, 0.83, 0.53)));
24 | int b = 0;
25 | for (int i = 0; i < nb; i++) {
26 | for (int j = 0; j < nb; j++) {
27 | float w = 100;
28 | float x0 = -1000 + i*w;
29 | float z0 = -1000 + j*w;
30 | float y0 = 0;
31 | float x1 = x0 + w;
32 | float y1 = 100*(drand48()+0.01);
33 | float z1 = z0 + w;
34 | boxlist[b++] = new box(vec3(x0,y0,z0), vec3(x1,y1,z1), ground);
35 | }
36 | }
37 | int l = 0;
38 | list[l++] = new bvh_node(boxlist, b, 0, 1);
39 | material *light = new diffuse_light( new constant_texture(vec3(7, 7, 7)) );
40 | list[l++] = new xz_rect(123, 423, 147, 412, 554, light);
41 | vec3 center(400, 400, 200);
42 | list[l++] = new moving_sphere(center, center+vec3(30, 0, 0),
43 | 0, 1, 50, new lambertian(new constant_texture(vec3(0.7, 0.3, 0.1))));
44 | list[l++] = new sphere(vec3(260, 150, 45), 50, new dielectric(1.5));
45 | list[l++] = new sphere(vec3(0, 150, 145), 50,
46 | new metal(vec3(0.8, 0.8, 0.9), 10.0));
47 | hitable *boundary = new sphere(vec3(360, 150, 145), 70, new dielectric(1.5));
48 | list[l++] = boundary;
49 | list[l++] = new constant_medium(boundary, 0.2,
50 | new constant_texture(vec3(0.2, 0.4, 0.9)));
51 | boundary = new sphere(vec3(0, 0, 0), 5000, new dielectric(1.5));
52 | list[l++] = new constant_medium(boundary, 0.0001,
53 | new constant_texture(vec3(1.0, 1.0, 1.0)));
54 | int nx, ny, nn;
55 | unsigned char *tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
56 | material *emat = new lambertian(new image_texture(tex_data, nx, ny));
57 | list[l++] = new sphere(vec3(400,200, 400), 100, emat);
58 | texture *pertext = new noise_texture(0.1);
59 | list[l++] = new sphere(vec3(220,280, 300), 80, new lambertian( pertext ));
60 | int ns = 1000;
61 | for (int j = 0; j < ns; j++) {
62 | boxlist2[j] = new sphere(
63 | vec3(165*drand48(), 165*drand48(), 165*drand48()), 10, white);
64 | }
65 | list[l++] = new translate(new rotate_y(
66 | new bvh_node(boxlist2,ns, 0.0, 1.0), 15), vec3(-100,270,395));
67 | return new hitable_list(list,l);
68 | }
69 |
70 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
71 |
72 | Running it with 10,000 rays per pixel yields:
73 |
74 | 
75 |
76 | Now go off and make a really cool image of your own! See http://in1weekend.com/ for pointers to
77 | further reading and features, and feel free to email questions, comments, and cool images to me at
78 | ptrshrl@gmail.com.
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WARNING
Content Under Development
4 | See
release page for
5 | latest official PDF version.
6 |
7 |
8 | **Rough Content**
9 |
10 | - [Chapter 0](book/ch00.md.html)
11 | - [Chapter 1](book/ch01.md.html)
12 | - [Chapter 2](book/ch02.md.html)
13 | - [Chapter 3](book/ch03.md.html)
14 | - [Chapter 4](book/ch04.md.html)
15 | - [Chapter 5](book/ch05.md.html)
16 | - [Chapter 6](book/ch06.md.html)
17 | - [Chapter 7](book/ch07.md.html)
18 | - [Chapter 8](book/ch08.md.html)
19 | - [Chapter 9](book/ch09.md.html)
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/aabb.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef AABBH
13 | #define AABBH
14 | #include "ray.h"
15 | #include "hitable.h"
16 |
17 | inline float ffmin(float a, float b) { return a < b ? a : b; }
18 | inline float ffmax(float a, float b) { return a > b ? a : b; }
19 |
20 | class aabb {
21 | public:
22 | aabb() {}
23 | aabb(const vec3& a, const vec3& b) { _min = a; _max = b;}
24 |
25 | vec3 min() const {return _min; }
26 | vec3 max() const {return _max; }
27 |
28 | bool hit(const ray& r, float tmin, float tmax) const {
29 | for (int a = 0; a < 3; a++) {
30 | float t0 = ffmin((_min[a] - r.origin()[a]) / r.direction()[a],
31 | (_max[a] - r.origin()[a]) / r.direction()[a]);
32 | float t1 = ffmax((_min[a] - r.origin()[a]) / r.direction()[a],
33 | (_max[a] - r.origin()[a]) / r.direction()[a]);
34 | tmin = ffmax(t0, tmin);
35 | tmax = ffmin(t1, tmax);
36 | if (tmax <= tmin)
37 | return false;
38 | }
39 | return true;
40 | }
41 |
42 | vec3 _min;
43 | vec3 _max;
44 | };
45 |
46 | aabb surrounding_box(aabb box0, aabb box1) {
47 | vec3 small( ffmin(box0.min().x(), box1.min().x()),
48 | ffmin(box0.min().y(), box1.min().y()),
49 | ffmin(box0.min().z(), box1.min().z()));
50 | vec3 big ( ffmax(box0.max().x(), box1.max().x()),
51 | ffmax(box0.max().y(), box1.max().y()),
52 | ffmax(box0.max().z(), box1.max().z()));
53 | return aabb(small,big);
54 | }
55 |
56 |
57 | #endif
58 |
59 |
--------------------------------------------------------------------------------
/src/aarect.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef AARECTH
13 | #define AARECTH
14 |
15 | #include "hitable.h"
16 |
17 | class xy_rect: public hitable {
18 | public:
19 | xy_rect() {}
20 | xy_rect(float _x0, float _x1, float _y0, float _y1, float _k, material *mat) : x0(_x0), x1(_x1), y0(_y0), y1(_y1), k(_k), mp(mat) {};
21 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
22 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
23 | box = aabb(vec3(x0,y0, k-0.0001), vec3(x1, y1, k+0.0001));
24 | return true; }
25 | material *mp;
26 | float x0, x1, y0, y1, k;
27 | };
28 |
29 | class xz_rect: public hitable {
30 | public:
31 | xz_rect() {}
32 | xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat) : x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
33 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
34 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
35 | box = aabb(vec3(x0,k-0.0001,z0), vec3(x1, k+0.0001, z1));
36 | return true; }
37 | material *mp;
38 | float x0, x1, z0, z1, k;
39 | };
40 |
41 | class yz_rect: public hitable {
42 | public:
43 | yz_rect() {}
44 | yz_rect(float _y0, float _y1, float _z0, float _z1, float _k, material *mat) : y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
45 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
46 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
47 | box = aabb(vec3(k-0.0001, y0, z0), vec3(k+0.0001, y1, z1));
48 | return true; }
49 | material *mp;
50 | float y0, y1, z0, z1, k;
51 | };
52 |
53 |
54 |
55 |
56 | bool xy_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
57 | float t = (k-r.origin().z()) / r.direction().z();
58 | if (t < t0 || t > t1)
59 | return false;
60 | float x = r.origin().x() + t*r.direction().x();
61 | float y = r.origin().y() + t*r.direction().y();
62 | if (x < x0 || x > x1 || y < y0 || y > y1)
63 | return false;
64 | rec.u = (x-x0)/(x1-x0);
65 | rec.v = (y-y0)/(y1-y0);
66 | rec.t = t;
67 | rec.mat_ptr = mp;
68 | rec.p = r.point_at_parameter(t);
69 | rec.normal = vec3(0, 0, 1);
70 | return true;
71 | }
72 |
73 |
74 | bool xz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
75 | float t = (k-r.origin().y()) / r.direction().y();
76 | if (t < t0 || t > t1)
77 | return false;
78 | float x = r.origin().x() + t*r.direction().x();
79 | float z = r.origin().z() + t*r.direction().z();
80 | if (x < x0 || x > x1 || z < z0 || z > z1)
81 | return false;
82 | rec.u = (x-x0)/(x1-x0);
83 | rec.v = (z-z0)/(z1-z0);
84 | rec.t = t;
85 | rec.mat_ptr = mp;
86 | rec.p = r.point_at_parameter(t);
87 | rec.normal = vec3(0, 1, 0);
88 | return true;
89 | }
90 |
91 | bool yz_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {
92 | float t = (k-r.origin().x()) / r.direction().x();
93 | if (t < t0 || t > t1)
94 | return false;
95 | float y = r.origin().y() + t*r.direction().y();
96 | float z = r.origin().z() + t*r.direction().z();
97 | if (y < y0 || y > y1 || z < z0 || z > z1)
98 | return false;
99 | rec.u = (y-y0)/(y1-y0);
100 | rec.v = (z-z0)/(z1-z0);
101 | rec.t = t;
102 | rec.mat_ptr = mp;
103 | rec.p = r.point_at_parameter(t);
104 | rec.normal = vec3(1, 0, 0);
105 | return true;
106 | }
107 |
108 | #endif
109 |
--------------------------------------------------------------------------------
/src/box.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef BOXH
13 | #define BOXH
14 |
15 | #include "aarect.h"
16 | #include "hitable_list.h"
17 |
18 | class box: public hitable {
19 | public:
20 | box() {}
21 | box(const vec3& p0, const vec3& p1, material *ptr);
22 | virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
23 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
24 | box = aabb(pmin, pmax);
25 | return true; }
26 | vec3 pmin, pmax;
27 | hitable *list_ptr;
28 | };
29 |
30 | box::box(const vec3& p0, const vec3& p1, material *ptr) {
31 | pmin = p0;
32 | pmax = p1;
33 | hitable **list = new hitable*[6];
34 | list[0] = new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p1.z(), ptr);
35 | list[1] = new flip_normals(new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p0.z(), ptr));
36 | list[2] = new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p1.y(), ptr);
37 | list[3] = new flip_normals(new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p0.y(), ptr));
38 | list[4] = new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p1.x(), ptr);
39 | list[5] = new flip_normals(new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p0.x(), ptr));
40 | list_ptr = new hitable_list(list,6);
41 | }
42 |
43 | bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const {
44 | return list_ptr->hit(r, t0, t1, rec);
45 | }
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/src/bvh.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef BVHH
13 | #define BVHH
14 |
15 | #include "hitable.h"
16 |
17 | class bvh_node : public hitable {
18 | public:
19 | bvh_node() {}
20 | bvh_node(hitable **l, int n, float time0, float time1);
21 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
22 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
23 | hitable *left;
24 | hitable *right;
25 | aabb box;
26 | };
27 |
28 |
29 | bool bvh_node::bounding_box(float t0, float t1, aabb& b) const {
30 | b = box;
31 | return true;
32 | }
33 |
34 | bool bvh_node::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
35 | if (box.hit(r, t_min, t_max)) {
36 | hit_record left_rec, right_rec;
37 | bool hit_left = left->hit(r, t_min, t_max, left_rec);
38 | bool hit_right = right->hit(r, t_min, t_max, right_rec);
39 | if (hit_left && hit_right) {
40 | if (left_rec.t < right_rec.t)
41 | rec = left_rec;
42 | else
43 | rec = right_rec;
44 | return true;
45 | }
46 | else if (hit_left) {
47 | rec = left_rec;
48 | return true;
49 | }
50 | else if (hit_right) {
51 | rec = right_rec;
52 | return true;
53 | }
54 | else
55 | return false;
56 | }
57 | else return false;
58 | }
59 |
60 |
61 | int box_x_compare (const void * a, const void * b) {
62 | aabb box_left, box_right;
63 | hitable *ah = *(hitable**)a;
64 | hitable *bh = *(hitable**)b;
65 | if(!ah->bounding_box(0,0, box_left) || !bh->bounding_box(0,0, box_right))
66 | std::cerr << "no bounding box in bvh_node constructor\n";
67 | if ( box_left.min().x() - box_right.min().x() < 0.0 )
68 | return -1;
69 | else
70 | return 1;
71 | }
72 |
73 | int box_y_compare (const void * a, const void * b)
74 | {
75 | aabb box_left, box_right;
76 | hitable *ah = *(hitable**)a;
77 | hitable *bh = *(hitable**)b;
78 | if(!ah->bounding_box(0,0, box_left) || !bh->bounding_box(0,0, box_right))
79 | std::cerr << "no bounding box in bvh_node constructor\n";
80 | if ( box_left.min().y() - box_right.min().y() < 0.0 )
81 | return -1;
82 | else
83 | return 1;
84 | }
85 | int box_z_compare (const void * a, const void * b)
86 | {
87 | aabb box_left, box_right;
88 | hitable *ah = *(hitable**)a;
89 | hitable *bh = *(hitable**)b;
90 | if(!ah->bounding_box(0,0, box_left) || !bh->bounding_box(0,0, box_right))
91 | std::cerr << "no bounding box in bvh_node constructor\n";
92 | if ( box_left.min().z() - box_right.min().z() < 0.0 )
93 | return -1;
94 | else
95 | return 1;
96 | }
97 |
98 |
99 | bvh_node::bvh_node(hitable **l, int n, float time0, float time1) {
100 | int axis = int(3*drand48());
101 | if (axis == 0)
102 | qsort(l, n, sizeof(hitable *), box_x_compare);
103 | else if (axis == 1)
104 | qsort(l, n, sizeof(hitable *), box_y_compare);
105 | else
106 | qsort(l, n, sizeof(hitable *), box_z_compare);
107 | if (n == 1) {
108 | left = right = l[0];
109 | }
110 | else if (n == 2) {
111 | left = l[0];
112 | right = l[1];
113 | }
114 | else {
115 | left = new bvh_node(l, n/2, time0, time1);
116 | right = new bvh_node(l + n/2, n - n/2, time0, time1);
117 | }
118 | aabb box_left, box_right;
119 | if(!left->bounding_box(time0,time1, box_left) || !right->bounding_box(time0,time1, box_right))
120 | std::cerr << "no bounding box in bvh_node constructor\n";
121 | box = surrounding_box(box_left, box_right);
122 | }
123 |
124 | #endif
125 |
126 |
--------------------------------------------------------------------------------
/src/camera.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef CAMERAH
13 | #define CAMERAH
14 | #include "ray.h"
15 |
16 | vec3 random_in_unit_disk() {
17 | vec3 p;
18 | do {
19 | p = 2.0*vec3(drand48(),drand48(),0) - vec3(1,1,0);
20 | } while (dot(p,p) >= 1.0);
21 | return p;
22 | }
23 |
24 | class camera {
25 | public:
26 | // new: add t0 and t1
27 | camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist, float t0, float t1) { // vfov is top to bottom in degrees
28 | time0 = t0;
29 | time1 = t1;
30 | lens_radius = aperture / 2;
31 | float theta = vfov*M_PI/180;
32 | float half_height = tan(theta/2);
33 | float half_width = aspect * half_height;
34 | origin = lookfrom;
35 | w = unit_vector(lookfrom - lookat);
36 | u = unit_vector(cross(vup, w));
37 | v = cross(w, u);
38 | lower_left_corner = origin - half_width*focus_dist*u -half_height*focus_dist*v - focus_dist*w;
39 | horizontal = 2*half_width*focus_dist*u;
40 | vertical = 2*half_height*focus_dist*v;
41 | }
42 |
43 | // new: add time to construct ray
44 | ray get_ray(float s, float t) {
45 | vec3 rd = lens_radius*random_in_unit_disk();
46 | vec3 offset = u * rd.x() + v * rd.y();
47 | float time = time0 + drand48()*(time1-time0);
48 | return ray(origin + offset, lower_left_corner + s*horizontal + t*vertical - origin - offset, time);
49 | }
50 |
51 | vec3 origin;
52 | vec3 lower_left_corner;
53 | vec3 horizontal;
54 | vec3 vertical;
55 | vec3 u, v, w;
56 | float time0, time1; // new variables for shutter open/close times
57 | float lens_radius;
58 | };
59 | #endif
60 |
61 |
--------------------------------------------------------------------------------
/src/constant_medium.h:
--------------------------------------------------------------------------------
1 | #ifndef CMEDH
2 | #define CMEDH
3 | //==================================================================================================
4 | // Written in 2016 by Peter Shirley
5 | //
6 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
7 | // neighboring rights to this software to the public domain worldwide. This software is distributed
8 | // without any warranty.
9 | //
10 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
11 | // with this software. If not, see .
12 | //==================================================================================================
13 |
14 | #include "hitable.h"
15 | #include
16 |
17 |
18 | class constant_medium : public hitable {
19 | public:
20 | constant_medium(hitable *b, float d, texture *a) : boundary(b), density(d) {
21 | phase_function = new isotropic(a);
22 | }
23 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
24 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
25 | return boundary->bounding_box(t0, t1, box);
26 | }
27 | hitable *boundary;
28 | float density;
29 | material *phase_function;
30 | };
31 |
32 |
33 | bool constant_medium::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
34 |
35 | // Print occasional samples when debugging. To enable, set enableDebug true.
36 | const bool enableDebug = false;
37 | bool debugging = enableDebug && drand48() < 0.00001;
38 |
39 | hit_record rec1, rec2;
40 |
41 | if (boundary->hit(r, -FLT_MAX, FLT_MAX, rec1)) {
42 | if (boundary->hit(r, rec1.t+0.0001, FLT_MAX, rec2)) {
43 |
44 | if (debugging) std::cerr << "\nt0 t1 " << rec1.t << " " << rec2.t << '\n';
45 |
46 | if (rec1.t < t_min) rec1.t = t_min;
47 | if (rec2.t > t_max) rec2.t = t_max;
48 |
49 | if (rec1.t >= rec2.t)
50 | return false;
51 | if (rec1.t < 0)
52 | rec1.t = 0;
53 |
54 | float distance_inside_boundary = (rec2.t - rec1.t) * r.direction().length();
55 | float hit_distance = -(1/density) * log(drand48());
56 |
57 | if (hit_distance < distance_inside_boundary) {
58 |
59 | rec.t = rec1.t + hit_distance / r.direction().length();
60 | rec.p = r.point_at_parameter(rec.t);
61 |
62 | if (debugging) {
63 | std::cerr << "hit_distance = " << hit_distance << '\n'
64 | << "rec.t = " << rec.t << '\n'
65 | << "rec.p = " << rec.p << '\n';
66 | }
67 |
68 | rec.normal = vec3(1,0,0); // arbitrary
69 | rec.mat_ptr = phase_function;
70 | return true;
71 | }
72 | }
73 | }
74 | return false;
75 | }
76 |
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/src/hitable.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef HITABLEH
13 | #define HITABLEH
14 |
15 | #include "aabb.h"
16 | #include
17 |
18 | class material;
19 |
20 | void get_sphere_uv(const vec3& p, float& u, float& v) {
21 | float phi = atan2(p.z(), p.x());
22 | float theta = asin(p.y());
23 | u = 1-(phi + M_PI) / (2*M_PI);
24 | v = (theta + M_PI/2) / M_PI;
25 | }
26 |
27 |
28 | struct hit_record
29 | {
30 | float t;
31 | float u;
32 | float v;
33 | vec3 p;
34 | vec3 normal;
35 | material *mat_ptr;
36 | };
37 |
38 | class hitable {
39 | public:
40 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const = 0;
41 | virtual bool bounding_box(float t0, float t1, aabb& box) const = 0;
42 | };
43 |
44 | class flip_normals : public hitable {
45 | public:
46 | flip_normals(hitable *p) : ptr(p) {}
47 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
48 | if (ptr->hit(r, t_min, t_max, rec)) {
49 | rec.normal = -rec.normal;
50 | return true;
51 | }
52 | else
53 | return false;
54 | }
55 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
56 | return ptr->bounding_box(t0, t1, box);
57 | }
58 | hitable *ptr;
59 | };
60 |
61 | class translate : public hitable {
62 | public:
63 | translate(hitable *p, const vec3& displacement) : ptr(p), offset(displacement) {}
64 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
65 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
66 | hitable *ptr;
67 | vec3 offset;
68 | };
69 |
70 | bool translate::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
71 | ray moved_r(r.origin() - offset, r.direction(), r.time());
72 | if (ptr->hit(moved_r, t_min, t_max, rec)) {
73 | rec.p += offset;
74 | return true;
75 | }
76 | else
77 | return false;
78 | }
79 |
80 | bool translate::bounding_box(float t0, float t1, aabb& box) const {
81 | if (ptr->bounding_box(t0, t1, box)) {
82 | box = aabb(box.min() + offset, box.max()+offset);
83 | return true;
84 | }
85 | else
86 | return false;
87 | }
88 |
89 | class rotate_y : public hitable {
90 | public:
91 | rotate_y(hitable *p, float angle);
92 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
93 | virtual bool bounding_box(float t0, float t1, aabb& box) const {
94 | box = bbox; return hasbox;}
95 | hitable *ptr;
96 | float sin_theta;
97 | float cos_theta;
98 | bool hasbox;
99 | aabb bbox;
100 | };
101 |
102 | rotate_y::rotate_y(hitable *p, float angle) : ptr(p) {
103 | float radians = (M_PI / 180.) * angle;
104 | sin_theta = sin(radians);
105 | cos_theta = cos(radians);
106 | hasbox = ptr->bounding_box(0, 1, bbox);
107 | vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
108 | vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
109 | for (int i = 0; i < 2; i++) {
110 | for (int j = 0; j < 2; j++) {
111 | for (int k = 0; k < 2; k++) {
112 | float x = i*bbox.max().x() + (1-i)*bbox.min().x();
113 | float y = j*bbox.max().y() + (1-j)*bbox.min().y();
114 | float z = k*bbox.max().z() + (1-k)*bbox.min().z();
115 | float newx = cos_theta*x + sin_theta*z;
116 | float newz = -sin_theta*x + cos_theta*z;
117 | vec3 tester(newx, y, newz);
118 | for ( int c = 0; c < 3; c++ )
119 | {
120 | if ( tester[c] > max[c] )
121 | max[c] = tester[c];
122 | if ( tester[c] < min[c] )
123 | min[c] = tester[c];
124 | }
125 | }
126 | }
127 | }
128 | bbox = aabb(min, max);
129 | }
130 |
131 | bool rotate_y::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
132 | vec3 origin = r.origin();
133 | vec3 direction = r.direction();
134 | origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
135 | origin[2] = sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
136 | direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
137 | direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
138 | ray rotated_r(origin, direction, r.time());
139 | if (ptr->hit(rotated_r, t_min, t_max, rec)) {
140 | vec3 p = rec.p;
141 | vec3 normal = rec.normal;
142 | p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];
143 | p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
144 | normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];
145 | normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
146 | rec.p = p;
147 | rec.normal = normal;
148 | return true;
149 | }
150 | else
151 | return false;
152 | }
153 |
154 | #endif
155 |
156 |
--------------------------------------------------------------------------------
/src/hitable_list.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef HITABLELISTH
13 | #define HITABLELISTH
14 |
15 | #include "hitable.h"
16 |
17 | class hitable_list: public hitable {
18 | public:
19 | hitable_list() {}
20 | hitable_list(hitable **l, int n) {list = l; list_size = n; }
21 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
22 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
23 | hitable **list;
24 | int list_size;
25 | };
26 |
27 | bool hitable_list::bounding_box(float t0, float t1, aabb& box) const {
28 | if (list_size < 1) return false;
29 | aabb temp_box;
30 | bool first_true = list[0]->bounding_box(t0, t1, temp_box);
31 | if (!first_true)
32 | return false;
33 | else
34 | box = temp_box;
35 | for (int i = 1; i < list_size; i++) {
36 | if(list[i]->bounding_box(t0, t1, temp_box)) {
37 | box = surrounding_box(box, temp_box);
38 | }
39 | else
40 | return false;
41 | }
42 | return true;
43 | }
44 |
45 | bool hitable_list::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
46 | hit_record temp_rec;
47 | bool hit_anything = false;
48 | double closest_so_far = t_max;
49 | for (int i = 0; i < list_size; i++) {
50 | if (list[i]->hit(r, t_min, closest_so_far, temp_rec)) {
51 | hit_anything = true;
52 | closest_so_far = temp_rec.t;
53 | rec = temp_rec;
54 | }
55 | }
56 | return hit_anything;
57 | }
58 |
59 | #endif
60 |
61 |
--------------------------------------------------------------------------------
/src/main.cc:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #include
13 | #include "sphere.h"
14 | #include "moving_sphere.h"
15 | #include "hitable_list.h"
16 | #include "float.h"
17 | #include "camera.h"
18 | #include "material.h"
19 | #include "bvh.h"
20 | #include "box.h"
21 | #include "surface_texture.h"
22 | #include "aarect.h"
23 | #include "constant_medium.h"
24 | #include "texture.h"
25 | #define STB_IMAGE_IMPLEMENTATION
26 | #include "stb_image.h"
27 |
28 |
29 | vec3 color(const ray& r, hitable *world, int depth) {
30 | hit_record rec;
31 | if (world->hit(r, 0.001, MAXFLOAT, rec)) {
32 | ray scattered;
33 | vec3 attenuation;
34 | vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
35 | if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
36 | return emitted + attenuation*color(scattered, world, depth+1);
37 | else
38 | return emitted;
39 | }
40 | else
41 | return vec3(0,0,0);
42 | }
43 |
44 | hitable *earth() {
45 | int nx, ny, nn;
46 | //unsigned char *tex_data = stbi_load("tiled.jpg", &nx, &ny, &nn, 0);
47 | unsigned char *tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
48 | material *mat = new lambertian(new image_texture(tex_data, nx, ny));
49 | return new sphere(vec3(0,0, 0), 2, mat);
50 | }
51 |
52 | hitable *two_spheres() {
53 | texture *checker = new checker_texture( new constant_texture(vec3(0.2,0.3, 0.1)), new constant_texture(vec3(0.9, 0.9, 0.9)));
54 | int n = 50;
55 | hitable **list = new hitable*[n+1];
56 | list[0] = new sphere(vec3(0,-10, 0), 10, new lambertian( checker));
57 | list[1] = new sphere(vec3(0, 10, 0), 10, new lambertian( checker));
58 |
59 | return new hitable_list(list,2);
60 | }
61 |
62 | hitable *final() {
63 | int nb = 20;
64 | hitable **list = new hitable*[30];
65 | hitable **boxlist = new hitable*[10000];
66 | hitable **boxlist2 = new hitable*[10000];
67 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)) );
68 | material *ground = new lambertian( new constant_texture(vec3(0.48, 0.83, 0.53)) );
69 | int b = 0;
70 | for (int i = 0; i < nb; i++) {
71 | for (int j = 0; j < nb; j++) {
72 | float w = 100;
73 | float x0 = -1000 + i*w;
74 | float z0 = -1000 + j*w;
75 | float y0 = 0;
76 | float x1 = x0 + w;
77 | float y1 = 100*(drand48()+0.01);
78 | float z1 = z0 + w;
79 | boxlist[b++] = new box(vec3(x0,y0,z0), vec3(x1,y1,z1), ground);
80 | }
81 | }
82 | int l = 0;
83 | list[l++] = new bvh_node(boxlist, b, 0, 1);
84 | material *light = new diffuse_light( new constant_texture(vec3(7, 7, 7)) );
85 | list[l++] = new xz_rect(123, 423, 147, 412, 554, light);
86 | vec3 center(400, 400, 200);
87 | list[l++] = new moving_sphere(center, center+vec3(30, 0, 0), 0, 1, 50, new lambertian(new constant_texture(vec3(0.7, 0.3, 0.1))));
88 | list[l++] = new sphere(vec3(260, 150, 45), 50, new dielectric(1.5));
89 | list[l++] = new sphere(vec3(0, 150, 145), 50, new metal(vec3(0.8, 0.8, 0.9), 10.0));
90 | hitable *boundary = new sphere(vec3(360, 150, 145), 70, new dielectric(1.5));
91 | list[l++] = boundary;
92 | list[l++] = new constant_medium(boundary, 0.2, new constant_texture(vec3(0.2, 0.4, 0.9)));
93 | boundary = new sphere(vec3(0, 0, 0), 5000, new dielectric(1.5));
94 | list[l++] = new constant_medium(boundary, 0.0001, new constant_texture(vec3(1.0, 1.0, 1.0)));
95 | int nx, ny, nn;
96 | unsigned char *tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
97 | material *emat = new lambertian(new image_texture(tex_data, nx, ny));
98 | list[l++] = new sphere(vec3(400,200, 400), 100, emat);
99 | texture *pertext = new noise_texture(0.1);
100 | list[l++] = new sphere(vec3(220,280, 300), 80, new lambertian( pertext ));
101 | int ns = 1000;
102 | for (int j = 0; j < ns; j++) {
103 | boxlist2[j] = new sphere(vec3(165*drand48(), 165*drand48(), 165*drand48()), 10, white);
104 | }
105 | list[l++] = new translate(new rotate_y(new bvh_node(boxlist2,ns, 0.0, 1.0), 15), vec3(-100,270,395));
106 | return new hitable_list(list,l);
107 | }
108 |
109 | hitable *cornell_final() {
110 | hitable **list = new hitable*[30];
111 | hitable **boxlist = new hitable*[10000];
112 | texture *pertext = new noise_texture(0.1);
113 | int nx, ny, nn;
114 | unsigned char *tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
115 | material *mat = new lambertian(new image_texture(tex_data, nx, ny));
116 | int i = 0;
117 | material *red = new lambertian( new constant_texture(vec3(0.65, 0.05, 0.05)) );
118 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)) );
119 | material *green = new lambertian( new constant_texture(vec3(0.12, 0.45, 0.15)) );
120 | material *light = new diffuse_light( new constant_texture(vec3(7, 7, 7)) );
121 | //list[i++] = new sphere(vec3(260, 50, 145), 50,mat);
122 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
123 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
124 | list[i++] = new xz_rect(123, 423, 147, 412, 554, light);
125 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
126 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
127 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
128 | /*
129 | hitable *boundary = new sphere(vec3(160, 50, 345), 50, new dielectric(1.5));
130 | list[i++] = boundary;
131 | list[i++] = new constant_medium(boundary, 0.2, new constant_texture(vec3(0.2, 0.4, 0.9)));
132 | list[i++] = new sphere(vec3(460, 50, 105), 50, new dielectric(1.5));
133 | list[i++] = new sphere(vec3(120, 50, 205), 50, new lambertian(pertext));
134 | int ns = 10000;
135 | for (int j = 0; j < ns; j++) {
136 | boxlist[j] = new sphere(vec3(165*drand48(), 330*drand48(), 165*drand48()), 10, white);
137 | }
138 | list[i++] = new translate(new rotate_y(new bvh_node(boxlist,ns, 0.0, 1.0), 15), vec3(265,0,295));
139 | */
140 | hitable *boundary2 = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 165, 165), new dielectric(1.5)), -18), vec3(130,0,65));
141 | list[i++] = boundary2;
142 | list[i++] = new constant_medium(boundary2, 0.2, new constant_texture(vec3(0.9, 0.9, 0.9)));
143 | return new hitable_list(list,i);
144 | }
145 |
146 | hitable *cornell_balls() {
147 | hitable **list = new hitable*[9];
148 | int i = 0;
149 | material *red = new lambertian( new constant_texture(vec3(0.65, 0.05, 0.05)) );
150 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)) );
151 | material *green = new lambertian( new constant_texture(vec3(0.12, 0.45, 0.15)) );
152 | material *light = new diffuse_light( new constant_texture(vec3(5, 5, 5)) );
153 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
154 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
155 | list[i++] = new xz_rect(113, 443, 127, 432, 554, light);
156 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
157 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
158 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
159 | hitable *boundary = new sphere(vec3(160, 100, 145), 100, new dielectric(1.5));
160 | list[i++] = boundary;
161 | list[i++] = new constant_medium(boundary, 0.1, new constant_texture(vec3(1.0, 1.0, 1.0)));
162 | list[i++] = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 330, 165), white), 15), vec3(265,0,295));
163 | return new hitable_list(list,i);
164 | }
165 |
166 | hitable *cornell_smoke() {
167 | hitable **list = new hitable*[8];
168 | int i = 0;
169 | material *red = new lambertian( new constant_texture(vec3(0.65, 0.05, 0.05)) );
170 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)) );
171 | material *green = new lambertian( new constant_texture(vec3(0.12, 0.45, 0.15)) );
172 | material *light = new diffuse_light( new constant_texture(vec3(7, 7, 7)) );
173 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
174 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
175 | list[i++] = new xz_rect(113, 443, 127, 432, 554, light);
176 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
177 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
178 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
179 | hitable *b1 = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 165, 165), white), -18), vec3(130,0,65));
180 | hitable *b2 = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 330, 165), white), 15), vec3(265,0,295));
181 | list[i++] = new constant_medium(b1, 0.01, new constant_texture(vec3(1.0, 1.0, 1.0)));
182 | list[i++] = new constant_medium(b2, 0.01, new constant_texture(vec3(0.0, 0.0, 0.0)));
183 | return new hitable_list(list,i);
184 | }
185 |
186 | hitable *cornell_box() {
187 | hitable **list = new hitable*[8];
188 | int i = 0;
189 | material *red = new lambertian( new constant_texture(vec3(0.65, 0.05, 0.05)) );
190 | material *white = new lambertian( new constant_texture(vec3(0.73, 0.73, 0.73)) );
191 | material *green = new lambertian( new constant_texture(vec3(0.12, 0.45, 0.15)) );
192 | material *light = new diffuse_light( new constant_texture(vec3(15, 15, 15)) );
193 | list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
194 | list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
195 | list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
196 | list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
197 | list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
198 | list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));
199 | list[i++] = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 165, 165), white), -18), vec3(130,0,65));
200 | list[i++] = new translate(new rotate_y(new box(vec3(0, 0, 0), vec3(165, 330, 165), white), 15), vec3(265,0,295));
201 | return new hitable_list(list,i);
202 | }
203 |
204 | hitable *two_perlin_spheres() {
205 | texture *pertext = new noise_texture(4);
206 | hitable **list = new hitable*[2];
207 | list[0] = new sphere(vec3(0,-1000, 0), 1000, new lambertian( pertext ));
208 | list[1] = new sphere(vec3(0, 2, 0), 2, new lambertian( pertext ));
209 | return new hitable_list(list,2);
210 | }
211 |
212 | hitable *simple_light() {
213 | texture *pertext = new noise_texture(4);
214 | hitable **list = new hitable*[4];
215 | list[0] = new sphere(vec3(0,-1000, 0), 1000, new lambertian( pertext ));
216 | list[1] = new sphere(vec3(0, 2, 0), 2, new lambertian( pertext ));
217 | list[2] = new sphere(vec3(0, 7, 0), 2, new diffuse_light( new constant_texture(vec3(4,4,4))));
218 | list[3] = new xy_rect(3, 5, 1, 3, -2, new diffuse_light(new constant_texture(vec3(4,4,4))));
219 | return new hitable_list(list,4);
220 | }
221 |
222 | hitable *random_scene() {
223 | int n = 50000;
224 | hitable **list = new hitable*[n+1];
225 | texture *checker = new checker_texture( new constant_texture(vec3(0.2,0.3, 0.1)), new constant_texture(vec3(0.9, 0.9, 0.9)));
226 | list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian( checker));
227 | int i = 1;
228 | for (int a = -10; a < 10; a++) {
229 | for (int b = -10; b < 10; b++) {
230 | float choose_mat = drand48();
231 | vec3 center(a+0.9*drand48(),0.2,b+0.9*drand48());
232 | if ((center-vec3(4,0.2,0)).length() > 0.9) {
233 | if (choose_mat < 0.8) { // diffuse
234 | list[i++] = new moving_sphere(center, center+vec3(0,0.5*drand48(), 0), 0.0, 1.0, 0.2, new lambertian(new constant_texture(vec3(drand48()*drand48(), drand48()*drand48(), drand48()*drand48()))));
235 | }
236 | else if (choose_mat < 0.95) { // metal
237 | list[i++] = new sphere(center, 0.2,
238 | new metal(vec3(0.5*(1 + drand48()), 0.5*(1 + drand48()), 0.5*(1 + drand48())), 0.5*drand48()));
239 | }
240 | else { // glass
241 | list[i++] = new sphere(center, 0.2, new dielectric(1.5));
242 | }
243 | }
244 | }
245 | }
246 |
247 | list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
248 | list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(new constant_texture(vec3(0.4, 0.2, 0.1))));
249 | list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));
250 |
251 | //return new hitable_list(list,i);
252 | return new bvh_node(list,i, 0.0, 1.0);
253 | }
254 |
255 | int main() {
256 | int nx = 800;
257 | int ny = 800;
258 | int ns = 100;
259 | std::cout << "P3\n" << nx << " " << ny << "\n255\n";
260 | hitable *list[5];
261 | float R = cos(M_PI/4);
262 | //hitable *world = random_scene();
263 | //hitable *world = two_spheres();
264 | //hitable *world = two_perlin_spheres();
265 | //hitable *world = earth();
266 | //hitable *world = simple_light();
267 | hitable *world = cornell_box();
268 | //hitable *world = cornell_balls();
269 | //hitable *world = cornell_smoke();
270 | //hitable *world = cornell_final();
271 | //hitable *world = final();
272 |
273 | vec3 lookfrom(278, 278, -800);
274 | //vec3 lookfrom(478, 278, -600);
275 | vec3 lookat(278,278,0);
276 | //vec3 lookfrom(0, 0, 6);
277 | //vec3 lookat(0,0,0);
278 | float dist_to_focus = 10.0;
279 | float aperture = 0.0;
280 | float vfov = 40.0;
281 |
282 | camera cam(lookfrom, lookat, vec3(0,1,0), vfov, float(nx)/float(ny), aperture, dist_to_focus, 0.0, 1.0);
283 |
284 | for (int j = ny-1; j >= 0; j--) {
285 | for (int i = 0; i < nx; i++) {
286 | vec3 col(0, 0, 0);
287 | for (int s=0; s < ns; s++) {
288 | float u = float(i+drand48())/ float(nx);
289 | float v = float(j+drand48())/ float(ny);
290 | ray r = cam.get_ray(u, v);
291 | vec3 p = r.point_at_parameter(2.0);
292 | col += color(r, world,0);
293 | }
294 | col /= float(ns);
295 | col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) );
296 | int ir = int(255.99*col[0]);
297 | int ig = int(255.99*col[1]);
298 | int ib = int(255.99*col[2]);
299 | std::cout << ir << " " << ig << " " << ib << "\n";
300 | }
301 | }
302 | }
303 |
304 |
--------------------------------------------------------------------------------
/src/material.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef MATERIALH
13 | #define MATERIALH
14 |
15 | struct hit_record;
16 |
17 | #include "ray.h"
18 | #include "hitable.h"
19 | #include "texture.h"
20 |
21 |
22 | float schlick(float cosine, float ref_idx) {
23 | float r0 = (1-ref_idx) / (1+ref_idx);
24 | r0 = r0*r0;
25 | return r0 + (1-r0)*pow((1 - cosine),5);
26 | }
27 |
28 | bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) {
29 | vec3 uv = unit_vector(v);
30 | float dt = dot(uv, n);
31 | float discriminant = 1.0 - ni_over_nt*ni_over_nt*(1-dt*dt);
32 | if (discriminant > 0) {
33 | refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminant);
34 | return true;
35 | }
36 | else
37 | return false;
38 | }
39 |
40 |
41 | vec3 reflect(const vec3& v, const vec3& n) {
42 | return v - 2*dot(v,n)*n;
43 | }
44 |
45 |
46 | vec3 random_in_unit_sphere() {
47 | vec3 p;
48 | do {
49 | p = 2.0*vec3(drand48(),drand48(),drand48()) - vec3(1,1,1);
50 | } while (dot(p,p) >= 1.0);
51 | return p;
52 | }
53 |
54 |
55 | class material {
56 | public:
57 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
58 | virtual vec3 emitted(float u, float v, const vec3& p) const {
59 | return vec3(0,0,0); }
60 | };
61 |
62 | class diffuse_light : public material {
63 | public:
64 | diffuse_light(texture *a) : emit(a) {}
65 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const { return false; }
66 | virtual vec3 emitted(float u, float v, const vec3& p) const { return emit->value(u, v, p); }
67 | texture *emit;
68 | };
69 |
70 |
71 | class isotropic : public material {
72 | public:
73 | isotropic(texture *a) : albedo(a) {}
74 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
75 | scattered = ray(rec.p, random_in_unit_sphere(), r_in.time());
76 | attenuation = albedo->value(rec.u, rec.v, rec.p);
77 | return true;
78 | }
79 | texture *albedo;
80 | };
81 |
82 | class lambertian : public material {
83 | public:
84 | lambertian(texture *a) : albedo(a) {}
85 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
86 | vec3 target = rec.p + rec.normal + random_in_unit_sphere();
87 | scattered = ray(rec.p, target-rec.p, r_in.time());
88 | attenuation = albedo->value(rec.u, rec.v, rec.p);
89 | return true;
90 | }
91 |
92 | texture *albedo;
93 | };
94 |
95 | class metal : public material {
96 | public:
97 | metal(const vec3& a, float f) : albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; }
98 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
99 | vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
100 | scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere(), r_in.time());
101 | attenuation = albedo;
102 | return (dot(scattered.direction(), rec.normal) > 0);
103 | }
104 | vec3 albedo;
105 | float fuzz;
106 | };
107 |
108 | class dielectric : public material {
109 | public:
110 | dielectric(float ri) : ref_idx(ri) {}
111 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
112 | vec3 outward_normal;
113 | vec3 reflected = reflect(r_in.direction(), rec.normal);
114 | float ni_over_nt;
115 | attenuation = vec3(1.0, 1.0, 1.0);
116 | vec3 refracted;
117 | float reflect_prob;
118 | float cosine;
119 | if (dot(r_in.direction(), rec.normal) > 0) {
120 | outward_normal = -rec.normal;
121 | ni_over_nt = ref_idx;
122 | cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();
123 | }
124 | else {
125 | outward_normal = rec.normal;
126 | ni_over_nt = 1.0 / ref_idx;
127 | cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();
128 | }
129 | if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted)) {
130 | reflect_prob = schlick(cosine, ref_idx);
131 | }
132 | else {
133 | scattered = ray(rec.p, reflected, r_in.time());
134 | reflect_prob = 1.0;
135 | }
136 | if (drand48() < reflect_prob) {
137 | scattered = ray(rec.p, reflected, r_in.time());
138 | }
139 | else {
140 | scattered = ray(rec.p, refracted, r_in.time());
141 | }
142 | return true;
143 | }
144 |
145 | float ref_idx;
146 | };
147 |
148 | #endif
149 |
150 |
--------------------------------------------------------------------------------
/src/moving_sphere.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef MOVINGSPHEREH
13 | #define MOVINGSPHEREH
14 |
15 | #include "hitable.h"
16 |
17 | class moving_sphere: public hitable {
18 | public:
19 | moving_sphere() {}
20 | moving_sphere(vec3 cen0, vec3 cen1, float t0, float t1, float r, material *m) : center0(cen0), center1(cen1), time0(t0),time1(t1), radius(r), mat_ptr(m) {};
21 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
22 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
23 | vec3 center(float time) const;
24 | vec3 center0, center1;
25 | float time0, time1;
26 | float radius;
27 | material *mat_ptr;
28 | };
29 |
30 | vec3 moving_sphere::center(float time) const{
31 | return center0 + ((time - time0) / (time1 - time0))*(center1 - center0);
32 | }
33 |
34 |
35 | bool moving_sphere::bounding_box(float t0, float t1, aabb& box) const {
36 | aabb box0(center(t0) - vec3(radius, radius, radius), center(t0) + vec3(radius, radius, radius));
37 | aabb box1(center(t1) - vec3(radius, radius, radius), center(t1) + vec3(radius, radius, radius));
38 | box = surrounding_box(box0, box1);
39 | return true;
40 | }
41 |
42 |
43 | // replace "center" with "center(r.time())"
44 | bool moving_sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
45 | vec3 oc = r.origin() - center(r.time());
46 | float a = dot(r.direction(), r.direction());
47 | float b = dot(oc, r.direction());
48 | float c = dot(oc, oc) - radius*radius;
49 | float discriminant = b*b - a*c;
50 | if (discriminant > 0) {
51 | float temp = (-b - sqrt(discriminant))/a;
52 | if (temp < t_max && temp > t_min) {
53 | rec.t = temp;
54 | rec.p = r.point_at_parameter(rec.t);
55 | rec.normal = (rec.p - center(r.time())) / radius;
56 | rec.mat_ptr = mat_ptr;
57 | return true;
58 | }
59 | temp = (-b + sqrt(discriminant))/a;
60 | if (temp < t_max && temp > t_min) {
61 | rec.t = temp;
62 | rec.p = r.point_at_parameter(rec.t);
63 | rec.normal = (rec.p - center(r.time())) / radius;
64 | rec.mat_ptr = mat_ptr;
65 | return true;
66 | }
67 | }
68 | return false;
69 | }
70 |
71 |
72 | #endif
73 |
74 |
--------------------------------------------------------------------------------
/src/perlin.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef PERLINH
13 | #define PERLINH
14 |
15 | #include "vec3.h"
16 |
17 |
18 | inline float perlin_interp(vec3 c[2][2][2], float u, float v, float w) {
19 | float uu = u*u*(3-2*u);
20 | float vv = v*v*(3-2*v);
21 | float ww = w*w*(3-2*w);
22 | float accum = 0;
23 | for (int i=0; i < 2; i++)
24 | for (int j=0; j < 2; j++)
25 | for (int k=0; k < 2; k++) {
26 | vec3 weight_v(u-i, v-j, w-k);
27 | accum += (i*uu + (1-i)*(1-uu))*
28 | (j*vv + (1-j)*(1-vv))*
29 | (k*ww + (1-k)*(1-ww))*dot(c[i][j][k], weight_v);
30 | }
31 | return accum;
32 | }
33 |
34 | class perlin {
35 | public:
36 | float noise(const vec3& p) const {
37 | float u = p.x() - floor(p.x());
38 | float v = p.y() - floor(p.y());
39 | float w = p.z() - floor(p.z());
40 | int i = floor(p.x());
41 | int j = floor(p.y());
42 | int k = floor(p.z());
43 | vec3 c[2][2][2];
44 | for (int di=0; di < 2; di++)
45 | for (int dj=0; dj < 2; dj++)
46 | for (int dk=0; dk < 2; dk++)
47 | c[di][dj][dk] = ranvec[perm_x[(i+di) & 255] ^ perm_y[(j+dj) & 255] ^ perm_z[(k+dk) & 255]];
48 | return perlin_interp(c, u, v, w);
49 | }
50 | float turb(const vec3& p, int depth=7) const {
51 | float accum = 0;
52 | vec3 temp_p = p;
53 | float weight = 1.0;
54 | for (int i = 0; i < depth; i++) {
55 | accum += weight*noise(temp_p);
56 | weight *= 0.5;
57 | temp_p *= 2;
58 | }
59 | return fabs(accum);
60 | }
61 | static vec3 *ranvec;
62 | static int *perm_x;
63 | static int *perm_y;
64 | static int *perm_z;
65 | };
66 |
67 | static vec3* perlin_generate() {
68 | vec3 * p = new vec3[256];
69 | for ( int i = 0; i < 256; ++i )
70 | p[i] = unit_vector(vec3(-1 + 2*drand48(), -1 + 2*drand48(), -1 + 2*drand48()));
71 | return p;
72 | }
73 |
74 | void permute(int *p, int n) {
75 | for (int i = n-1; i > 0; i--) {
76 | int target = int(drand48()*(i+1));
77 | int tmp = p[i];
78 | p[i] = p[target];
79 | p[target] = tmp;
80 | }
81 | return;
82 | }
83 |
84 | static int* perlin_generate_perm() {
85 | int * p = new int[256];
86 | for (int i = 0; i < 256; i++)
87 | p[i] = i;
88 | permute(p, 256);
89 | return p;
90 | }
91 |
92 | vec3 *perlin::ranvec = perlin_generate();
93 | int *perlin::perm_x = perlin_generate_perm();
94 | int *perlin::perm_y = perlin_generate_perm();
95 | int *perlin::perm_z = perlin_generate_perm();
96 |
97 |
98 | #endif
99 |
100 |
--------------------------------------------------------------------------------
/src/ray.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef RAYH
13 | #define RAYH
14 |
15 | #include "vec3.h"
16 |
17 | class ray
18 | {
19 | public:
20 | ray() {}
21 | ray(const vec3& a, const vec3& b, float ti = 0.0) { A = a; B = b; _time = ti;}
22 | vec3 origin() const { return A; }
23 | vec3 direction() const { return B; }
24 | float time() const { return _time; }
25 | vec3 point_at_parameter(float t) const { return A + t*B; }
26 |
27 | vec3 A;
28 | vec3 B;
29 | float _time;
30 | };
31 |
32 | #endif
33 |
34 |
--------------------------------------------------------------------------------
/src/sphere.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef SPHEREH
13 | #define SPHEREH
14 |
15 | #include "hitable.h"
16 |
17 | class sphere: public hitable {
18 | public:
19 | sphere() {}
20 | sphere(vec3 cen, float r, material *m) : center(cen), radius(r), mat_ptr(m) {};
21 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
22 | virtual bool bounding_box(float t0, float t1, aabb& box) const;
23 | vec3 center;
24 | float radius;
25 | material *mat_ptr;
26 | };
27 |
28 |
29 | bool sphere::bounding_box(float t0, float t1, aabb& box) const {
30 | box = aabb(center - vec3(radius, radius, radius), center + vec3(radius, radius, radius));
31 | return true;
32 | }
33 |
34 | bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
35 | vec3 oc = r.origin() - center;
36 | float a = dot(r.direction(), r.direction());
37 | float b = dot(oc, r.direction());
38 | float c = dot(oc, oc) - radius*radius;
39 | float discriminant = b*b - a*c;
40 | if (discriminant > 0) {
41 | float temp = (-b - sqrt(b*b-a*c))/a;
42 | if (temp < t_max && temp > t_min) {
43 | rec.t = temp;
44 | rec.p = r.point_at_parameter(rec.t);
45 | get_sphere_uv((rec.p-center)/radius, rec.u, rec.v);
46 | rec.normal = (rec.p - center) / radius;
47 | rec.mat_ptr = mat_ptr;
48 | return true;
49 | }
50 | temp = (-b + sqrt(b*b-a*c))/a;
51 | if (temp < t_max && temp > t_min) {
52 | rec.t = temp;
53 | rec.p = r.point_at_parameter(rec.t);
54 | get_sphere_uv((rec.p-center)/radius, rec.u, rec.v);
55 | rec.normal = (rec.p - center) / radius;
56 | rec.mat_ptr = mat_ptr;
57 | return true;
58 | }
59 | }
60 | return false;
61 | }
62 |
63 |
64 | #endif
65 |
66 |
--------------------------------------------------------------------------------
/src/stb_image_write.h:
--------------------------------------------------------------------------------
1 | /* stb_image_write - v0.98 - public domain - http://nothings.org/stb/stb_image_write.h
2 | writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010
3 | no warranty implied; use at your own risk
4 |
5 |
6 | Before #including,
7 |
8 | #define STB_IMAGE_WRITE_IMPLEMENTATION
9 |
10 | in the file that you want to have the implementation.
11 |
12 | Will probably not work correctly with strict-aliasing optimizations.
13 |
14 | ABOUT:
15 |
16 | This header file is a library for writing images to C stdio. It could be
17 | adapted to write to memory or a general streaming interface; let me know.
18 |
19 | The PNG output is not optimal; it is 20-50% larger than the file
20 | written by a decent optimizing implementation. This library is designed
21 | for source code compactness and simplicitly, not optimal image file size
22 | or run-time performance.
23 |
24 | BUILDING:
25 |
26 | You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h.
27 | You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace
28 | malloc,realloc,free.
29 | You can define STBIW_MEMMOVE() to replace memmove()
30 |
31 | USAGE:
32 |
33 | There are four functions, one for each image file format:
34 |
35 | int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
36 | int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
37 | int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
38 | int stbi_write_hdr(char const *filename, int w, int h, int comp, const void *data);
39 |
40 | Each function returns 0 on failure and non-0 on success.
41 |
42 | The functions create an image file defined by the parameters. The image
43 | is a rectangle of pixels stored from left-to-right, top-to-bottom.
44 | Each pixel contains 'comp' channels of data stored interleaved with 8-bits
45 | per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
46 | monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
47 | The *data pointer points to the first byte of the top-left-most pixel.
48 | For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
49 | a row of pixels to the first byte of the next row of pixels.
50 |
51 | PNG creates output files with the same number of components as the input.
52 | The BMP format expands Y to RGB in the file format and does not
53 | output alpha.
54 |
55 | PNG supports writing rectangles of data even when the bytes storing rows of
56 | data are not consecutive in memory (e.g. sub-rectangles of a larger image),
57 | by supplying the stride between the beginning of adjacent rows. The other
58 | formats do not. (Thus you cannot write a native-format BMP through the BMP
59 | writer, both because it is in BGR order and because it may have padding
60 | at the end of the line.)
61 |
62 | HDR expects linear float data. Since the format is always 32-bit rgb(e)
63 | data, alpha (if provided) is discarded, and for monochrome data it is
64 | replicated across all three channels.
65 |
66 | CREDITS:
67 |
68 | PNG/BMP/TGA
69 | Sean Barrett
70 | HDR
71 | Baldur Karlsson
72 | TGA monochrome:
73 | Jean-Sebastien Guay
74 | misc enhancements:
75 | Tim Kelsey
76 | bugfixes:
77 | github:Chribba
78 | */
79 |
80 | #ifndef INCLUDE_STB_IMAGE_WRITE_H
81 | #define INCLUDE_STB_IMAGE_WRITE_H
82 |
83 | #ifdef __cplusplus
84 | extern "C" {
85 | #endif
86 |
87 | extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
88 | extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
89 | extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
90 | extern int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
91 |
92 | #ifdef __cplusplus
93 | }
94 | #endif
95 |
96 | #endif//INCLUDE_STB_IMAGE_WRITE_H
97 |
98 | #ifdef STB_IMAGE_WRITE_IMPLEMENTATION
99 |
100 | #include
101 | #include
102 | #include
103 | #include
104 | #include
105 |
106 | #if defined(STBIW_MALLOC) && defined(STBIW_FREE) && defined(STBIW_REALLOC)
107 | // ok
108 | #elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC)
109 | // ok
110 | #else
111 | #error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC."
112 | #endif
113 |
114 | #ifndef STBIW_MALLOC
115 | #define STBIW_MALLOC(sz) malloc(sz)
116 | #define STBIW_REALLOC(p,sz) realloc(p,sz)
117 | #define STBIW_FREE(p) free(p)
118 | #endif
119 | #ifndef STBIW_MEMMOVE
120 | #define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
121 | #endif
122 |
123 |
124 | #ifndef STBIW_ASSERT
125 | #include
126 | #define STBIW_ASSERT(x) assert(x)
127 | #endif
128 |
129 | typedef unsigned int stbiw_uint32;
130 | typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
131 |
132 | static void writefv(FILE *f, const char *fmt, va_list v)
133 | {
134 | while (*fmt) {
135 | switch (*fmt++) {
136 | case ' ': break;
137 | case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; }
138 | case '2': { int x = va_arg(v,int); unsigned char b[2];
139 | b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8);
140 | fwrite(b,2,1,f); break; }
141 | case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4];
142 | b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8);
143 | b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24);
144 | fwrite(b,4,1,f); break; }
145 | default:
146 | STBIW_ASSERT(0);
147 | return;
148 | }
149 | }
150 | }
151 |
152 | static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c)
153 | {
154 | unsigned char arr[3];
155 | arr[0] = a, arr[1] = b, arr[2] = c;
156 | fwrite(arr, 3, 1, f);
157 | }
158 |
159 | static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
160 | {
161 | unsigned char bg[3] = { 255, 0, 255}, px[3];
162 | stbiw_uint32 zero = 0;
163 | int i,j,k, j_end;
164 |
165 | if (y <= 0)
166 | return;
167 |
168 | if (vdir < 0)
169 | j_end = -1, j = y-1;
170 | else
171 | j_end = y, j = 0;
172 |
173 | for (; j != j_end; j += vdir) {
174 | for (i=0; i < x; ++i) {
175 | unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
176 | if (write_alpha < 0)
177 | fwrite(&d[comp-1], 1, 1, f);
178 | switch (comp) {
179 | case 1: fwrite(d, 1, 1, f);
180 | break;
181 | case 2: if (expand_mono)
182 | write3(f, d[0],d[0],d[0]); // monochrome bmp
183 | else
184 | fwrite(d, 1, 1, f); // monochrome TGA
185 | break;
186 | case 4:
187 | if (!write_alpha) {
188 | // composite against pink background
189 | for (k=0; k < 3; ++k)
190 | px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255;
191 | write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]);
192 | break;
193 | }
194 | /* FALLTHROUGH */
195 | case 3:
196 | write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]);
197 | break;
198 | }
199 | if (write_alpha > 0)
200 | fwrite(&d[comp-1], 1, 1, f);
201 | }
202 | fwrite(&zero,scanline_pad,1,f);
203 | }
204 | }
205 |
206 | static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...)
207 | {
208 | FILE *f;
209 | if (y < 0 || x < 0) return 0;
210 | f = fopen(filename, "wb");
211 | if (f) {
212 | va_list v;
213 | va_start(v, fmt);
214 | writefv(f, fmt, v);
215 | va_end(v);
216 | write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad,expand_mono);
217 | fclose(f);
218 | }
219 | return f != NULL;
220 | }
221 |
222 | int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
223 | {
224 | int pad = (-x*3) & 3;
225 | return outfile(filename,-1,-1,x,y,comp,1,(void *) data,0,pad,
226 | "11 4 22 4" "4 44 22 444444",
227 | 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
228 | 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
229 | }
230 |
231 | int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
232 | {
233 | int has_alpha = (comp == 2 || comp == 4);
234 | int colorbytes = has_alpha ? comp-1 : comp;
235 | int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3
236 | return outfile(filename, -1,-1, x, y, comp, 0, (void *) data, has_alpha, 0,
237 | "111 221 2222 11", 0,0,format, 0,0,0, 0,0,x,y, (colorbytes+has_alpha)*8, has_alpha*8);
238 | }
239 |
240 | // *************************************************************************************************
241 | // Radiance RGBE HDR writer
242 | // by Baldur Karlsson
243 | #define stbiw__max(a, b) ((a) > (b) ? (a) : (b))
244 |
245 | void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
246 | {
247 | int exponent;
248 | float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2]));
249 |
250 | if (maxcomp < 1e-32) {
251 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
252 | } else {
253 | float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp;
254 |
255 | rgbe[0] = (unsigned char)(linear[0] * normalize);
256 | rgbe[1] = (unsigned char)(linear[1] * normalize);
257 | rgbe[2] = (unsigned char)(linear[2] * normalize);
258 | rgbe[3] = (unsigned char)(exponent + 128);
259 | }
260 | }
261 |
262 | void stbiw__write_run_data(FILE *f, int length, unsigned char databyte)
263 | {
264 | unsigned char lengthbyte = (unsigned char) (length+128);
265 | STBIW_ASSERT(length+128 <= 255);
266 | fwrite(&lengthbyte, 1, 1, f);
267 | fwrite(&databyte, 1, 1, f);
268 | }
269 |
270 | void stbiw__write_dump_data(FILE *f, int length, unsigned char *data)
271 | {
272 | unsigned char lengthbyte = (unsigned char )(length & 0xff);
273 | STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
274 | fwrite(&lengthbyte, 1, 1, f);
275 | fwrite(data, length, 1, f);
276 | }
277 |
278 | void stbiw__write_hdr_scanline(FILE *f, int width, int comp, unsigned char *scratch, const float *scanline)
279 | {
280 | unsigned char scanlineheader[4] = { 2, 2, 0, 0 };
281 | unsigned char rgbe[4];
282 | float linear[3];
283 | int x;
284 |
285 | scanlineheader[2] = (width&0xff00)>>8;
286 | scanlineheader[3] = (width&0x00ff);
287 |
288 | /* skip RLE for images too small or large */
289 | if (width < 8 || width >= 32768) {
290 | for (x=0; x < width; x++) {
291 | switch (comp) {
292 | case 4: /* fallthrough */
293 | case 3: linear[2] = scanline[x*comp + 2];
294 | linear[1] = scanline[x*comp + 1];
295 | linear[0] = scanline[x*comp + 0];
296 | break;
297 | case 2: /* fallthrough */
298 | case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0];
299 | break;
300 | }
301 | stbiw__linear_to_rgbe(rgbe, linear);
302 | fwrite(rgbe, 4, 1, f);
303 | }
304 | } else {
305 | int c,r;
306 | /* encode into scratch buffer */
307 | for (x=0; x < width; x++) {
308 | switch(comp) {
309 | case 4: /* fallthrough */
310 | case 3: linear[2] = scanline[x*comp + 2];
311 | linear[1] = scanline[x*comp + 1];
312 | linear[0] = scanline[x*comp + 0];
313 | break;
314 | case 2: /* fallthrough */
315 | case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0];
316 | break;
317 | }
318 | stbiw__linear_to_rgbe(rgbe, linear);
319 | scratch[x + width*0] = rgbe[0];
320 | scratch[x + width*1] = rgbe[1];
321 | scratch[x + width*2] = rgbe[2];
322 | scratch[x + width*3] = rgbe[3];
323 | }
324 |
325 | fwrite(scanlineheader, 4, 1, f);
326 |
327 | /* RLE each component separately */
328 | for (c=0; c < 4; c++) {
329 | unsigned char *comp = &scratch[width*c];
330 |
331 | x = 0;
332 | while (x < width) {
333 | // find first run
334 | r = x;
335 | while (r+2 < width) {
336 | if (comp[r] == comp[r+1] && comp[r] == comp[r+2])
337 | break;
338 | ++r;
339 | }
340 | if (r+2 >= width)
341 | r = width;
342 | // dump up to first run
343 | while (x < r) {
344 | int len = r-x;
345 | if (len > 128) len = 128;
346 | stbiw__write_dump_data(f, len, &comp[x]);
347 | x += len;
348 | }
349 | // if there's a run, output it
350 | if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd
351 | // find next byte after run
352 | while (r < width && comp[r] == comp[x])
353 | ++r;
354 | // output run up to r
355 | while (x < r) {
356 | int len = r-x;
357 | if (len > 127) len = 127;
358 | stbiw__write_run_data(f, len, comp[x]);
359 | x += len;
360 | }
361 | }
362 | }
363 | }
364 | }
365 | }
366 |
367 | int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
368 | {
369 | int i;
370 | FILE *f;
371 | if (y <= 0 || x <= 0 || data == NULL) return 0;
372 | f = fopen(filename, "wb");
373 | if (f) {
374 | /* Each component is stored separately. Allocate scratch space for full output scanline. */
375 | unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4);
376 | fprintf(f, "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n" );
377 | fprintf(f, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n" , y, x);
378 | for(i=0; i < y; i++)
379 | stbiw__write_hdr_scanline(f, x, comp, scratch, data + comp*i*x);
380 | STBIW_FREE(scratch);
381 | fclose(f);
382 | }
383 | return f != NULL;
384 | }
385 |
386 | /////////////////////////////////////////////////////////
387 | // PNG
388 |
389 | // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
390 | #define stbiw__sbraw(a) ((int *) (a) - 2)
391 | #define stbiw__sbm(a) stbiw__sbraw(a)[0]
392 | #define stbiw__sbn(a) stbiw__sbraw(a)[1]
393 |
394 | #define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a))
395 | #define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0)
396 | #define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a)))
397 |
398 | #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v))
399 | #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0)
400 | #define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0)
401 |
402 | static void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
403 | {
404 | int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1;
405 | void *p = STBIW_REALLOC(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2);
406 | STBIW_ASSERT(p);
407 | if (p) {
408 | if (!*arr) ((int *) p)[1] = 0;
409 | *arr = (void *) ((int *) p + 2);
410 | stbiw__sbm(*arr) = m;
411 | }
412 | return *arr;
413 | }
414 |
415 | static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
416 | {
417 | while (*bitcount >= 8) {
418 | stbiw__sbpush(data, (unsigned char) *bitbuffer);
419 | *bitbuffer >>= 8;
420 | *bitcount -= 8;
421 | }
422 | return data;
423 | }
424 |
425 | static int stbiw__zlib_bitrev(int code, int codebits)
426 | {
427 | int res=0;
428 | while (codebits--) {
429 | res = (res << 1) | (code & 1);
430 | code >>= 1;
431 | }
432 | return res;
433 | }
434 |
435 | static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit)
436 | {
437 | int i;
438 | for (i=0; i < limit && i < 258; ++i)
439 | if (a[i] != b[i]) break;
440 | return i;
441 | }
442 |
443 | static unsigned int stbiw__zhash(unsigned char *data)
444 | {
445 | stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
446 | hash ^= hash << 3;
447 | hash += hash >> 5;
448 | hash ^= hash << 4;
449 | hash += hash >> 17;
450 | hash ^= hash << 25;
451 | hash += hash >> 6;
452 | return hash;
453 | }
454 |
455 | #define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount))
456 | #define stbiw__zlib_add(code,codebits) \
457 | (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush())
458 | #define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c)
459 | // default huffman tables
460 | #define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8)
461 | #define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9)
462 | #define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7)
463 | #define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8)
464 | #define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n))
465 | #define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n))
466 |
467 | #define stbiw__ZHASH 16384
468 |
469 | unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
470 | {
471 | static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
472 | static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 };
473 | static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
474 | static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
475 | unsigned int bitbuf=0;
476 | int i,j, bitcount=0;
477 | unsigned char *out = NULL;
478 | unsigned char **hash_table[stbiw__ZHASH]; // 64KB on the stack!
479 | if (quality < 5) quality = 5;
480 |
481 | stbiw__sbpush(out, 0x78); // DEFLATE 32K window
482 | stbiw__sbpush(out, 0x5e); // FLEVEL = 1
483 | stbiw__zlib_add(1,1); // BFINAL = 1
484 | stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman
485 |
486 | for (i=0; i < stbiw__ZHASH; ++i)
487 | hash_table[i] = NULL;
488 |
489 | i=0;
490 | while (i < data_len-3) {
491 | // hash next 3 bytes of data to be compressed
492 | int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3;
493 | unsigned char *bestloc = 0;
494 | unsigned char **hlist = hash_table[h];
495 | int n = stbiw__sbcount(hlist);
496 | for (j=0; j < n; ++j) {
497 | if (hlist[j]-data > i-32768) { // if entry lies within window
498 | int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i);
499 | if (d >= best) best=d,bestloc=hlist[j];
500 | }
501 | }
502 | // when hash table entry is too long, delete half the entries
503 | if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) {
504 | STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
505 | stbiw__sbn(hash_table[h]) = quality;
506 | }
507 | stbiw__sbpush(hash_table[h],data+i);
508 |
509 | if (bestloc) {
510 | // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
511 | h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1);
512 | hlist = hash_table[h];
513 | n = stbiw__sbcount(hlist);
514 | for (j=0; j < n; ++j) {
515 | if (hlist[j]-data > i-32767) {
516 | int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1);
517 | if (e > best) { // if next match is better, bail on current match
518 | bestloc = NULL;
519 | break;
520 | }
521 | }
522 | }
523 | }
524 |
525 | if (bestloc) {
526 | int d = (int) (data+i - bestloc); // distance back
527 | STBIW_ASSERT(d <= 32767 && best <= 258);
528 | for (j=0; best > lengthc[j+1]-1; ++j);
529 | stbiw__zlib_huff(j+257);
530 | if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
531 | for (j=0; d > distc[j+1]-1; ++j);
532 | stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5);
533 | if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
534 | i += best;
535 | } else {
536 | stbiw__zlib_huffb(data[i]);
537 | ++i;
538 | }
539 | }
540 | // write out final bytes
541 | for (;i < data_len; ++i)
542 | stbiw__zlib_huffb(data[i]);
543 | stbiw__zlib_huff(256); // end of block
544 | // pad with 0 bits to byte boundary
545 | while (bitcount)
546 | stbiw__zlib_add(0,1);
547 |
548 | for (i=0; i < stbiw__ZHASH; ++i)
549 | (void) stbiw__sbfree(hash_table[i]);
550 |
551 | {
552 | // compute adler32 on input
553 | unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552;
554 | int j=0;
555 | while (j < data_len) {
556 | for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1;
557 | s1 %= 65521, s2 %= 65521;
558 | j += blocklen;
559 | blocklen = 5552;
560 | }
561 | stbiw__sbpush(out, (unsigned char) (s2 >> 8));
562 | stbiw__sbpush(out, (unsigned char) s2);
563 | stbiw__sbpush(out, (unsigned char) (s1 >> 8));
564 | stbiw__sbpush(out, (unsigned char) s1);
565 | }
566 | *out_len = stbiw__sbn(out);
567 | // make returned pointer freeable
568 | STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
569 | return (unsigned char *) stbiw__sbraw(out);
570 | }
571 |
572 | unsigned int stbiw__crc32(unsigned char *buffer, int len)
573 | {
574 | static unsigned int crc_table[256];
575 | unsigned int crc = ~0u;
576 | int i,j;
577 | if (crc_table[1] == 0)
578 | for(i=0; i < 256; i++)
579 | for (crc_table[i]=i, j=0; j < 8; ++j)
580 | crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0);
581 | for (i=0; i < len; ++i)
582 | crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
583 | return ~crc;
584 | }
585 |
586 | #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4)
587 | #define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
588 | #define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])
589 |
590 | static void stbiw__wpcrc(unsigned char **data, int len)
591 | {
592 | unsigned int crc = stbiw__crc32(*data - len - 4, len+4);
593 | stbiw__wp32(*data, crc);
594 | }
595 |
596 | static unsigned char stbiw__paeth(int a, int b, int c)
597 | {
598 | int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
599 | if (pa <= pb && pa <= pc) return (unsigned char) a;
600 | if (pb <= pc) return (unsigned char) b;
601 | return (unsigned char) c;
602 | }
603 |
604 | unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
605 | {
606 | int ctype[5] = { -1, 0, 4, 2, 6 };
607 | unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
608 | unsigned char *out,*o, *filt, *zlib;
609 | signed char *line_buffer;
610 | int i,j,k,p,zlen;
611 |
612 | if (stride_bytes == 0)
613 | stride_bytes = x * n;
614 |
615 | filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0;
616 | line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }
617 | for (j=0; j < y; ++j) {
618 | static int mapping[] = { 0,1,2,3,4 };
619 | static int firstmap[] = { 0,1,0,5,6 };
620 | int *mymap = j ? mapping : firstmap;
621 | int best = 0, bestval = 0x7fffffff;
622 | for (p=0; p < 2; ++p) {
623 | for (k= p?best:0; k < 5; ++k) {
624 | int type = mymap[k],est=0;
625 | unsigned char *z = pixels + stride_bytes*j;
626 | for (i=0; i < n; ++i)
627 | switch (type) {
628 | case 0: line_buffer[i] = z[i]; break;
629 | case 1: line_buffer[i] = z[i]; break;
630 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
631 | case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break;
632 | case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break;
633 | case 5: line_buffer[i] = z[i]; break;
634 | case 6: line_buffer[i] = z[i]; break;
635 | }
636 | for (i=n; i < x*n; ++i) {
637 | switch (type) {
638 | case 0: line_buffer[i] = z[i]; break;
639 | case 1: line_buffer[i] = z[i] - z[i-n]; break;
640 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
641 | case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break;
642 | case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break;
643 | case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break;
644 | case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
645 | }
646 | }
647 | if (p) break;
648 | for (i=0; i < x*n; ++i)
649 | est += abs((signed char) line_buffer[i]);
650 | if (est < bestval) { bestval = est; best = k; }
651 | }
652 | }
653 | // when we get here, best contains the filter type, and line_buffer contains the data
654 | filt[j*(x*n+1)] = (unsigned char) best;
655 | STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n);
656 | }
657 | STBIW_FREE(line_buffer);
658 | zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory
659 | STBIW_FREE(filt);
660 | if (!zlib) return 0;
661 |
662 | // each tag requires 12 bytes of overhead
663 | out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12);
664 | if (!out) return 0;
665 | *out_len = 8 + 12+13 + 12+zlen + 12;
666 |
667 | o=out;
668 | STBIW_MEMMOVE(o,sig,8); o+= 8;
669 | stbiw__wp32(o, 13); // header length
670 | stbiw__wptag(o, "IHDR");
671 | stbiw__wp32(o, x);
672 | stbiw__wp32(o, y);
673 | *o++ = 8;
674 | *o++ = (unsigned char) ctype[n];
675 | *o++ = 0;
676 | *o++ = 0;
677 | *o++ = 0;
678 | stbiw__wpcrc(&o,13);
679 |
680 | stbiw__wp32(o, zlen);
681 | stbiw__wptag(o, "IDAT");
682 | STBIW_MEMMOVE(o, zlib, zlen);
683 | o += zlen;
684 | STBIW_FREE(zlib);
685 | stbiw__wpcrc(&o, zlen);
686 |
687 | stbiw__wp32(o,0);
688 | stbiw__wptag(o, "IEND");
689 | stbiw__wpcrc(&o,0);
690 |
691 | STBIW_ASSERT(o == out + *out_len);
692 |
693 | return out;
694 | }
695 |
696 | int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
697 | {
698 | FILE *f;
699 | int len;
700 | unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len);
701 | if (!png) return 0;
702 | f = fopen(filename, "wb");
703 | if (!f) { STBIW_FREE(png); return 0; }
704 | fwrite(png, 1, len, f);
705 | fclose(f);
706 | STBIW_FREE(png);
707 | return 1;
708 | }
709 | #endif // STB_IMAGE_WRITE_IMPLEMENTATION
710 |
711 | /* Revision history
712 | 0.98 (2015-04-08)
713 | added STBIW_MALLOC, STBIW_ASSERT etc
714 | 0.97 (2015-01-18)
715 | fixed HDR asserts, rewrote HDR rle logic
716 | 0.96 (2015-01-17)
717 | add HDR output
718 | fix monochrome BMP
719 | 0.95 (2014-08-17)
720 | add monochrome TGA output
721 | 0.94 (2014-05-31)
722 | rename private functions to avoid conflicts with stb_image.h
723 | 0.93 (2014-05-27)
724 | warning fixes
725 | 0.92 (2010-08-01)
726 | casts to unsigned char to fix warnings
727 | 0.91 (2010-07-17)
728 | first public release
729 | 0.90 first internal release
730 | */
731 |
--------------------------------------------------------------------------------
/src/surface_texture.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef SURFTEXTH
13 | #define SURFTEXTH
14 |
15 | #include "texture.h"
16 |
17 | class image_texture : public texture {
18 | public:
19 | image_texture() {}
20 | image_texture(unsigned char *pixels, int A, int B) : data(pixels), nx(A), ny(B) {}
21 | virtual vec3 value(float u, float v, const vec3& p) const;
22 | unsigned char *data;
23 | int nx, ny;
24 | };
25 |
26 | vec3 image_texture::value(float u, float v, const vec3& p) const {
27 | int i = ( u)*nx;
28 | int j = (1-v)*ny-0.001;
29 | if (i < 0) i = 0;
30 | if (j < 0) j = 0;
31 | if (i > nx-1) i = nx-1;
32 | if (j > ny-1) j = ny-1;
33 | float r = int(data[3*i + 3*nx*j] ) / 255.0;
34 | float g = int(data[3*i + 3*nx*j+1]) / 255.0;
35 | float b = int(data[3*i + 3*nx*j+2]) / 255.0;
36 | return vec3(r, g, b);
37 | }
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/src/texture.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef TEXTUREH
13 | #define TEXTUREH
14 |
15 | #include "perlin.h"
16 |
17 | class texture {
18 | public:
19 | virtual vec3 value(float u, float v, const vec3& p) const = 0;
20 | };
21 |
22 | class constant_texture : public texture {
23 | public:
24 | constant_texture() { }
25 | constant_texture(vec3 c) : color(c) { }
26 | virtual vec3 value(float u, float v, const vec3& p) const {
27 | return color;
28 | }
29 | vec3 color;
30 | };
31 |
32 | class checker_texture : public texture {
33 | public:
34 | checker_texture() { }
35 | checker_texture(texture *t0, texture *t1): even(t0), odd(t1) { }
36 | virtual vec3 value(float u, float v, const vec3& p) const {
37 | float sines = sin(10*p.x())*sin(10*p.y())*sin(10*p.z());
38 | if (sines < 0)
39 | return odd->value(u, v, p);
40 | else
41 | return even->value(u, v, p);
42 | }
43 | texture *odd;
44 | texture *even;
45 | };
46 |
47 |
48 | class noise_texture : public texture {
49 | public:
50 | noise_texture() {}
51 | noise_texture(float sc) : scale(sc) {}
52 | virtual vec3 value(float u, float v, const vec3& p) const {
53 | // return vec3(1,1,1)*0.5*(1 + noise.turb(scale * p));
54 | // return vec3(1,1,1)*noise.turb(scale * p);
55 | return vec3(1,1,1)*0.5*(1 + sin(scale*p.x() + 5*noise.turb(scale*p))) ;
56 | }
57 | perlin noise;
58 | float scale;
59 | };
60 |
61 | #endif
62 |
--------------------------------------------------------------------------------
/src/vec3.h:
--------------------------------------------------------------------------------
1 | //==================================================================================================
2 | // Written in 2016 by Peter Shirley
3 | //
4 | // To the extent possible under law, the author(s) have dedicated all copyright and related and
5 | // neighboring rights to this software to the public domain worldwide. This software is distributed
6 | // without any warranty.
7 | //
8 | // You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
9 | // with this software. If not, see .
10 | //==================================================================================================
11 |
12 | #ifndef VEC3H
13 | #define VEC3H
14 |
15 | #include
16 | #include
17 | #include
18 |
19 | class vec3 {
20 |
21 |
22 | public:
23 | vec3() {}
24 | vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; }
25 | inline float x() const { return e[0]; }
26 | inline float y() const { return e[1]; }
27 | inline float z() const { return e[2]; }
28 | inline float r() const { return e[0]; }
29 | inline float g() const { return e[1]; }
30 | inline float b() const { return e[2]; }
31 |
32 | inline const vec3& operator+() const { return *this; }
33 | inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
34 | inline float operator[](int i) const { return e[i]; }
35 | inline float& operator[](int i) { return e[i]; };
36 |
37 | inline vec3& operator+=(const vec3 &v2);
38 | inline vec3& operator-=(const vec3 &v2);
39 | inline vec3& operator*=(const vec3 &v2);
40 | inline vec3& operator/=(const vec3 &v2);
41 | inline vec3& operator*=(const float t);
42 | inline vec3& operator/=(const float t);
43 |
44 | inline float length() const { return sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); }
45 | inline float squared_length() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; }
46 | inline void make_unit_vector();
47 |
48 | float e[3];
49 | };
50 |
51 |
52 |
53 | inline std::istream& operator>>(std::istream &is, vec3 &t) {
54 | is >> t.e[0] >> t.e[1] >> t.e[2];
55 | return is;
56 | }
57 |
58 | inline std::ostream& operator<<(std::ostream &os, const vec3 &t) {
59 | os << t.e[0] << " " << t.e[1] << " " << t.e[2];
60 | return os;
61 | }
62 |
63 | inline void vec3::make_unit_vector() {
64 | float k = 1.0 / sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]);
65 | e[0] *= k; e[1] *= k; e[2] *= k;
66 | }
67 |
68 | inline vec3 operator+(const vec3 &v1, const vec3 &v2) {
69 | return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]);
70 | }
71 |
72 | inline vec3 operator-(const vec3 &v1, const vec3 &v2) {
73 | return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]);
74 | }
75 |
76 | inline vec3 operator*(const vec3 &v1, const vec3 &v2) {
77 | return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]);
78 | }
79 |
80 | inline vec3 operator/(const vec3 &v1, const vec3 &v2) {
81 | return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]);
82 | }
83 |
84 | inline vec3 operator*(float t, const vec3 &v) {
85 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
86 | }
87 |
88 | inline vec3 operator/(vec3 v, float t) {
89 | return vec3(v.e[0]/t, v.e[1]/t, v.e[2]/t);
90 | }
91 |
92 | inline vec3 operator*(const vec3 &v, float t) {
93 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
94 | }
95 |
96 | inline float dot(const vec3 &v1, const vec3 &v2) {
97 | return v1.e[0] *v2.e[0] + v1.e[1] *v2.e[1] + v1.e[2] *v2.e[2];
98 | }
99 |
100 | inline vec3 cross(const vec3 &v1, const vec3 &v2) {
101 | return vec3( (v1.e[1]*v2.e[2] - v1.e[2]*v2.e[1]),
102 | (-(v1.e[0]*v2.e[2] - v1.e[2]*v2.e[0])),
103 | (v1.e[0]*v2.e[1] - v1.e[1]*v2.e[0]));
104 | }
105 |
106 |
107 | inline vec3& vec3::operator+=(const vec3 &v){
108 | e[0] += v.e[0];
109 | e[1] += v.e[1];
110 | e[2] += v.e[2];
111 | return *this;
112 | }
113 |
114 | inline vec3& vec3::operator*=(const vec3 &v){
115 | e[0] *= v.e[0];
116 | e[1] *= v.e[1];
117 | e[2] *= v.e[2];
118 | return *this;
119 | }
120 |
121 | inline vec3& vec3::operator/=(const vec3 &v){
122 | e[0] /= v.e[0];
123 | e[1] /= v.e[1];
124 | e[2] /= v.e[2];
125 | return *this;
126 | }
127 |
128 | inline vec3& vec3::operator-=(const vec3& v) {
129 | e[0] -= v.e[0];
130 | e[1] -= v.e[1];
131 | e[2] -= v.e[2];
132 | return *this;
133 | }
134 |
135 | inline vec3& vec3::operator*=(const float t) {
136 | e[0] *= t;
137 | e[1] *= t;
138 | e[2] *= t;
139 | return *this;
140 | }
141 |
142 | inline vec3& vec3::operator/=(const float t) {
143 | float k = 1.0/t;
144 |
145 | e[0] *= k;
146 | e[1] *= k;
147 | e[2] *= k;
148 | return *this;
149 | }
150 |
151 | inline vec3 unit_vector(vec3 v) {
152 | return v / v.length();
153 | }
154 |
155 | #endif
156 |
--------------------------------------------------------------------------------