├── CHANGELOG.md ├── COPYING.txt ├── README.md ├── assets ├── RTOneWeekend.jpg ├── book.css ├── fig03-1.jpg ├── fig03-2.jpg ├── fig04-1.jpg ├── fig05-1.jpg ├── fig06-1.jpg ├── fig07-1.jpg ├── fig07-2.jpg ├── fig08-1.jpg ├── fig08-2.jpg ├── fig09-1.jpg ├── fig10-1.jpg ├── fig10-2.jpg ├── fig10-3.jpg ├── fig11-1.jpg ├── fig11-2.jpg ├── img01-1.jpg ├── img01-2.jpg ├── img03-1.jpg ├── img04-1.jpg ├── img05-1.jpg ├── img05-2.jpg ├── img06-1.jpg ├── img07-1.jpg ├── img07-2.jpg ├── img08-1.jpg ├── img08-2.jpg ├── img09-1.jpg ├── img09-2.jpg ├── img09-3.jpg ├── img10-1.jpg ├── img10-2.jpg ├── img10-3.jpg ├── img11-1.jpg ├── img12-1.jpg └── markdeep.min.js ├── book ├── ack.md.html ├── 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 ├── ch10.md.html ├── ch11.md.html └── ch12.md.html ├── index.html └── src ├── camera.h ├── hitable.h ├── hitable_list.h ├── main.cc ├── material.h ├── random.h ├── ray.h ├── sphere.h └── vec3.h /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log — _Ray Tracing in One Weekend_ 2 | ================================================================================ 3 | 4 | v1.54.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/RTOneWeekend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/RTOneWeekend.jpg -------------------------------------------------------------------------------- /assets/book.css: -------------------------------------------------------------------------------- 1 | body { 2 | } 3 | 4 | /* Under-Development Warning Box */ 5 | 6 | div.warning { 7 | background: #fee; 8 | margin: 1em 4em 4em 4em; 9 | border: solid 4px red; 10 | padding: 0em 4ex 1em 4ex; 11 | } 12 | 13 | div.warning a { 14 | font-family: sans-serif; 15 | } 16 | 17 | div.warning p { 18 | font-family: sans-serif; 19 | font-size: 100%; 20 | font-weight: normal; 21 | color: black; 22 | } 23 | 24 | div.warning p.title { 25 | font-family: sans-serif; 26 | font-size: 160%; 27 | font-weight: bold; 28 | color: red; 29 | } 30 | -------------------------------------------------------------------------------- /assets/fig03-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig03-1.jpg -------------------------------------------------------------------------------- /assets/fig03-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig03-2.jpg -------------------------------------------------------------------------------- /assets/fig04-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig04-1.jpg -------------------------------------------------------------------------------- /assets/fig05-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig05-1.jpg -------------------------------------------------------------------------------- /assets/fig06-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig06-1.jpg -------------------------------------------------------------------------------- /assets/fig07-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig07-1.jpg -------------------------------------------------------------------------------- /assets/fig07-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig07-2.jpg -------------------------------------------------------------------------------- /assets/fig08-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig08-1.jpg -------------------------------------------------------------------------------- /assets/fig08-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig08-2.jpg -------------------------------------------------------------------------------- /assets/fig09-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig09-1.jpg -------------------------------------------------------------------------------- /assets/fig10-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig10-1.jpg -------------------------------------------------------------------------------- /assets/fig10-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig10-2.jpg -------------------------------------------------------------------------------- /assets/fig10-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig10-3.jpg -------------------------------------------------------------------------------- /assets/fig11-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig11-1.jpg -------------------------------------------------------------------------------- /assets/fig11-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/fig11-2.jpg -------------------------------------------------------------------------------- /assets/img01-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img01-1.jpg -------------------------------------------------------------------------------- /assets/img01-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img01-2.jpg -------------------------------------------------------------------------------- /assets/img03-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img03-1.jpg -------------------------------------------------------------------------------- /assets/img04-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img04-1.jpg -------------------------------------------------------------------------------- /assets/img05-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img05-1.jpg -------------------------------------------------------------------------------- /assets/img05-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img05-2.jpg -------------------------------------------------------------------------------- /assets/img06-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img06-1.jpg -------------------------------------------------------------------------------- /assets/img07-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img07-1.jpg -------------------------------------------------------------------------------- /assets/img07-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img07-2.jpg -------------------------------------------------------------------------------- /assets/img08-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img08-1.jpg -------------------------------------------------------------------------------- /assets/img08-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img08-2.jpg -------------------------------------------------------------------------------- /assets/img09-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img09-1.jpg -------------------------------------------------------------------------------- /assets/img09-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img09-2.jpg -------------------------------------------------------------------------------- /assets/img09-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img09-3.jpg -------------------------------------------------------------------------------- /assets/img10-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img10-1.jpg -------------------------------------------------------------------------------- /assets/img10-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img10-2.jpg -------------------------------------------------------------------------------- /assets/img10-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img10-3.jpg -------------------------------------------------------------------------------- /assets/img11-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img11-1.jpg -------------------------------------------------------------------------------- /assets/img12-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTracing/InOneWeekend/e6eed60166360c18ef5d87b4938a207f78a36a2a/assets/img12-1.jpg -------------------------------------------------------------------------------- /book/ack.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | **Acknowledgements** 11 | 12 | Thanks to readers Apoorva Joshi, Jason Stone, Becker, Matthew Heimlich, Frank He, Benjamin 13 | Summerton, Marcus Ottosson and Lorenzo Mancini for finding bugs, and to the [limnu.com][] team with 14 | help on the figures. Thanks to Fabio San for pull requests. 15 | 16 | 17 | [limnu.com]: https://limnu.com/ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /book/ch00.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | **Ray Tracing in One Weekend** 11 | Peter Shirley 12 | Version 1.54 13 | Copyright 2018. Peter Shirley. All rights reserved. 14 | 15 | 16 | 17 | **Chapter 0: Overview** 18 | 19 | I’ve taught many graphics classes over the years. Often I do them in ray tracing, because you are 20 | forced to write all the code but you can still get cool images with no API. I decided to adapt my 21 | course notes into a how-to, to get you to a cool program as quickly as possible. It will not be a 22 | full-featured ray tracer, but it does have the indirect lighting which has made ray tracing a staple 23 | in movies. Follow these steps, and the architecture of the ray tracer you produce will be good for 24 | extending to a more extensive ray tracer if you get excited and want to pursue that. 25 | 26 | When somebody says “ray tracing” it could mean many things. What I am going to describe is 27 | technically a path tracer, and a fairly general one. While the code will be pretty simple (let the 28 | computer do the work!) I think you’ll be very happy with the images you can make. 29 | 30 | I’ll take you through writing a ray tracer in the order I do it, along with some debugging tips. By 31 | the end, you will have a ray tracer that produces some great images. You should be able to do this 32 | in a weekend. If you take longer, don’t worry about it. I use C++ as the driving language, but you 33 | don’t need to. However, I suggest you do, because it’s fast, portable, and most production movie and 34 | video game renderers are written in C++. Note that I avoid most “modern features” of C++, but 35 | inheritance and operator overloading are too useful for ray tracers to pass on. I do not provide the 36 | code online, but the code is real and I show all of it except for a few straightforward operators in 37 | the vec3 class. I am a big believer in typing in code to learn it, but when code is available I use 38 | it, so I only practice what I preach when the code is not available. So don’t ask! 39 | 40 | I have left that last part in because it is funny what a 180 I have done. Several readers ended up 41 | with subtle errors that were helped when we compared code. So please do type in the code, but if you 42 | want to look at mine it is at: 43 | 44 | https://github.com/petershirley/raytracinginoneweekend 45 | 46 | I assume a little bit of familiarity with vectors (like dot product and vector addition). If you 47 | don’t know that, do a little review. If you need that review, or to learn it for the first time, 48 | check out Marschner’s and my graphics text, Foley, Van Dam, et al., or McGuire’s graphics codex. 49 | 50 | If you run into trouble, or do something cool you’d like to show somebody, send me some email at 51 | ptrshrl@gmail.com 52 | 53 | I’ll be maintaining a site related to the book including further reading and links to resources at a 54 | blog in1weekend related to this book. 55 | 56 | Let’s get on with it! 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /book/ch01.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 1: Output an image** 12 | 13 | Whenever you start a renderer, you need a way to see an image. The most straightforward way is to 14 | write it to a file. The catch is, there are so many formats and many of those are complex. I always 15 | start with a plain text ppm file. Here’s a nice description from Wikipedia: 16 | 17 | ![Image 1-1: PPM Example](../assets/img01-1.jpg) 18 | 19 | Let’s make some C++ code to output such a thing: 20 | 21 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 22 | #include 23 | 24 | int main() { 25 | int nx = 200; 26 | int ny = 100; 27 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 28 | for (int j = ny-1; j >= 0; j--) { 29 | for (int i = 0; i < nx; i++) { 30 | float r = float(i) / float(nx); 31 | float g = float(j) / float(ny); 32 | float b = 0.2; 33 | int ir = int(255.99*r); 34 | int ig = int(255.99*g); 35 | int ib = int(255.99*b); 36 | std::cout << ir << " " << ig << " " << ib << "\n"; 37 | } 38 | } 39 | } 40 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 41 | 42 | There are some things to note in that code: 43 | 44 | 1. The pixels are written out in rows with pixels left to right. 45 | 46 | 2. The rows are written out from top to bottom. 47 | 48 | 3. By convention, each of the red/green/blue components range from 0.0 to 1.0. We will relax that 49 | later when we internally use high dynamic range, but before output we will tone map to the zero 50 | to one range, so this code won’t change. 51 | 52 | 4. Red goes from black to fully on from left to right, and green goes from black at the bottom to 53 | fully on at the top. Red and green together make yellow so we should expect the upper right 54 | corner to be yellow. 55 | 56 | Opening the output file (in ToyViewer on my mac, but try it in your favorite viewer and google “ppm 57 | viewer” if your viewer doesn’t support it) shows: 58 | 59 | ![Image 1-2](../assets/img01-2.jpg) 60 | 61 | Hooray! This is the graphics “hello world”. If your image doesn’t look like that, open the output 62 | file in a text editor and see what it looks like. It should start something like this: 63 | 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | P3 66 | 200 100 67 | 255 68 | 0 253 51 69 | 1 253 51 70 | 2 253 51 71 | 3 253 51 72 | 4 253 51 73 | 5 253 51 74 | 6 253 51 75 | 7 253 51 76 | 8 253 51 77 | 9 253 51 78 | 10 253 51 79 | 11 253 51 80 | 12 253 51 81 | 13 253 51 82 | 14 253 51 83 | 15 253 51 84 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 85 | 86 | If it doesn’t, then you probably just have some newlines or something similar that is confusing the 87 | image reader. 88 | 89 | If you want to produce more image types than PPM, I am a fan of `stb_image.h` available on github. 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /book/ch02.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 2: The vec3 Class** 12 | 13 | Almost all graphics programs have some class(es) for storing geometric vectors and colors. In many 14 | systems these vectors are 4D (3D plus a homogeneous coordinate for geometry, and RGB plus an alpha 15 | transparency channel for colors). For our purposes, three coordinates suffices. We’ll use the same 16 | class `vec3` for colors, locations, directions, offsets, whatever. Some people don’t like this 17 | because it doesn’t prevent you from doing something silly, like adding a color to a location. They 18 | have a good point, but we’re going to always take the “less code” route when not obviously wrong. 19 | 20 | Here’s the top part of my vec3 class: 21 | 22 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 23 | #include 24 | #include 25 | #include 26 | 27 | class vec3 { 28 | public: 29 | vec3() {} 30 | vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; } 31 | inline float x() const { return e[0]; } 32 | inline float y() const { return e[1]; } 33 | inline float z() const { return e[2]; } 34 | inline float r() const { return e[0]; } 35 | inline float g() const { return e[1]; } 36 | inline float b() const { return e[2]; } 37 | 38 | inline const vec3& operator+() const { return *this; } 39 | inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); } 40 | inline float operator[](int i) const { return e[i]; } 41 | inline float& operator[](int i) { return e[i]; } 42 | 43 | inline vec3& operator+=(const vec3 &v2); 44 | inline vec3& operator-=(const vec3 &v2); 45 | inline vec3& operator*=(const vec3 &v2); 46 | inline vec3& operator/=(const vec3 &v2); 47 | inline vec3& operator*=(const float t); 48 | inline vec3& operator/=(const float t); 49 | 50 | inline float length() const { return sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); } 51 | inline float squared_length() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; } 52 | inline void make_unit_vector(); 53 | 54 | float e[3]; 55 | }; 56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | I use floats here, but in some ray tracers I have used doubles. Neither is correct -- follow your 59 | own tastes. Everything is in the header file, and later on in the file are lots of vector 60 | operations: 61 | 62 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 63 | inline std::istream& operator>>(std::istream &is, vec3 &t) { 64 | is >> t.e[0] >> t.e[1] >> t.e[2]; 65 | return is; 66 | } 67 | 68 | inline std::ostream& operator<<(std::ostream &os, const vec3 &t) { 69 | os << t.e[0] << " " << t.e[1] << " " << t.e[2]; 70 | return os; 71 | } 72 | 73 | inline void vec3::make_unit_vector() { 74 | float k = 1.0 / sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); 75 | e[0] *= k; e[1] *= k; e[2] *= k; 76 | } 77 | 78 | inline vec3 operator+(const vec3 &v1, const vec3 &v2) { 79 | return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]); 80 | } 81 | 82 | inline vec3 operator-(const vec3 &v1, const vec3 &v2) { 83 | return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]); 84 | } 85 | 86 | inline vec3 operator*(const vec3 &v1, const vec3 &v2) { 87 | return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]); 88 | } 89 | 90 | inline vec3 operator/(const vec3 &v1, const vec3 &v2) { 91 | return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]); 92 | } 93 | 94 | inline vec3 operator*(float t, const vec3 &v) { 95 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); 96 | } 97 | 98 | inline vec3 operator/(vec3 v, float t) { 99 | return vec3(v.e[0]/t, v.e[1]/t, v.e[2]/t); 100 | } 101 | 102 | inline vec3 operator*(const vec3 &v, float t) { 103 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); 104 | } 105 | 106 | inline float dot(const vec3 &v1, const vec3 &v2) { 107 | return v1.e[0] *v2.e[0] + v1.e[1] *v2.e[1] + v1.e[2] *v2.e[2]; 108 | } 109 | 110 | inline vec3 cross(const vec3 &v1, const vec3 &v2) { 111 | return vec3(v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1], 112 | v1.e[2] * v2.e[0] - v1.e[0] * v2.e[2], 113 | v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]); 114 | } 115 | 116 | inline vec3& vec3::operator+=(const vec3 &v){ 117 | e[0] += v.e[0]; 118 | e[1] += v.e[1]; 119 | e[2] += v.e[2]; 120 | return *this; 121 | } 122 | 123 | inline vec3& vec3::operator*=(const vec3 &v){ 124 | e[0] *= v.e[0]; 125 | e[1] *= v.e[1]; 126 | e[2] *= v.e[2]; 127 | return *this; 128 | } 129 | 130 | inline vec3& vec3::operator/=(const vec3 &v){ 131 | e[0] /= v.e[0]; 132 | e[1] /= v.e[1]; 133 | e[2] /= v.e[2]; 134 | return *this; 135 | } 136 | 137 | inline vec3& vec3::operator-=(const vec3& v) { 138 | e[0] -= v.e[0]; 139 | e[1] -= v.e[1]; 140 | e[2] -= v.e[2]; 141 | return *this; 142 | } 143 | 144 | inline vec3& vec3::operator*=(const float t) { 145 | e[0] *= t; 146 | e[1] *= t; 147 | e[2] *= t; 148 | return *this; 149 | } 150 | 151 | inline vec3& vec3::operator/=(const float t) { 152 | float k = 1.0/t; 153 | 154 | e[0] *= k; 155 | e[1] *= k; 156 | e[2] *= k; 157 | return *this; 158 | } 159 | 160 | inline vec3 unit_vector(vec3 v) { 161 | return v / v.length(); 162 | } 163 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 164 | 165 | Now we can change our main to use this: 166 | 167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 168 | #include 169 | #include "vec3.h" 170 | 171 | int main() { 172 | int nx = 200; 173 | int ny = 100; 174 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 175 | for (int j = ny-1; j >= 0; j--) { 176 | for (int i = 0; i < nx; i++) { 177 | vec3 col(float(i) / float(nx), float(j) / float(ny), 0.2); 178 | int ir = int(255.99*col[0]); 179 | int ig = int(255.99*col[1]); 180 | int ib = int(255.99*col[2]); 181 | std::cout << ir << " " << ig << " " << ib << "\n"; 182 | } 183 | } 184 | } 185 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /book/ch03.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 3: Rays, a simple camera, and background** 12 | 13 | The one thing that all ray tracers have is a ray class, and a computation of what color is seen 14 | along a ray. Let’s think of a ray as a function $p(t) = A + t*B$. Here $p$ is a 3D position along a 15 | line in 3D. $A$ is the ray origin and $B$ is the ray direction. The ray parameter $t$ is a real 16 | number (float in the code). Plug in a different $t$ and $p(t)$ moves the point along the ray. Add in 17 | negative $t$ and you can go anywhere on the 3D line. For positive $t$, you get only the parts in 18 | front of $A$, and this is what is often called a half-line or ray. The example $C = p(2)$ is shown 19 | here: 20 | 21 | ![Figure 3-1](../assets/fig03-1.jpg) 22 | 23 | The function $p(t)$ in more verbose code form I call “point_at_parameter(t)”: 24 | 25 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 26 | #ifndef RAYH 27 | #define RAYH 28 | #include "vec3.h" 29 | 30 | class ray 31 | { 32 | public: 33 | ray() {} 34 | ray(const vec3& a, const vec3& b) { A = a; B = b; } 35 | vec3 origin() const { return A; } 36 | vec3 direction() const { return B; } 37 | vec3 point_at_parameter(float t) const { return A + t*B; } 38 | 39 | vec3 A; 40 | vec3 B; 41 | }; 42 | 43 | #endif 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | Now we are ready to turn the corner and make a ray tracer. At the core of a ray tracer is to send 47 | rays through pixels and compute what color is seen in the direction of those rays. This is of the 48 | form calculate which ray goes from the eye to a pixel, compute what that ray intersects, and compute 49 | a color for that intersection point. When first developing a ray tracer, I always do a simple camera 50 | for getting the code up and running. I also make a simple `color(ray)` function that returns the 51 | color of the background (a simple gradient). 52 | 53 | I’ve often gotten into trouble using square images for debugging because I transpose $x$ and $y$ too 54 | often, so I’ll stick with a 200×100 image. I’ll put the “eye” (or camera center if you think of a 55 | camera) at $(0,0,0)$. I will have the y-axis go up, and the x-axis to the right. In order to respect 56 | the convention of a right handed coordinate system, into the screen is the negative z-axis. I will 57 | traverse the screen from the lower left hand corner and use two offset vectors along the screen 58 | sides to move the ray endpoint across the screen. Note that I do not make the ray direction a unit 59 | length vector because I think not doing that makes for simpler and slightly faster code. 60 | 61 | ![Figure 3-2](../assets/fig03-2.jpg) 62 | 63 | Below in code, the ray $r$ goes to approximately the pixel centers (I won’t worry about exactness 64 | for now because we’ll add antialiasing later): 65 | 66 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 67 | #include 68 | #include "ray.h" 69 | 70 | vec3 color(const ray& r, hitable *world, int depth) { 71 | vec3 unit_direction = unit_vector(r.direction()); 72 | float t = 0.5*(unit_direction.y() + 1.0); 73 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 74 | } 75 | 76 | int main() { 77 | int nx = 200; 78 | int ny = 100; 79 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 80 | vec3 lower_left_corner(-2.0, -1.0, -1.0); 81 | vec3 horizontal(4.0, 0.0, 0.0); 82 | vec3 vertical(0.0, 2.0, 0.0); 83 | vec3 origin(0.0, 0.0, 0.0); 84 | for (int j = ny-1; j >= 0; j--) { 85 | for (int i = 0; i < nx; i++) { 86 | float u = float(i) / float(nx); 87 | float v = float(j) / float(ny); 88 | ray r(origin, lower_left_corner + u*horizontal + v*vertical); 89 | vec3 col = color(r); 90 | int ir = int(255.99*col[0]); 91 | int ig = int(255.99*col[1]); 92 | int ib = int(255.99*col[2]); 93 | 94 | std::cout << ir << " " << ig << " " << ib << "\n"; 95 | } 96 | } 97 | } 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 99 | 100 | The `color(ray)` function linearly blends white and blue depending on the up/downess of the $y$ 101 | coordinate. I first made it a unit vector so $-1.0 < y < 1.0$. I then did a standard graphics trick 102 | of scaling that to $0.0 < t < 1.0$. When $t = 1.0$ I want blue. When $t = 0.0$ I want white. In 103 | between, I want a blend. This forms a “linear blend”, or “linear interpolation”, or “lerp” for 104 | short, between two things. A lerp is always of the form 105 | 106 | $$ blendedValue = (1-t)*startValue + t*endValue, $$ 107 | 108 | with $t$ going from zero to one. In our case this produces: 109 | 110 | ![Image 3-1](../assets/img03-1.jpg) 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /book/ch04.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 4: Adding a sphere** 12 | 13 | Let’s add a single object to our ray tracer. People often use spheres in ray tracers because 14 | calculating whether a ray hits a sphere is pretty straightforward. Recall that the equation for a 15 | sphere centered at the origin of radius $R$ is $x^2 + y^2 + z^2 = R^2$. The way you can read that 16 | equation is “for any $(x, y, z)$, if $x^2 + y^2 + z^2 = R^2$ then $(x,y,z)$ is on the sphere, and 17 | otherwise it is not”. It gets uglier if the sphere center is at $(C_x, C_y, C_z)$: 18 | 19 | $$ (x-C_x)^2 + (y-C_y)^2 + (z-C_z)^2 = R^2 $$ 20 | 21 | In graphics, you almost always want your formulas to be in terms of vectors so all the x/y/z stuff 22 | is under the hood in the `vec3` class. You might note that the vector from center 23 | $ C = (C_x,C_y,C_z) $ to point $ P = (x,y,z) $ is $ (p - C) $, and therefore 24 | 25 | $$ dot((p - C),(p - C)) = (x-C_x)^2 + (y-C_y)^2 + (z-C_z)^2 $$ 26 | 27 | So the equation of the sphere in vector form is: 28 | 29 | $$ dot((p - C),(p - C)) = R^2 $$ 30 | 31 | We can read this as “any point p that satisfies this equation is on the sphere”. We want to know if 32 | our ray $ p(t) = A + t*B $ ever hits the sphere anywhere. If it does hit the sphere, there is some 33 | $t$ for which $p(t)$ satisfies the sphere equation. So we are looking for any t where this is true: 34 | 35 | $$ dot((p(t) - C),(p(t) - C)) = R^2 $$ 36 | 37 | or expanding the full form of the ray $p(t)$: 38 | 39 | $$ dot((A + t*B - C), (A + t*B - C)) = R^2 $$ 40 | 41 | The rules of vector algebra are all that we would want here, and if we expand that equation and 42 | move all the terms to the left hand side we get: 43 | 44 | $$ t^2 \cdot dot(B,B) + 2t \cdot dot(B,A-C) + dot(A-C,A-C) - R^2 = 0 $$ 45 | 46 | The vectors and $R$ in that equation are all constant and known. The unknown is $t$, and the 47 | equation is a quadratic, like you probably saw in your high school math class. You can solve for $t$ 48 | and there is a square root part that is either positive (meaning two real solutions), negative 49 | (meaning no real solutions), or zero (meaning one real solution). In graphics, the algebra almost 50 | always relates very directly to the geometry. What we have is: 51 | 52 | ![Figure 4-1][fig04-1] 53 | 54 | If we take that math and hard-code it into our program, we can test it by coloring red any pixel 55 | that hits a small sphere we place at -1 on the z-axis: 56 | 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 58 | bool hit_sphere(const vec3& center, float radius, const ray& r) { 59 | vec3 oc = r.origin() - center; 60 | float a = dot(r.direction(), r.direction()); 61 | float b = 2.0 * dot(oc, r.direction()); 62 | float c = dot(oc, oc) - radius*radius; 63 | float discriminant = b*b - 4*a*c; 64 | return (discriminant > 0); 65 | } 66 | 67 | vec3 color(const ray& r) { 68 | if (hit_sphere(vec3(0,0,-1), 0.5, r)) 69 | return vec3(1, 0, 0); 70 | vec3 unit_direction = unit_vector(r.direction()); 71 | float t = 0.5*(unit_direction.y() + 1.0); 72 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 73 | } 74 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 75 | 76 | What we get is this: 77 | 78 | ![Image 4-1][img04-1] 79 | 80 | Now this lacks all sorts of things -- like shading and reflection rays and more than one object -- 81 | but we are closer to halfway done than we are to our start! One thing to be aware of is that we 82 | tested whether the ray hits the sphere at all, but $t < 0$ solutions work fine. If you change your 83 | sphere center to $z = +1$ you will get exactly the same picture because you see the things behind 84 | you. This is not a feature! We’ll fix those issues next. 85 | 86 | 87 | 88 | [fig04-1]: ../assets/fig04-1.jpg 89 | [img04-1]: ../assets/img04-1.jpg 90 | 91 | 92 | 93 | 94 | 95 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /book/ch05.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 5: Surface normals and multiple objects** 12 | 13 | First, let’s get ourselves a surface normal so we can shade. This is a vector that is perpendicular 14 | to the surface, and by convention, points out. One design decision is whether these normals 15 | (again by convention) are unit length. That is convenient for shading so I will say yes, but I won’t 16 | enforce that in the code. This could allow subtle bugs, so be aware this is personal preference 17 | as are most design decisions like that. For a sphere, the normal is in the direction of the hitpoint 18 | minus the center: 19 | 20 | ![Figure 5-1](../assets/fig05-1.jpg) 21 | 22 | On the earth, this implies that the vector from the earth’s center to you points straight up. Let’s 23 | throw that into the code now, and shade it. We don’t have any lights or anything yet, so let’s just 24 | visualize the normals with a color map. A common trick used for visualizing normals (because it’s 25 | easy and somewhat intuitive to assume $N$ is a unit length vector -- so each component is between -1 26 | and 1) is to map each component to the interval from 0 to 1, and then map x/y/z to r/g/b. For the 27 | normal, we need the hit point, not just whether we hit or not. Let’s assume the closest hit point 28 | (smallest $t$). These changes in the code let us compute and visualize $N$: 29 | 30 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 31 | bool hit_sphere(const vec3& center, float radius, const ray& r) { 32 | vec3 oc = r.origin() - center; 33 | float a = dot(r.direction(), r.direction()); 34 | float b = 2.0 * dot(oc, r.direction()); 35 | float c = dot(oc, oc) - radius*radius; 36 | float discriminant = b*b - 4*a*c; 37 | if (discriminant < 0) { 38 | return -1.0; 39 | } 40 | else { 41 | return (-b - sqrt(discriminant) ) / (2.0*a); 42 | } 43 | } 44 | 45 | vec3 color(const ray& r) { 46 | float t = hit_sphere(vec3(0,0,-1), 0.5, r); 47 | if (t > 0.0) { 48 | vec3 N = unit_vector(r.point_at_parameter(t) - vec3(0,0,-1)); 49 | return 0.5*vec3(N.x()+1, N.y()+1, N.z()+1); 50 | } 51 | vec3 unit_direction = unit_vector(r.direction()); 52 | t = 0.5*(unit_direction.y() + 1.0); 53 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 54 | } 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | And that yields this picture: 58 | 59 | ![Image 5-1](../assets/img05-1.jpg) 60 | 61 | Now, how about several spheres? While it is tempting to have an array of spheres, a very clean 62 | solution is the make an “abstract class” for anything a ray might hit and make both a sphere and a 63 | list of spheres just something you can hit. What that class should be called is something of a 64 | quandary -- calling it an “object” would be good if not for “object oriented” programming. “Surface” 65 | is often used, with the weakness being maybe we will want volumes. “Hitable” emphasizes the member 66 | function that unites them. I don’t love any of these but I will go with “hitable”. 67 | 68 | This `hitable` abstract class will have a hit function that takes in a ray. Most ray tracers have 69 | found it convenient to add a valid interval for hits $t_{min}$ to $t_{max}$, so the hit only 70 | “counts” if $t_{min} < t < t_{max}$. For the initial rays this is positive $t$, but as we will see, 71 | it can help some details in the code to have an interval $t_{min}$ to $t_{max}$. One design question 72 | is whether to do things like compute the normal if we hit something. We might end up hitting 73 | something closer as we do our search, and we will only need the normal of the closest thing. I will 74 | go with the simple solution and compute a bundle of stuff I will store in some structure. I know 75 | we’ll want motion blur at some point, so I’ll add a time input variable. Here’s the abstract class: 76 | 77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 78 | #ifndef HITABLEH 79 | #define HITABLEH 80 | 81 | #include "ray.h" 82 | 83 | struct hit_record 84 | { 85 | float t; 86 | vec3 p; 87 | vec3 normal; 88 | }; 89 | 90 | class hitable { 91 | public: 92 | virtual bool hit( 93 | const ray& r, float t_min, float t_max, hit_record& rec) const = 0; 94 | }; 95 | 96 | #endif 97 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 | 99 | And here’s the sphere (note that I eliminated a bunch of redundant 2’s that cancel each other out): 100 | 101 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 102 | #ifndef SPHEREH 103 | #define SPHEREH 104 | 105 | #include "hitable.h" 106 | 107 | class sphere: public hitable { 108 | public: 109 | sphere() {} 110 | sphere(vec3 cen, float r) : center(cen), radius(r) {}; 111 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const; 112 | vec3 center; 113 | float radius; 114 | }; 115 | 116 | bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const { 117 | vec3 oc = r.origin() - center; 118 | float a = dot(r.direction(), r.direction()); 119 | float b = dot(oc, r.direction()); 120 | float c = dot(oc, oc) - radius*radius; 121 | float discriminant = b*b - a*c; 122 | if (discriminant > 0) { 123 | float temp = (-b - sqrt(discriminant))/a; 124 | if (temp < t_max && temp > t_min) { 125 | rec.t = temp; 126 | rec.p = r.point_at_parameter(rec.t); 127 | rec.normal = (rec.p - center) / radius; 128 | return true; 129 | } 130 | temp = (-b + sqrt(discriminant)) / a; 131 | if (temp < t_max && temp > t_min) { 132 | rec.t = temp; 133 | rec.p = r.point_at_parameter(rec.t); 134 | rec.normal = (rec.p - center) / radius; 135 | return true; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | 142 | #endif 143 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 144 | 145 | And a list of objects: 146 | 147 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 148 | #ifndef HITABLELISTH 149 | #define HITABLELISTH 150 | 151 | #include "hitable.h" 152 | 153 | class hitable_list: public hitable { 154 | public: 155 | hitable_list() {} 156 | hitable_list(hitable **l, int n) {list = l; list_size = n; } 157 | virtual bool hit( 158 | const ray& r, float tmin, float tmax, hit_record& rec) const; 159 | hitable **list; 160 | int list_size; 161 | }; 162 | 163 | bool hitable_list::hit( 164 | const ray& r, float t_min, float t_max, hit_record& rec) const { 165 | 166 | hit_record temp_rec; 167 | bool hit_anything = false; 168 | double closest_so_far = t_max; 169 | for (int i = 0; i < list_size; i++) { 170 | if (list[i]->hit(r, t_min, closest_so_far, temp_rec)) { 171 | hit_anything = true; 172 | closest_so_far = temp_rec.t; 173 | rec = temp_rec; 174 | } 175 | } 176 | return hit_anything; 177 | } 178 | 179 | #endif 180 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 181 | 182 | And the new main: 183 | 184 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 185 | #include 186 | #include "sphere.h" 187 | #include "hitable_list.h" 188 | #include "float.h" 189 | 190 | vec3 color(const ray& r, hitable *world) { 191 | hit_record rec; 192 | if (world->hit(r, 0.0, MAXFLOAT, rec)) { 193 | return 0.5*vec3(rec.normal.x()+1, rec.normal.y()+1, rec.normal.z()+1); 194 | } 195 | else { 196 | vec3 unit_direction = unit_vector(r.direction()); 197 | float t = 0.5*(unit_direction.y() + 1.0); 198 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 199 | } 200 | } 201 | 202 | int main() { 203 | int nx = 200; 204 | int ny = 100; 205 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 206 | vec3 lower_left_corner(-2.0, -1.0, -1.0); 207 | vec3 horizontal(4.0, 0.0, 0.0); 208 | vec3 vertical(0.0, 2.0, 0.0); 209 | vec3 origin(0.0, 0.0, 0.0); 210 | hitable *list[2]; 211 | list[0] = new sphere(vec3(0,0,-1), 0.5); 212 | list[1] = new sphere(vec3(0,-100.5,-1), 100); 213 | hitable *world = new hitable_list(list,2); 214 | for (int j = ny-1; j >= 0; j--) { 215 | for (int i = 0; i < nx; i++) { 216 | float u = float(i) / float(nx); 217 | float v = float(j) / float(ny); 218 | ray r(origin, lower_left_corner + u*horizontal + v*vertical); 219 | 220 | vec3 p = r.point_at_parameter(2.0); 221 | vec3 col = color(r, world); 222 | int ir = int(255.99*col[0]); 223 | int ig = int(255.99*col[1]); 224 | int ib = int(255.99*col[2]); 225 | 226 | std::cout << ir << " " << ig << " " << ib << "\n"; 227 | } 228 | } 229 | } 230 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 231 | 232 | This yields a picture that is really just a visualization of where the spheres are along with their 233 | surface normal. This is often a great way to look at your model for flaws and characteristics. 234 | 235 | ![Image 5-2](../assets/img05-2.jpg) 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /book/ch06.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 6: Antialiasing** 12 | 13 | When a real camera takes a picture, there are usually no jaggies along edges because the edge pixels 14 | are a blend of some foreground and some background. We can get the same effect by averaging a bunch 15 | of samples inside each pixel. We will not bother with stratification, which is controversial but is 16 | usual for my programs. For some ray tracers it is critical, but the kind of general one we are 17 | writing doesn’t benefit very much from it and it makes the code uglier. We abstract the camera class 18 | a bit so we can make a cooler camera later. 19 | 20 | One thing we need is a random number generator that returns real random numbers. We need a function 21 | that returns a canonical random number which by convention returns random real in the range 22 | $0 ≤ ran < 1$. The “less than” before the 1 is important as we will sometimes take advantage of that. 23 | 24 | A simple approach to this is to use the `rand()` function that can be found in ``. This 25 | function returns a random integer in the range 0 and RANDMAX. Hence we can get a real random number 26 | as desired with the following code snippet: 27 | 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 29 | #ifndef RANDOMH 30 | #define RANDOMH 31 | 32 | #include 33 | 34 | inline double random_double() { 35 | return rand() / (RAND_MAX + 1.0); 36 | } 37 | #endif 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | C++ did not traditionally have a standard random number generator, but newer versions of C++ have 41 | addressed this issue with the `` header (if imperfectly according to some experts). 42 | If you want to use this, you can obtain a random number with the conditions we need as follows: 43 | 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 45 | #ifndef RANDOMH 46 | #define RANDOMH 47 | 48 | #include 49 | #include 50 | 51 | inline double random_double() { 52 | static std::uniform_real_distribution distribution(0.0, 1.0); 53 | static std::mt19937 generator; 54 | static std::function rand_generator = 55 | std::bind(distribution, generator); 56 | return rand_generator(); 57 | } 58 | #endif 59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | 61 | For a given pixel we have several samples within that pixel and send rays through each of the 62 | samples. The colors of these rays are then averaged: 63 | 64 | ![Figure 6-1](../assets/fig06-1.jpg) 65 | 66 | Putting that all together yields a camera class encapsulating our simple axis-aligned camera from 67 | before: 68 | 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 70 | #ifndef CAMERAH 71 | #define CAMERAH 72 | 73 | #include "ray.h" 74 | 75 | class camera { 76 | public: 77 | camera() { 78 | lower_left_corner = vec3(-2.0, -1.0, -1.0); 79 | horizontal = vec3(4.0, 0.0, 0.0); 80 | vertical = vec3(0.0, 2.0, 0.0); 81 | origin = vec3(0.0, 0.0, 0.0); 82 | } 83 | ray get_ray(float u, float v) { 84 | return ray(origin, 85 | lower_left_corner + u*horizontal + v*vertical - origin); 86 | } 87 | 88 | vec3 origin; 89 | vec3 lower_left_corner; 90 | vec3 horizontal; 91 | vec3 vertical; 92 | }; 93 | #endif 94 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 | 96 | Main is also changed: 97 | 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 99 | int main() { 100 | int nx = 200; 101 | int ny = 100; 102 | int ns = 100; 103 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 104 | hitable *list[2]; 105 | list[0] = new sphere(vec3(0,0,-1), 0.5); 106 | list[1] = new sphere(vec3(0,-100.5,-1), 100); 107 | hitable *world = new hitable_list(list,2); 108 | camera cam; 109 | for (int j = ny-1; j >= 0; j--) { 110 | for (int i = 0; i < nx; i++) { 111 | vec3 col(0, 0, 0); 112 | for (int s=0; s < ns; s++) { 113 | float u = float(i + random_double()) / float(nx); 114 | float v = float(j + random_double()) / float(ny); 115 | ray r = cam.get_ray(u, v); 116 | col += color(r, world); 117 | } 118 | col /= float(ns); 119 | int ir = int(255.99*col[0]); 120 | int ig = int(255.99*col[1]); 121 | int ib = int(255.99*col[2]); 122 | std::cout << ir << " " << ig << " " << ib << "\n"; 123 | } 124 | } 125 | } 126 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 | 128 | Zooming into the image that is produced, the big change is in edge pixels that are part background 129 | and part foreground: 130 | 131 | ![Image 6-1](../assets/img06-1.jpg) 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /book/ch07.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 7: Diffuse Materials** 12 | 13 | Now that we have objects and multiple rays per pixel, we can make some realistic looking materials. 14 | We’ll start with diffuse (matte) materials. One question is whether we can mix and match shapes and 15 | materials (so we assign a sphere a material) or if it’s put together so the geometry and material 16 | are tightly bound (that could be useful for procedural objects where the geometry and material are 17 | linked). We’ll go with separate -- which is usual in most renderers -- but do be aware of the 18 | limitation. 19 | 20 | Diffuse objects that don’t emit light merely take on the color of their surroundings, but they 21 | modulate that with their own intrinsic color. Light that reflects off a diffuse surface has its 22 | direction randomized. So, if we send three rays into a crack between two diffuse surfaces they will 23 | each have different random behavior: 24 | 25 | ![Figure 7-1](../assets/fig07-1.jpg) 26 | 27 | They also might be absorbed rather than reflected. The darker the surface, the more likely 28 | absorption is. (That’s why it is dark!) Really any algorithm that randomizes direction will produce 29 | surfaces that look matte. One of the simplest ways to do this turns out to be exactly correct for 30 | ideal diffuse surfaces. (I used to do it as a lazy hack that approximates mathematically ideal 31 | Lambertian.) 32 | 33 | Pick a random point s from the unit radius sphere that is tangent to the hitpoint, and send a ray 34 | from the hitpoint $p$ to the random point $s$. That sphere has center $(p + N)$: 35 | 36 | ![Figure 7-2](../assets/fig07-2.jpg) 37 | 38 | We also need a way to pick a random point in a unit radius sphere centered at the origin. We’ll use 39 | what is usually the easiest algorithm: a rejection method. First, we pick a random point in the unit 40 | cube where x, y, and z all range from -1 to +1. We reject this point and try again if the point is 41 | outside the sphere. A do/while construct is perfect for that: 42 | 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 44 | vec3 random_in_unit_sphere() { 45 | vec3 p; 46 | do { 47 | p = 2.0*vec3(random_double(), random_double(), random_double()) - vec3(1,1,1); 48 | } while (p.squared_length() >= 1.0); 49 | return p; 50 | } 51 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 54 | vec3 color(const ray& r, hitable *world, int depth) { 55 | hit_record rec; 56 | if (world->hit(r, 0.0, MAXFLOAT, rec)) { 57 | vec3 target = rec.p + rec.normal + random_in_unit_sphere(); 58 | return 0.5 * color(ray(rec.p, target - rec.p), world); 59 | } 60 | else { 61 | vec3 unit_direction = unit_vector(r.direction()); 62 | float t = 0.5*(unit_direction.y() + 1.0); 63 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 64 | } 65 | } 66 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67 | 68 | This gives us: 69 | 70 | ![Image 7-1](../assets/img07-1.jpg) 71 | 72 | Note the shadowing under the sphere. This picture is very dark, but our spheres only absorb half the 73 | energy on each bounce, so they are 50% reflectors. If you can’t see the shadow, don’t worry, we will 74 | fix that now. These spheres should look pretty light (in real life, a light grey). The reason for 75 | this is that almost all image viewers assume that the image is “gamma corrected”, meaning the 0 to 1 76 | values have some transform before being stored as a byte. There are many good reasons for that, but 77 | for our purposes we just need to be aware of it. To a first approximation, we can use “gamma 2” 78 | which means raising the color to the power $1/gamma$, or in our simple case ½, which is just 79 | square-root: 80 | 81 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 82 | col /= float(ns); 83 | col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) ); 84 | int ir = int(255.99*col[0]); 85 | int ig = int(255.99*col[1]); 86 | int ib = int(255.99*col[2]); 87 | std::cout << ir << " " << ig << " " << ib << "\n"; 88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 | 90 | That yields light grey, as we desire: 91 | 92 | ![Image 7-2](../assets/img07-2.jpg) 93 | 94 | There’s also a subtle bug in there. Some of the reflected rays hit the object they are reflecting 95 | off of not at exactly $t=0$, but instead at $t=-0.0000001$ or $t=0.00000001$ or whatever floating 96 | point approximation the sphere intersector gives us. So we need to ignore hits very near zero: 97 | 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 99 | if (world->hit(r, 0.001, MAXFLOAT, rec)) { 100 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | 102 | This gets rid of the shadow acne problem. Yes it is really called that. 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /book/ch08.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 8: Metal** 12 | 13 | If we want different objects to have different materials, we have a design decision. We could have a 14 | universal material with lots of parameters and different material types just zero out some of those 15 | parameters. This is not a bad approach. Or we could have an abstract material class that 16 | encapsulates behavior. I am a fan of the latter approach. For our program the material needs to do 17 | two things: 18 | 19 | 1. Produce a scattered ray (or say it absorbed the incident ray). 20 | 2. If scattered, say how much the ray should be attenuated. 21 | 22 | This suggests the abstract class: 23 | 24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 25 | class material { 26 | public: 27 | virtual bool scatter( 28 | const ray& r_in, const hit_record& rec, vec3& attenuation, 29 | ray& scattered) const = 0; 30 | }; 31 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 | 33 | The `hit_record` is to avoid a bunch of arguments so we can stuff whatever info we want in there. 34 | You can use arguments instead; it’s a matter of taste. Hitables and materials need to know each 35 | other so there is some circularity of the references. In C++ you just need to alert the compiler 36 | that the pointer is to a class, which the “class material” in the hitable class below does: 37 | 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 39 | #ifndef HITABLEH 40 | #define HITABLEH 41 | #include "ray.h" 42 | 43 | class material; 44 | 45 | struct hit_record 46 | { 47 | float t; 48 | vec3 p; 49 | vec3 normal; 50 | material *mat_ptr; 51 | }; 52 | 53 | class hitable { 54 | public: 55 | virtual bool hit( 56 | const ray& r, float t_min, float t_max, hit_record& rec) const = 0; 57 | }; 58 | 59 | #endif 60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | What we have set up here is that material will tell us how rays interact with the surface. 63 | `hit_record` is just a way to stuff a bunch of arguments into a struct so we can send them as a 64 | group. When a ray hits a surface (a particular sphere for example), the material pointer in the 65 | `hit_record` will be set to point at the material pointer the sphere was given when it was set up in 66 | `main()` when we start. When the `color()` routine gets the `hit_record` it can call member 67 | functions of the material pointer to find out what ray, if any, is scattered. 68 | 69 | To achieve this, we must have a reference to the material for our sphere class to returned 70 | within `hit_record`. See the lines below marked with **`/* NEW */`**. 71 | 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 73 | class sphere: public hitable { 74 | public: 75 | sphere() {} 76 | sphere(vec3 cen, float r, material *m) 77 | : center(cen), radius(r), mat_ptr(m) {}; 78 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const; 79 | vec3 center; 80 | float radius; 81 | material *mat_ptr; /* NEW */ 82 | }; 83 | 84 | bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const { 85 | vec3 oc = r.origin() - center; 86 | float a = dot(r.direction(), r.direction()); 87 | float b = dot(oc, r.direction()); 88 | float c = dot(oc, oc) - radius*radius; 89 | float discriminant = b*b - a*c; 90 | if (discriminant > 0) { 91 | float temp = (-b - sqrt(discriminant))/a; 92 | if (temp < t_max && temp > t_min) { 93 | rec.t = temp; 94 | rec.p = r.point_at_parameter(rec.t); 95 | rec.normal = (rec.p - center) / radius; 96 | rec.mat_ptr = mat_ptr; /* NEW */ 97 | return true; 98 | } 99 | temp = (-b + sqrt(discriminant)) / a; 100 | if (temp < t_max && temp > t_min) { 101 | rec.t = temp; 102 | rec.p = r.point_at_parameter(rec.t); 103 | rec.normal = (rec.p - center) / radius; 104 | rec.mat_ptr = mat_ptr; /* NEW */ 105 | return true; 106 | } 107 | } 108 | return false; 109 | } 110 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 111 | 112 | For the Lambertian (diffuse) case we already have, it can either scatter always and attenuate by its 113 | reflectance $R$, or it can scatter with no attenuation but absorb the fraction $1-R$ of the rays. Or 114 | it could be a mixture of those strategies. For Lambertian materials we get this simple class: 115 | 116 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 117 | class lambertian : public material { 118 | public: 119 | lambertian(const vec3& a) : albedo(a) {} 120 | virtual bool scatter(const ray& r_in, const hit_record& rec, 121 | vec3& attenuation, ray& scattered) const 122 | { 123 | vec3 target = rec.p + rec.normal + random_in_unit_sphere(); 124 | scattered = ray(rec.p, target-rec.p); 125 | attenuation = albedo; 126 | return true; 127 | } 128 | 129 | vec3 albedo; 130 | }; 131 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 132 | 133 | Note we could just as well only scatter with some probability $p$ and have attenuation be 134 | $albedo/p$. Your choice. 135 | 136 | For smooth metals the ray won’t be randomly scattered. The key math is: how does a ray get 137 | reflected from a metal mirror? Vector math is our friend here: 138 | 139 | ![Figure 8-1](../assets/fig08-1.jpg) 140 | 141 | The reflected ray direction in red is just $(v + 2B)$. In our design, $N$ is a unit vector, but $v$ 142 | may not be. The length of $B$ should be $dot(v,N)$. Because $v$ points in, we will need a minus 143 | sign, yielding: 144 | 145 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 146 | vec3 reflect(const vec3& v, const vec3& n) { 147 | return v - 2*dot(v,n)*n; 148 | } 149 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 150 | 151 | The metal material just reflects rays using that formula: 152 | 153 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 154 | class metal : public material { 155 | public: 156 | metal(const vec3& a) : albedo(a) {} 157 | virtual bool scatter(const ray& r_in, const hit_record& rec, 158 | vec3& attenuation, ray& scattered) const 159 | { 160 | vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); 161 | scattered = ray(rec.p, reflected); 162 | attenuation = albedo; 163 | return (dot(scattered.direction(), rec.normal) > 0); 164 | } 165 | vec3 albedo; 166 | }; 167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 168 | 169 | We need to modify the color function to use this: 170 | 171 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 172 | vec3 color(const ray& r, hitable *world, int depth) { 173 | hit_record rec; 174 | if (world->hit(r, 0.001, MAXFLOAT, rec)) { 175 | ray scattered; 176 | vec3 attenuation; 177 | if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered)) { 178 | return attenuation*color(scattered, world, depth+1); 179 | } 180 | else { 181 | return vec3(0,0,0); 182 | } 183 | } 184 | else { 185 | vec3 unit_direction = unit_vector(r.direction()); 186 | float t = 0.5*(unit_direction.y() + 1.0); 187 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 188 | } 189 | } 190 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 191 | 192 | You will also need to modify the sphere class to have a material pointer to it. And add some 193 | metal spheres: 194 | 195 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 196 | int main() { 197 | int nx = 200; 198 | int ny = 100; 199 | int ns = 100; 200 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 201 | hitable *list[4]; 202 | list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.1, 0.2, 0.5))); 203 | list[1] = new sphere(vec3(0,-100.5,-1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); 204 | list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2), 0.0)); 205 | list[3] = new sphere(vec3(-1,0,-1), 0.5, new metal(vec3(0.8, 0.8, 0.8))); 206 | hitable *world = new hitable_list(list,4); 207 | camera cam; 208 | for (int j = ny-1; j >= 0; j--) { 209 | for (int i = 0; i < nx; i++) { 210 | vec3 col(0, 0, 0); 211 | for (int s=0; s < ns; s++) { 212 | float u = float(i + random_double()) / float(nx); 213 | float v = float(j + random_double()) / float(ny); 214 | ray r = cam.get_ray(u, v); 215 | vec3 p = r.point_at_parameter(2.0); 216 | col += color(r, world,0); 217 | } 218 | col /= float(ns); 219 | col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) ); 220 | int ir = int(255.99*col[0]); 221 | int ig = int(255.99*col[1]); 222 | int ib = int(255.99*col[2]); 223 | std::cout << ir << " " << ig << " " << ib << "\n"; 224 | } 225 | } 226 | } 227 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 228 | 229 | Which gives: 230 | 231 | ![Image 8-1](../assets/img08-1.jpg) 232 | 233 | We can also randomize the reflected direction by using a small sphere and choosing a new endpoint 234 | for the ray: 235 | 236 | ![Figure 8-2](../assets/fig08-2.jpg) 237 | 238 | The bigger the sphere, the fuzzier the reflections will be. This suggests adding a fuzziness 239 | parameter that is just the radius of the sphere (so zero is no perturbation). The catch is that for 240 | big spheres or grazing rays, we may scatter below the surface. We can just have the surface 241 | absorb those. We’ll put a maximum of 1 on the radius of the sphere which yields: 242 | 243 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 244 | class metal : public material { 245 | public: 246 | metal(const vec3& a, float f) : albedo(a) { 247 | if (f < 1) fuzz = f; else fuzz = 1; 248 | } 249 | 250 | virtual bool scatter(const ray& r_in, const hit_record& rec, 251 | vec3& attenuation, ray& scattered) const 252 | { 253 | vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); 254 | scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere()); 255 | attenuation = albedo; 256 | return (dot(scattered.direction(), rec.normal) > 0); 257 | } 258 | vec3 albedo; 259 | float fuzz; 260 | }; 261 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 262 | 263 | We can try that out by adding fuzziness 0.3 and 1.0 to the metals: 264 | 265 | ![Image 8-2](../assets/img08-2.jpg) 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /book/ch09.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 9: Dielectrics** 12 | 13 | Clear materials such as water, glass, and diamonds are dielectrics. When a light ray hits them, it 14 | splits into a reflected ray and a refracted (transmitted) ray. We’ll handle that by randomly 15 | choosing between reflection or refraction and only generating one scattered ray per interaction. 16 | 17 | The hardest part to debug is the refracted ray. I usually first just have all the light refract if 18 | there is a refraction ray at all. For this project, I tried to put two glass balls in our scene, and 19 | I got this (I have not told you how to do this right or wrong yet, but soon!): 20 | 21 | ![Image 9-1](../assets/img09-1.jpg) 22 | 23 | Is that right? Glass balls look odd in real life. But no, it isn’t right. The world should be 24 | flipped upside down and no weird black stuff. I just printed out the ray straight through the middle 25 | of the image and it was clearly wrong. That often does the job. 26 | 27 | The refraction is described by Snell’s law: 28 | 29 | $$ n \cdot sin(theta) = n' \cdot sin(theta') $$ 30 | 31 | Where $n$ and $n'$ are the refractive indices (typically air = 1, glass = 1.3–1.7, diamond = 2.4) 32 | and the geometry is: 33 | 34 | ![Figure 9-1](../assets/fig09-1.jpg) 35 | 36 | One troublesome practical issue is that when the ray is in the material with the higher refractive 37 | index, there is no real solution to Snell’s law and thus there is no refraction possible. Here all 38 | the light is reflected, and because in practice that is usually inside solid objects, it is called 39 | “total internal reflection”. This is why sometimes the water-air boundary acts as a perfect mirror 40 | when you are submerged. The code for refraction is thus a bit more complicated than for reflection: 41 | 42 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 43 | bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) { 44 | vec3 uv = unit_vector(v); 45 | float dt = dot(uv, n); 46 | float discriminant = 1.0 - ni_over_nt*ni_over_nt*(1-dt*dt); 47 | if (discriminant > 0) { 48 | refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminant); 49 | return true; 50 | } 51 | else 52 | return false; 53 | } 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | And the dielectric material that always refracts when possible is: 57 | 58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 59 | class dielectric : public material { 60 | public: 61 | dielectric(float ri) : ref_idx(ri) {} 62 | virtual bool scatter(const ray& r_in, const hit_record& rec, 63 | vec3& attenuation, ray& scattered) const 64 | { 65 | vec3 outward_normal; 66 | vec3 reflected = reflect(r_in.direction(), rec.normal); 67 | float ni_over_nt; 68 | attenuation = vec3(1.0, 1.0, 0.0); 69 | vec3 refracted; 70 | if (dot(r_in.direction(), rec.normal) > 0) { 71 | outward_normal = -rec.normal; 72 | ni_over_nt = ref_idx; 73 | } 74 | else { 75 | outward_normal = rec.normal; 76 | ni_over_nt = 1.0 / ref_idx; 77 | } 78 | if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted)) 79 | scattered = ray(rec.p, refracted); 80 | else { 81 | scattered = ray(rec.p, reflected); 82 | return false; 83 | } 84 | return true; 85 | } 86 | 87 | float ref_idx; 88 | }; 89 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 90 | 91 | Attenuation is always 1 -- the glass surface absorbs nothing. The `attenuation = vec3(1.0, 1.0, 92 | 0.0)` above will also kill the blue channel which is the type of color bug that can be hard to find 93 | -- it will give a color shift only. Try it the way it is and then change it to 94 | `attenuation = vec3(1.0, 1.0, 1.0)` to see the difference. 95 | 96 | If we try that out with these parameters: 97 | 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 99 | list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.1, 0.2, 0.5))); 100 | list[1] = new sphere(vec3(0,-100.5,-1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); 101 | list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2), 0.0)); 102 | list[3] = new sphere(vec3(-1,0,-1), 0.5, new dielectric(1.5)); 103 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | 105 | We get: 106 | 107 | ![Image 9-2](../assets/img09-2.jpg) 108 | 109 | (The reader Becker has pointed out that when there is a reflection ray the function returns `false` 110 | so there are no reflections. He is right, and that is why there are none in the image above. I 111 | am leaving this in rather than correcting this because it is a very interesting example of a major 112 | bug that still leaves a reasonably plausible image. These sleeper bugs are the hardest bugs to 113 | find because we humans are not designed to find fault with what we see.) 114 | 115 | Now real glass has reflectivity that varies with angle -- look at a window at a steep angle and it 116 | becomes a mirror. There is a big ugly equation for that, but almost everybody uses a simple and 117 | surprisingly simple polynomial approximation by Christophe Schlick: 118 | 119 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 120 | float schlick(float cosine, float ref_idx) { 121 | float r0 = (1-ref_idx) / (1+ref_idx); 122 | r0 = r0*r0; 123 | return r0 + (1-r0)*pow((1 - cosine),5); 124 | } 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 126 | 127 | This yields our full glass material: 128 | 129 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 130 | class dielectric : public material { 131 | public: 132 | dielectric(float ri) : ref_idx(ri) {} 133 | virtual bool scatter( 134 | const ray& r_in, const hit_record& rec, vec3& attenuation, 135 | ray& scattered) const 136 | { 137 | vec3 outward_normal; 138 | vec3 reflected = reflect(r_in.direction(), rec.normal); 139 | float ni_over_nt; 140 | attenuation = vec3(1.0, 1.0, 1.0); 141 | vec3 refracted; 142 | float reflect_prob; 143 | float cosine; 144 | if (dot(r_in.direction(), rec.normal) > 0) { 145 | outward_normal = -rec.normal; 146 | ni_over_nt = ref_idx; 147 | cosine = ref_idx * dot(r_in.direction(), rec.normal) 148 | / r_in.direction().length(); 149 | } 150 | else { 151 | outward_normal = rec.normal; 152 | ni_over_nt = 1.0 / ref_idx; 153 | cosine = -dot(r_in.direction(), rec.normal) 154 | / r_in.direction().length(); 155 | } 156 | if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted)) { 157 | reflect_prob = schlick(cosine, ref_idx); 158 | } 159 | else { 160 | reflect_prob = 1.0; 161 | } 162 | if (random_double() < reflect_prob) { 163 | scattered = ray(rec.p, reflected); 164 | } 165 | else { 166 | scattered = ray(rec.p, refracted); 167 | } 168 | return true; 169 | } 170 | 171 | float ref_idx; 172 | }; 173 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 174 | 175 | An interesting and easy trick with dielectric spheres is to note that if you use a negative radius, 176 | the geometry is unaffected but the surface normal points inward, so it can be used as a bubble 177 | to make a hollow glass sphere: 178 | 179 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 180 | list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.1, 0.2, 0.5))); 181 | list[1] = new sphere(vec3(0,-100.5,-1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); 182 | list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2))); 183 | list[3] = new sphere(vec3(-1,0,-1), 0.5, new dielectric(1.5)); 184 | list[4] = new sphere(vec3(-1,0,-1), -0.45, new dielectric(1.5)); 185 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 186 | 187 | This gives: 188 | 189 | ![Image 9-3](../assets/img09-3.jpg) 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /book/ch10.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 10: Positionable camera** 12 | 13 | Cameras, like dielectrics, are a pain to debug. So I always develop mine incrementally. First, let’s 14 | allow an adjustable field of view (_fov_). This is the angle you see through the portal. Since our 15 | image is not square, the fov is different horizontally and vertically. I always use vertical fov. I 16 | also usually specify it in degrees and change to radians inside a constructor -- a matter of 17 | personal taste. 18 | 19 | I first keep the rays coming from the origin and heading to the $z = -1$ plane. We could make it the 20 | $z = -2$ plane, or whatever, as long as we made $h$ a ratio to that distance. Here is our setup: 21 | 22 | ![Figure 10-1](../assets/fig10-1.jpg) 23 | 24 | This implies $h = tan(\theta/2)$. Our camera now becomes: 25 | 26 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 27 | #ifndef CAMERAH 28 | #define CAMERAH 29 | 30 | #include "ray.h" 31 | 32 | class camera { 33 | public: 34 | camera(float vfov, float aspect) { // vfov is top to bottom in degrees 35 | float theta = vfov*M_PI/180; 36 | float half_height = tan(theta/2); 37 | float half_width = aspect * half_height; 38 | lower_left_corner = vec3(-half_width, -half_height, -1.0); 39 | horizontal = vec3(2*half_width, 0.0, 0.0); 40 | vertical = vec3(0.0, 2*half_height, 0.0); 41 | origin = vec3(0.0, 0.0, 0.0); 42 | } 43 | ray get_ray(float u, float v) { 44 | return ray(origin, 45 | lower_left_corner + u*horizontal + v*vertical - origin); 46 | } 47 | 48 | vec3 origin; 49 | vec3 lower_left_corner; 50 | vec3 horizontal; 51 | vec3 vertical; 52 | }; 53 | #endif 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | When calling it with camera `cam(90, float(nx)/float(ny))` and these spheres: 57 | 58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 59 | float R = cos(M_PI/4); 60 | list[0] = new sphere(vec3(-R,0,-1), R, new lambertian(vec3(0, 0, 1))); 61 | list[1] = new sphere(vec3( R,0,-1), R, new lambertian(vec3(1, 0, 0))); 62 | hitable *world = new hitable_list(list,2); 63 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 64 | 65 | gives: 66 | 67 | ![Image 10-1](../assets/img10-1.jpg) 68 | 69 | To get an arbitrary viewpoint, let’s first name the points we care about. We’ll call the position 70 | where we place the camera _lookfrom_, and the point we look at _lookat_. (Later, if you want, you 71 | could define a direction to look in instead of a point to look at.) 72 | 73 | We also need a way to specify the roll, or sideways tilt, of the camera; the rotation around the 74 | lookat-lookfrom axis. Another way to think about it is even if you keep `lookfrom` and `lookat` 75 | constant, you can still rotate your head around your nose. What we need is a way to specify an up 76 | vector for the camera. Notice we already we already have a plane that the up vector should be in, 77 | the plane orthogonal to the view direction. 78 | 79 | ![Figure 10-2](../assets/fig10-2.jpg) 80 | 81 | We can actually use any up vector we want, and simply project it onto this plane to get an up vector 82 | for the camera. I use the common convention of naming a “view up” (_vup_) vector. A couple of cross 83 | products, and we now have a complete orthonormal basis (u,v,w) to describe our camera’s orientation. 84 | 85 | ![Figure 10-3](../assets/fig10-3.jpg) 86 | 87 | Remember that `vup`, `v`, and `w` are all in the same plane. Note that, like before when our fixed 88 | camera faced -Z, our arbitrary view camera faces -w. And keep in mind that we can -- but we don’t 89 | have to -- use world up (0,1,0) to specify vup. This is convenient and will naturally keep your 90 | camera horizontally level until you decide to experiment with crazy camera angles. 91 | 92 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 93 | #ifndef CAMERAH 94 | #define CAMERAH 95 | 96 | #include "ray.h" 97 | 98 | class camera { 99 | public: 100 | camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect) { 101 | // vfov is top to bottom in degrees 102 | vec3 u, v, w; 103 | float theta = vfov*M_PI/180; 104 | float half_height = tan(theta/2); 105 | float half_width = aspect * half_height; 106 | origin = lookatfrom; 107 | w = unit_vector(lookfrom - lookat); 108 | u = unit_vector(cross(vup, w)); 109 | v = cross(w, u); 110 | lower_left_corner = origin - half_width*u - half_height*v - w; 111 | horizontal = 2*half_width*u; 112 | vertical = 2*half_height*v; 113 | } 114 | ray get_ray(float s, float t) { 115 | return ray(origin, 116 | lower_left_corner + s*horizontal + t*vertical - origin); 117 | } 118 | 119 | vec3 origin; 120 | vec3 lower_left_corner; 121 | vec3 horizontal; 122 | vec3 vertical; 123 | }; 124 | #endif 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 126 | 127 | This allows us to change the viewpoint: 128 | 129 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 130 | camera cam(vec3(-2,2,1), vec3(0,0,-1), vec3(0,1,0), 90, float(nx)/float(ny)); 131 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 132 | 133 | to get: 134 | 135 | ![Image 10-2](../assets/img10-2.jpg) 136 | 137 | And we can change field of view to get: 138 | 139 | ![Image 10-3](../assets/img10-3.jpg) 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /book/ch11.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 11: Defocus Blur** 12 | 13 | Now our final feature: defocus blur. Note, all photographers will call it “depth of field” so be 14 | aware of only using “defocus blur” among friends. 15 | 16 | The reason we defocus blur in real cameras is because they need a big hole (rather than just a 17 | pinhole) to gather light. This would defocus everything, but if we stick a lens in the hole, there 18 | will be a certain distance where everything is in focus. The distance to that plane where things are 19 | in focus is controlled by the distance between the lens and the film/sensor. That is why you see the 20 | lens move relative to the camera when you change what is in focus (that may happen in your phone 21 | camera too, but the sensor moves). The “aperture” is a hole to control how big the lens is 22 | effectively. For a real camera, if you need more light you make the aperture bigger, and will get 23 | more defocus blur. For our virtual camera, we can have a perfect sensor and never need more light, 24 | so we only have an aperture when we want defocus blur. 25 | 26 | A real camera has a compound lens that is complicated. For our code we could simulate the order: 27 | sensor, then lens, then aperture, and figure out where to send the rays and flip the image once 28 | computed (the image is projected upside down on the film). Graphics people usually use a thin lens 29 | approximation. 30 | 31 | ![Figure 11-1](../assets/fig11-1.jpg) 32 | 33 | We also don’t need to simulate any of the inside of the camera. For the purposes of rendering an 34 | image outside the camera, that would be unnecessary complexity. Instead I usually start rays from 35 | the surface of the lens, and send them toward a virtual film plane, by finding the projection of the 36 | film on the plane that is in focus (at the distance `focus_dist`). 37 | 38 | ![Figure 11-2](../assets/fig11-2.jpg) 39 | 40 | For that we just need to have the ray origins be on a disk around `lookfrom` rather than from a 41 | point: 42 | 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 44 | #ifndef CAMERAH 45 | #define CAMERAH 46 | 47 | #include "random.h" 48 | #include "ray.h" 49 | 50 | vec3 random_in_unit_disk() { 51 | vec3 p; 52 | do { 53 | p = 2.0*vec3(random_double(),random_double(),0) - vec3(1,1,0); 54 | } while (dot(p,p) >= 1.0); 55 | return p; 56 | } 57 | 58 | class camera { 59 | public: 60 | camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, 61 | float aperture, float focus_dist) 62 | { 63 | lens_radius = aperture / 2; 64 | float theta = vfov*M_PI/180; 65 | float half_height = tan(theta/2); 66 | float half_width = aspect * half_height; 67 | origin = lookfrom; 68 | w = unit_vector(lookfrom - lookat); 69 | u = unit_vector(cross(vup, w)); 70 | v = cross(w, u); 71 | lower_left_corner = origin 72 | - half_width * focus_dist * u 73 | - half_height * focus_dist * v 74 | - focus_dist * w; 75 | horizontal = 2*half_width*focus_dist*u; 76 | vertical = 2*half_height*focus_dist*v; 77 | } 78 | ray get_ray(float s, float t) { 79 | vec3 rd = lens_radius*random_in_unit_disk(); 80 | vec3 offset = u * rd.x() + v * rd.y(); 81 | return ray(origin + offset, 82 | lower_left_corner + s*horizontal + t*vertical 83 | - origin - offset); 84 | } 85 | 86 | vec3 origin; 87 | vec3 lower_left_corner; 88 | vec3 horizontal; 89 | vec3 vertical; 90 | vec3 u, v, w; 91 | float lens_radius; 92 | }; 93 | #endif 94 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 | 96 | Using a big aperture: 97 | 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 99 | vec3 lookfrom(3,3,2); 100 | vec3 lookat(0,0,-1); 101 | float dist_to_focus = (lookfrom-lookat).length(); 102 | float aperture = 2.0; 103 | 104 | camera cam(lookfrom, lookat, vec3(0,1,0), 20, 105 | float(nx)/float(ny), aperture, dist_to_focus); 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | We get: 109 | 110 | ![Image 11-1](../assets/img11-1.jpg) 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /book/ch12.md.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Chapter 12: Where next?** 12 | 13 | First let’s make the image on the cover of this book -- lots of random spheres: 14 | 15 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ 16 | hitable *random_scene() { 17 | int n = 500; 18 | hitable **list = new hitable*[n+1]; 19 | list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(vec3(0.5, 0.5, 0.5))); 20 | int i = 1; 21 | for (int a = -11; a < 11; a++) { 22 | for (int b = -11; b < 11; b++) { 23 | float choose_mat = random_double(); 24 | vec3 center(a+0.9*random_double(),0.2,b+0.9*random_double()); 25 | if ((center-vec3(4,0.2,0)).length() > 0.9) { 26 | if (choose_mat < 0.8) { // diffuse 27 | list[i++] = new sphere(center, 0.2, 28 | new lambertian(vec3(random_double()*random_double(), 29 | random_double()*random_double(), 30 | random_double()*random_double()) 31 | ) 32 | ); 33 | } 34 | else if (choose_mat < 0.95) { // metal 35 | list[i++] = new sphere(center, 0.2, 36 | new metal(vec3(0.5*(1 + random_double()), 37 | 0.5*(1 + random_double()), 38 | 0.5*(1 + random_double())), 39 | 0.5*random_double())); 40 | } 41 | else { // glass 42 | list[i++] = new sphere(center, 0.2, new dielectric(1.5)); 43 | } 44 | } 45 | } 46 | } 47 | 48 | list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5)); 49 | list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1))); 50 | list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0)); 51 | 52 | return new hitable_list(list,i); 53 | } 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | This gives: 57 | 58 | ![Image 12-1](../assets/img12-1.jpg) 59 | 60 | An interesting thing you might note is the glass balls don’t really have shadows which makes them 61 | look like they are floating. This is not a bug (you don’t see glass balls much in real life, where 62 | they also look a bit strange and indeed seem to float on cloudy days). A point on the big sphere 63 | under a glass ball still has lots of light hitting it because the sky is re-ordered rather than 64 | blocked. 65 | 66 | You now have a cool ray tracer! What next? 67 | 68 | 1. Lights. You can do this explicitly, by sending shadow rays to lights. Or it can be done 69 | implicitly by making some objects emit light, 70 | 71 | 2. Biasing scattered rays toward them, and then downweighting those rays to cancel out the bias. 72 | Both work. I am in the minority in favoring the latter approach. 73 | 74 | 3. Triangles. Most cool models are in triangle form. The model I/O is the worst and almost 75 | everybody tries to get somebody else’s code to do this. 76 | 77 | 4. Surface textures. This lets you paste images on like wall paper. Pretty easy and a good thing 78 | to do. 79 | 80 | 5. Solid textures. Ken Perlin has his code online. Andrew Kensler has some very cool info at his 81 | blog. 82 | 83 | 6. Volumes and media. Cool stuff and will challenge your software architecture. I favor making 84 | volumes have the hitable interface and probabilistically have intersections based on density. 85 | Your rendering code doesn’t even have to know it has volumes with that method. 86 | 87 | 7. Parallelism. Run $N$ copies of your code on $N$ cores with different random seeds. Average the 88 | $N$ runs. This averaging can also be done hierarchically where $N/2$ pairs can be averaged to 89 | get $N/4$ images, and pairs of those can be averaged. That method of parallelism should extend 90 | well into the thousands of cores with very little coding. 91 | 92 | Have fun, and please send me your cool images! 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

WARNING
Content Under Development 5 |

6 | See release page for latest official PDF version. 7 |

8 | 9 | 10 | 11 | **Book Rough Content** 12 | 13 | - [Acknowledgements](book/ack.md.html) 14 | - [Chapter 0](book/ch00.md.html) 15 | - [Chapter 1](book/ch01.md.html) 16 | - [Chapter 2](book/ch02.md.html) 17 | - [Chapter 3](book/ch03.md.html) 18 | - [Chapter 4](book/ch04.md.html) 19 | - [Chapter 5](book/ch05.md.html) 20 | - [Chapter 6](book/ch06.md.html) 21 | - [Chapter 7](book/ch07.md.html) 22 | - [Chapter 8](book/ch08.md.html) 23 | - [Chapter 9](book/ch09.md.html) 24 | - [Chapter 10](book/ch10.md.html) 25 | - [Chapter 11](book/ch11.md.html) 26 | - [Chapter 12](book/ch12.md.html) 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERAH 2 | #define CAMERAH 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 "ray.h" 15 | #include "random.h" 16 | 17 | vec3 random_in_unit_disk() { 18 | vec3 p; 19 | do { 20 | p = 2.0*vec3(random_double(),random_double(),0) - vec3(1,1,0); 21 | } while (dot(p,p) >= 1.0); 22 | return p; 23 | } 24 | 25 | class camera { 26 | public: 27 | camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist) { 28 | // vfov is top to bottom in degrees 29 | lens_radius = aperture / 2; 30 | float theta = vfov*M_PI/180; 31 | float half_height = tan(theta/2); 32 | float half_width = aspect * half_height; 33 | origin = lookfrom; 34 | w = unit_vector(lookfrom - lookat); 35 | u = unit_vector(cross(vup, w)); 36 | v = cross(w, u); 37 | lower_left_corner = origin - half_width*focus_dist*u -half_height*focus_dist*v - focus_dist*w; 38 | horizontal = 2*half_width*focus_dist*u; 39 | vertical = 2*half_height*focus_dist*v; 40 | } 41 | ray get_ray(float s, float t) { 42 | vec3 rd = lens_radius*random_in_unit_disk(); 43 | vec3 offset = u * rd.x() + v * rd.y(); 44 | return ray(origin + offset, lower_left_corner + s*horizontal + t*vertical - origin - offset); 45 | } 46 | 47 | vec3 origin; 48 | vec3 lower_left_corner; 49 | vec3 horizontal; 50 | vec3 vertical; 51 | vec3 u, v, w; 52 | float lens_radius; 53 | }; 54 | 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/hitable.h: -------------------------------------------------------------------------------- 1 | #ifndef HITABLEH 2 | #define HITABLEH 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 "ray.h" 15 | 16 | class material; 17 | 18 | struct hit_record 19 | { 20 | float t; 21 | vec3 p; 22 | vec3 normal; 23 | material *mat_ptr; 24 | }; 25 | 26 | class hitable { 27 | public: 28 | virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const = 0; 29 | }; 30 | 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/hitable_list.h: -------------------------------------------------------------------------------- 1 | #ifndef HITABLELISTH 2 | #define HITABLELISTH 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 | 16 | class hitable_list: public hitable { 17 | public: 18 | hitable_list() {} 19 | hitable_list(hitable **l, int n) { list = l; list_size = n; } 20 | virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const; 21 | hitable **list; 22 | int list_size; 23 | }; 24 | 25 | bool hitable_list::hit(const ray& r, float t_min, float t_max, hit_record& rec) const { 26 | hit_record temp_rec; 27 | bool hit_anything = false; 28 | double closest_so_far = t_max; 29 | for (int i = 0; i < list_size; i++) { 30 | if (list[i]->hit(r, t_min, closest_so_far, temp_rec)) { 31 | hit_anything = true; 32 | closest_so_far = temp_rec.t; 33 | rec = temp_rec; 34 | } 35 | } 36 | return hit_anything; 37 | } 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /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 "hitable_list.h" 15 | #include "float.h" 16 | #include "camera.h" 17 | #include "material.h" 18 | #include "random.h" 19 | 20 | 21 | vec3 color(const ray& r, hitable *world, int depth) { 22 | hit_record rec; 23 | if (world->hit(r, 0.001, MAXFLOAT, rec)) { 24 | ray scattered; 25 | vec3 attenuation; 26 | if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered)) { 27 | return attenuation*color(scattered, world, depth+1); 28 | } 29 | else { 30 | return vec3(0,0,0); 31 | } 32 | } 33 | else { 34 | vec3 unit_direction = unit_vector(r.direction()); 35 | float t = 0.5*(unit_direction.y() + 1.0); 36 | return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0); 37 | } 38 | } 39 | 40 | 41 | hitable *random_scene() { 42 | int n = 500; 43 | hitable **list = new hitable*[n+1]; 44 | list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(vec3(0.5, 0.5, 0.5))); 45 | int i = 1; 46 | for (int a = -11; a < 11; a++) { 47 | for (int b = -11; b < 11; b++) { 48 | float choose_mat = random_double(); 49 | vec3 center(a+0.9*random_double(),0.2,b+0.9*random_double()); 50 | if ((center-vec3(4,0.2,0)).length() > 0.9) { 51 | if (choose_mat < 0.8) { // diffuse 52 | list[i++] = new sphere( 53 | center, 0.2, 54 | new lambertian(vec3(random_double()*random_double(), 55 | random_double()*random_double(), 56 | random_double()*random_double())) 57 | ); 58 | } 59 | else if (choose_mat < 0.95) { // metal 60 | list[i++] = new sphere( 61 | center, 0.2, 62 | new metal(vec3(0.5*(1 + random_double()), 63 | 0.5*(1 + random_double()), 64 | 0.5*(1 + random_double())), 65 | 0.5*random_double()) 66 | ); 67 | } 68 | else { // glass 69 | list[i++] = new sphere(center, 0.2, new dielectric(1.5)); 70 | } 71 | } 72 | } 73 | } 74 | 75 | list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5)); 76 | list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1))); 77 | list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0)); 78 | 79 | return new hitable_list(list,i); 80 | } 81 | 82 | 83 | int main() { 84 | int nx = 1200; 85 | int ny = 800; 86 | int ns = 10; 87 | std::cout << "P3\n" << nx << " " << ny << "\n255\n"; 88 | hitable *world = random_scene(); 89 | 90 | vec3 lookfrom(13,2,3); 91 | vec3 lookat(0,0,0); 92 | float dist_to_focus = 10.0; 93 | float aperture = 0.1; 94 | 95 | camera cam(lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny), aperture, dist_to_focus); 96 | 97 | for (int j = ny-1; j >= 0; j--) { 98 | for (int i = 0; i < nx; i++) { 99 | vec3 col(0, 0, 0); 100 | for (int s=0; s < ns; s++) { 101 | float u = float(i + random_double()) / float(nx); 102 | float v = float(j + random_double()) / float(ny); 103 | ray r = cam.get_ray(u, v); 104 | col += color(r, world,0); 105 | } 106 | col /= float(ns); 107 | col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) ); 108 | int ir = int(255.99*col[0]); 109 | int ig = int(255.99*col[1]); 110 | int ib = int(255.99*col[2]); 111 | std::cout << ir << " " << ig << " " << ib << "\n"; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/material.h: -------------------------------------------------------------------------------- 1 | #ifndef MATERIALH 2 | #define MATERIALH 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 "ray.h" 15 | #include "hitable.h" 16 | #include "random.h" 17 | 18 | struct hit_record; 19 | 20 | 21 | float schlick(float cosine, float ref_idx) { 22 | float r0 = (1-ref_idx) / (1+ref_idx); 23 | r0 = r0*r0; 24 | return r0 + (1-r0)*pow((1 - cosine),5); 25 | } 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(random_double(),random_double(),random_double()) - vec3(1,1,1); 50 | } while (p.squared_length() >= 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 | }; 59 | 60 | 61 | class lambertian : public material { 62 | public: 63 | lambertian(const vec3& a) : albedo(a) {} 64 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const { 65 | vec3 target = rec.p + rec.normal + random_in_unit_sphere(); 66 | scattered = ray(rec.p, target-rec.p); 67 | attenuation = albedo; 68 | return true; 69 | } 70 | 71 | vec3 albedo; 72 | }; 73 | 74 | 75 | class metal : public material { 76 | public: 77 | metal(const vec3& a, float f) : albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; } 78 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const { 79 | vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal); 80 | scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere()); 81 | attenuation = albedo; 82 | return (dot(scattered.direction(), rec.normal) > 0); 83 | } 84 | vec3 albedo; 85 | float fuzz; 86 | }; 87 | 88 | 89 | class dielectric : public material { 90 | public: 91 | dielectric(float ri) : ref_idx(ri) {} 92 | virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const { 93 | vec3 outward_normal; 94 | vec3 reflected = reflect(r_in.direction(), rec.normal); 95 | float ni_over_nt; 96 | attenuation = vec3(1.0, 1.0, 1.0); 97 | vec3 refracted; 98 | float reflect_prob; 99 | float cosine; 100 | if (dot(r_in.direction(), rec.normal) > 0) { 101 | outward_normal = -rec.normal; 102 | ni_over_nt = ref_idx; 103 | // cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length(); 104 | cosine = dot(r_in.direction(), rec.normal) / r_in.direction().length(); 105 | cosine = sqrt(1 - ref_idx*ref_idx*(1-cosine*cosine)); 106 | } 107 | else { 108 | outward_normal = rec.normal; 109 | ni_over_nt = 1.0 / ref_idx; 110 | cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length(); 111 | } 112 | if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted)) 113 | reflect_prob = schlick(cosine, ref_idx); 114 | else 115 | reflect_prob = 1.0; 116 | if (random_double() < reflect_prob) 117 | scattered = ray(rec.p, reflected); 118 | else 119 | scattered = ray(rec.p, refracted); 120 | return true; 121 | } 122 | 123 | float ref_idx; 124 | }; 125 | 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/random.h: -------------------------------------------------------------------------------- 1 | #ifndef RANDOMH 2 | #define RANDOMH 3 | 4 | #include 5 | 6 | inline double random_double() { 7 | return rand() / (RAND_MAX + 1.0); 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/ray.h: -------------------------------------------------------------------------------- 1 | #ifndef RAYH 2 | #define RAYH 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 "vec3.h" 15 | 16 | 17 | class ray 18 | { 19 | public: 20 | ray() {} 21 | ray(const vec3& a, const vec3& b) { A = a; B = b; } 22 | vec3 origin() const { return A; } 23 | vec3 direction() const { return B; } 24 | vec3 point_at_parameter(float t) const { return A + t*B; } 25 | 26 | vec3 A; 27 | vec3 B; 28 | }; 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/sphere.h: -------------------------------------------------------------------------------- 1 | #ifndef SPHEREH 2 | #define SPHEREH 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 | 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 | vec3 center; 23 | float radius; 24 | material *mat_ptr; 25 | }; 26 | 27 | 28 | bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const { 29 | vec3 oc = r.origin() - center; 30 | float a = dot(r.direction(), r.direction()); 31 | float b = dot(oc, r.direction()); 32 | float c = dot(oc, oc) - radius*radius; 33 | float discriminant = b*b - a*c; 34 | if (discriminant > 0) { 35 | float temp = (-b - sqrt(discriminant))/a; 36 | if (temp < t_max && temp > t_min) { 37 | rec.t = temp; 38 | rec.p = r.point_at_parameter(rec.t); 39 | rec.normal = (rec.p - center) / radius; 40 | rec.mat_ptr = mat_ptr; 41 | return true; 42 | } 43 | temp = (-b + sqrt(discriminant)) / a; 44 | if (temp < t_max && temp > t_min) { 45 | rec.t = temp; 46 | rec.p = r.point_at_parameter(rec.t); 47 | rec.normal = (rec.p - center) / radius; 48 | rec.mat_ptr = mat_ptr; 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef VEC3H 2 | #define VEC3H 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 15 | #include 16 | #include 17 | 18 | 19 | class vec3 { 20 | public: 21 | vec3() {} 22 | vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; } 23 | inline float x() const { return e[0]; } 24 | inline float y() const { return e[1]; } 25 | inline float z() const { return e[2]; } 26 | inline float r() const { return e[0]; } 27 | inline float g() const { return e[1]; } 28 | inline float b() const { return e[2]; } 29 | 30 | inline const vec3& operator+() const { return *this; } 31 | inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); } 32 | inline float operator[](int i) const { return e[i]; } 33 | inline float& operator[](int i) { return e[i]; } 34 | 35 | inline vec3& operator+=(const vec3 &v2); 36 | inline vec3& operator-=(const vec3 &v2); 37 | inline vec3& operator*=(const vec3 &v2); 38 | inline vec3& operator/=(const vec3 &v2); 39 | inline vec3& operator*=(const float t); 40 | inline vec3& operator/=(const float t); 41 | 42 | inline float length() const { return sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); } 43 | inline float squared_length() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; } 44 | inline void make_unit_vector(); 45 | 46 | float e[3]; 47 | }; 48 | 49 | 50 | inline std::istream& operator>>(std::istream &is, vec3 &t) { 51 | is >> t.e[0] >> t.e[1] >> t.e[2]; 52 | return is; 53 | } 54 | 55 | inline std::ostream& operator<<(std::ostream &os, const vec3 &t) { 56 | os << t.e[0] << " " << t.e[1] << " " << t.e[2]; 57 | return os; 58 | } 59 | 60 | inline void vec3::make_unit_vector() { 61 | float k = 1.0 / sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); 62 | e[0] *= k; e[1] *= k; e[2] *= k; 63 | } 64 | 65 | inline vec3 operator+(const vec3 &v1, const vec3 &v2) { 66 | return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]); 67 | } 68 | 69 | inline vec3 operator-(const vec3 &v1, const vec3 &v2) { 70 | return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]); 71 | } 72 | 73 | inline vec3 operator*(const vec3 &v1, const vec3 &v2) { 74 | return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]); 75 | } 76 | 77 | inline vec3 operator/(const vec3 &v1, const vec3 &v2) { 78 | return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]); 79 | } 80 | 81 | inline vec3 operator*(float t, const vec3 &v) { 82 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); 83 | } 84 | 85 | inline vec3 operator/(vec3 v, float t) { 86 | return vec3(v.e[0]/t, v.e[1]/t, v.e[2]/t); 87 | } 88 | 89 | inline vec3 operator*(const vec3 &v, float t) { 90 | return vec3(t*v.e[0], t*v.e[1], t*v.e[2]); 91 | } 92 | 93 | inline float dot(const vec3 &v1, const vec3 &v2) { 94 | return v1.e[0] * v2.e[0] 95 | + v1.e[1] * v2.e[1] 96 | + v1.e[2] * v2.e[2]; 97 | } 98 | 99 | inline vec3 cross(const vec3 &v1, const vec3 &v2) { 100 | return vec3(v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1], 101 | v1.e[2] * v2.e[0] - v1.e[0] * v2.e[2], 102 | v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]); 103 | } 104 | 105 | inline vec3& vec3::operator+=(const vec3 &v){ 106 | e[0] += v.e[0]; 107 | e[1] += v.e[1]; 108 | e[2] += v.e[2]; 109 | return *this; 110 | } 111 | 112 | inline vec3& vec3::operator*=(const vec3 &v){ 113 | e[0] *= v.e[0]; 114 | e[1] *= v.e[1]; 115 | e[2] *= v.e[2]; 116 | return *this; 117 | } 118 | 119 | inline vec3& vec3::operator/=(const vec3 &v){ 120 | e[0] /= v.e[0]; 121 | e[1] /= v.e[1]; 122 | e[2] /= v.e[2]; 123 | return *this; 124 | } 125 | 126 | inline vec3& vec3::operator-=(const vec3& v) { 127 | e[0] -= v.e[0]; 128 | e[1] -= v.e[1]; 129 | e[2] -= v.e[2]; 130 | return *this; 131 | } 132 | 133 | inline vec3& vec3::operator*=(const float t) { 134 | e[0] *= t; 135 | e[1] *= t; 136 | e[2] *= t; 137 | return *this; 138 | } 139 | 140 | inline vec3& vec3::operator/=(const float t) { 141 | float k = 1.0f/t; 142 | 143 | e[0] *= k; 144 | e[1] *= k; 145 | e[2] *= k; 146 | return *this; 147 | } 148 | 149 | inline vec3 unit_vector(vec3 v) { 150 | return v / v.length(); 151 | } 152 | 153 | 154 | #endif 155 | --------------------------------------------------------------------------------