├── 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 | ![Image 1-1](../assets/img1-01.jpg) 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 | ![Figure 2-1](../assets/fig2-01.jpg) 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 | ![Figure 2-2](../assets/fig2-02.jpg) 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 | ![Figure 2-3](../assets/fig2-03.jpg) 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 | ![Figure 2-4](../assets/fig2-04.jpg) 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 | ![Image 3-1](../assets/img3-01.jpg) 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 | ![Image 3-2](../assets/img3-02.jpg) 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 | ![Image 4-1](../assets/img4-01.jpg) 15 | 16 | Instead it returns something similar to blurred white noise: 17 | 18 | ![Image 4-2](../assets/img4-02.jpg) 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 | ![Image 4-3](../assets/img4-03.jpg) 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 | ![Image 4-4](../assets/img4-04.jpg) 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 | ![Image 4-5](../assets/img4-05.jpg) 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 | ![Image 4-6](../assets/img4-06.jpg) 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 | ![Image 4-7](../assets/img4-07.jpg) 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 | ![Image 4-8](../assets/img4-08.jpg) 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 | ![Image 4-9](../assets/img4-09.jpg) 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 | ![Image 4-10](../assets/img4-10.jpg) 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 | ![Image 5-1](../assets/img5-01.jpg) 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 | ![Figure 6-1](../assets/fig6-01.jpg) 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 | ![Image 6-1](../assets/img6-01.jpg) 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 | ![Image 6-2](../assets/img6-02.jpg) 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 | ![Image 6-3](../assets/img6-03.jpg) 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 | ![Image 6-4](../assets/img6-04.jpg) 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 | ![Image 7-1](../assets/img7-01.jpg) 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 | ![Figure 7-1](../assets/fig7-01.jpg) 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 | ![Figure 7-2](../assets/fig7-02.jpg) 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 | ![Image 7-2](../assets/img7-02.jpg) 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 | ![Figure 8-1](../assets/fig8-01.jpg) 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 | ![Image 8-1](../assets/img8-01.jpg) 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 | ![Image 9-1](../assets/img9-01.jpg) 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 | --------------------------------------------------------------------------------