|
1 | 1 | <meta charset="utf-8"> |
| 2 | +<!-- Markdeep: https://casual-effects.com/markdeep/ --> |
| 3 | + |
| 4 | + |
2 | 5 |
|
3 | 6 | **Ray Tracing in One Weekend** |
4 | 7 | Peter Shirley |
|
63 | 66 | write it to a file. The catch is, there are so many formats and many of those are complex. I always |
64 | 67 | start with a plain text ppm file. Here’s a nice description from Wikipedia: |
65 | 68 |
|
66 | | -  |
| 69 | +  |
67 | 70 |
|
68 | 71 | Let’s make some C++ code to output such a thing: |
69 | 72 |
|
|
105 | 108 | Opening the output file (in ToyViewer on my mac, but try it in your favorite viewer and google “ppm |
106 | 109 | viewer” if your viewer doesn’t support it) shows: |
107 | 110 |
|
108 | | -  |
| 111 | +  |
109 | 112 |
|
110 | 113 | Hooray! This is the graphics “hello world”. If your image doesn’t look like that, open the output |
111 | 114 | file in a text editor and see what it looks like. It should start something like this: |
|
329 | 332 | front of $A$, and this is what is often called a half-line or ray. The example $C = p(2)$ is shown |
330 | 333 | here: |
331 | 334 |
|
332 | | -  |
| 335 | +  |
333 | 336 |
|
334 | 337 | The function $p(t)$ in more verbose code form I call “point_at_parameter(t)”: |
335 | 338 |
|
|
369 | 372 | sides to move the ray endpoint across the screen. Note that I do not make the ray direction a unit |
370 | 373 | length vector because I think not doing that makes for simpler and slightly faster code. |
371 | 374 |
|
372 | | -  |
| 375 | +  |
373 | 376 |
|
374 | 377 | Below in code, the ray $r$ goes to approximately the pixel centers (I won’t worry about exactness |
375 | 378 | for now because we’ll add antialiasing later): |
|
418 | 421 |
|
419 | 422 | with $t$ going from zero to one. In our case this produces: |
420 | 423 |
|
421 | | -  |
| 424 | +  |
422 | 425 |
|
423 | 426 |
|
424 | 427 |
|
|
464 | 467 | (meaning no real solutions), or zero (meaning one real solution). In graphics, the algebra almost |
465 | 468 | always relates very directly to the geometry. What we have is: |
466 | 469 |
|
467 | | -  |
| 470 | +  |
468 | 471 |
|
469 | 472 | If we take that math and hard-code it into our program, we can test it by coloring red any pixel |
470 | 473 | that hits a small sphere we place at -1 on the z-axis: |
|
490 | 493 |
|
491 | 494 | What we get is this: |
492 | 495 |
|
493 | | -  |
| 496 | +  |
494 | 497 |
|
495 | 498 | Now this lacks all sorts of things -- like shading and reflection rays and more than one object -- |
496 | 499 | but we are closer to halfway done than we are to our start! One thing to be aware of is that we |
|
510 | 513 | as are most design decisions like that. For a sphere, the normal is in the direction of the hitpoint |
511 | 514 | minus the center: |
512 | 515 |
|
513 | | -  |
| 516 | +  |
514 | 517 |
|
515 | 518 | On the earth, this implies that the vector from the earth’s center to you points straight up. Let’s |
516 | 519 | throw that into the code now, and shade it. We don’t have any lights or anything yet, so let’s just |
|
549 | 552 |
|
550 | 553 | And that yields this picture: |
551 | 554 |
|
552 | | -  |
| 555 | +  |
553 | 556 |
|
554 | 557 | Now, how about several spheres? While it is tempting to have an array of spheres, a very clean |
555 | 558 | solution is the make an “abstract class” for anything a ray might hit and make both a sphere and a |
|
725 | 728 | This yields a picture that is really just a visualization of where the spheres are along with their |
726 | 729 | surface normal. This is often a great way to look at your model for flaws and characteristics. |
727 | 730 |
|
728 | | -  |
| 731 | +  |
729 | 732 |
|
730 | 733 |
|
731 | 734 |
|
|
783 | 786 | For a given pixel we have several samples within that pixel and send rays through each of the |
784 | 787 | samples. The colors of these rays are then averaged: |
785 | 788 |
|
786 | | -  |
| 789 | +  |
787 | 790 |
|
788 | 791 | Putting that all together yields a camera class encapsulating our simple axis-aligned camera from |
789 | 792 | before: |
|
850 | 853 | Zooming into the image that is produced, the big change is in edge pixels that are part background |
851 | 854 | and part foreground: |
852 | 855 |
|
853 | | -  |
| 856 | +  |
854 | 857 |
|
855 | 858 |
|
856 | 859 |
|
|
869 | 872 | direction randomized. So, if we send three rays into a crack between two diffuse surfaces they will |
870 | 873 | each have different random behavior: |
871 | 874 |
|
872 | | -  |
| 875 | +  |
873 | 876 |
|
874 | 877 | They also might be absorbed rather than reflected. The darker the surface, the more likely |
875 | 878 | absorption is. (That’s why it is dark!) Really any algorithm that randomizes direction will produce |
|
880 | 883 | Pick a random point s from the unit radius sphere that is tangent to the hitpoint, and send a ray |
881 | 884 | from the hitpoint $p$ to the random point $s$. That sphere has center $(p + N)$: |
882 | 885 |
|
883 | | -  |
| 886 | +  |
884 | 887 |
|
885 | 888 | We also need a way to pick a random point in a unit radius sphere centered at the origin. We’ll use |
886 | 889 | what is usually the easiest algorithm: a rejection method. First, we pick a random point in the unit |
|
914 | 917 |
|
915 | 918 | This gives us: |
916 | 919 |
|
917 | | -  |
| 920 | +  |
918 | 921 |
|
919 | 922 | Note the shadowing under the sphere. This picture is very dark, but our spheres only absorb half the |
920 | 923 | energy on each bounce, so they are 50% reflectors. If you can’t see the shadow, don’t worry, we will |
|
936 | 939 |
|
937 | 940 | That yields light grey, as we desire: |
938 | 941 |
|
939 | | -  |
| 942 | +  |
940 | 943 |
|
941 | 944 | There’s also a subtle bug in there. Some of the reflected rays hit the object they are reflecting |
942 | 945 | off of not at exactly $t=0$, but instead at $t=-0.0000001$ or $t=0.00000001$ or whatever floating |
|
1079 | 1082 | For smooth metals the ray won’t be randomly scattered. The key math is: how does a ray get |
1080 | 1083 | reflected from a metal mirror? Vector math is our friend here: |
1081 | 1084 |
|
1082 | | -  |
| 1085 | +  |
1083 | 1086 |
|
1084 | 1087 | The reflected ray direction in red is just $(v + 2B)$. In our design, $N$ is a unit vector, but $v$ |
1085 | 1088 | may not be. The length of $B$ should be $dot(v,N)$. Because $v$ points in, we will need a minus |
|
1171 | 1174 |
|
1172 | 1175 | Which gives: |
1173 | 1176 |
|
1174 | | -  |
| 1177 | +  |
1175 | 1178 |
|
1176 | 1179 | We can also randomize the reflected direction by using a small sphere and choosing a new endpoint |
1177 | 1180 | for the ray: |
1178 | 1181 |
|
1179 | | -  |
| 1182 | +  |
1180 | 1183 |
|
1181 | 1184 | The bigger the sphere, the fuzzier the reflections will be. This suggests adding a fuzziness |
1182 | 1185 | parameter that is just the radius of the sphere (so zero is no perturbation). The catch is that for |
|
1205 | 1208 |
|
1206 | 1209 | We can try that out by adding fuzziness 0.3 and 1.0 to the metals: |
1207 | 1210 |
|
1208 | | -  |
| 1211 | +  |
1209 | 1212 |
|
1210 | 1213 |
|
1211 | 1214 |
|
|
1220 | 1223 | there is a refraction ray at all. For this project, I tried to put two glass balls in our scene, and |
1221 | 1224 | I got this (I have not told you how to do this right or wrong yet, but soon!): |
1222 | 1225 |
|
1223 | | -  |
| 1226 | +  |
1224 | 1227 |
|
1225 | 1228 | Is that right? Glass balls look odd in real life. But no, it isn’t right. The world should be |
1226 | 1229 | flipped upside down and no weird black stuff. I just printed out the ray straight through the middle |
|
1233 | 1236 | Where $n$ and $n'$ are the refractive indices (typically air = 1, glass = 1.3–1.7, diamond = 2.4) |
1234 | 1237 | and the geometry is: |
1235 | 1238 |
|
1236 | | -  |
| 1239 | +  |
1237 | 1240 |
|
1238 | 1241 | One troublesome practical issue is that when the ray is in the material with the higher refractive |
1239 | 1242 | index, there is no real solution to Snell’s law and thus there is no refraction possible. Here all |
|
1306 | 1309 |
|
1307 | 1310 | We get: |
1308 | 1311 |
|
1309 | | -  |
| 1312 | +  |
1310 | 1313 |
|
1311 | 1314 | (The reader Becker has pointed out that when there is a reflection ray the function returns `false` |
1312 | 1315 | so there are no reflections. He is right, and that is why there are none in the image above. I |
|
1388 | 1391 |
|
1389 | 1392 | This gives: |
1390 | 1393 |
|
1391 | | -  |
| 1394 | +  |
1392 | 1395 |
|
1393 | 1396 |
|
1394 | 1397 |
|
|
1404 | 1407 | I first keep the rays coming from the origin and heading to the $z = -1$ plane. We could make it the |
1405 | 1408 | $z = -2$ plane, or whatever, as long as we made $h$ a ratio to that distance. Here is our setup: |
1406 | 1409 |
|
1407 | | -  |
| 1410 | +  |
1408 | 1411 |
|
1409 | 1412 | This implies $h = tan(\theta/2)$. Our camera now becomes: |
1410 | 1413 |
|
|
1449 | 1452 |
|
1450 | 1453 | gives: |
1451 | 1454 |
|
1452 | | -  |
| 1455 | +  |
1453 | 1456 |
|
1454 | 1457 | To get an arbitrary viewpoint, let’s first name the points we care about. We’ll call the position |
1455 | 1458 | where we place the camera _lookfrom_, and the point we look at _lookat_. (Later, if you want, you |
|
1461 | 1464 | vector for the camera. Notice we already we already have a plane that the up vector should be in, |
1462 | 1465 | the plane orthogonal to the view direction. |
1463 | 1466 |
|
1464 | | -  |
| 1467 | +  |
1465 | 1468 |
|
1466 | 1469 | We can actually use any up vector we want, and simply project it onto this plane to get an up vector |
1467 | 1470 | for the camera. I use the common convention of naming a “view up” (_vup_) vector. A couple of cross |
1468 | 1471 | products, and we now have a complete orthonormal basis (u,v,w) to describe our camera’s orientation. |
1469 | 1472 |
|
1470 | | -  |
| 1473 | +  |
1471 | 1474 |
|
1472 | 1475 | Remember that `vup`, `v`, and `w` are all in the same plane. Note that, like before when our fixed |
1473 | 1476 | camera faced -Z, our arbitrary view camera faces -w. And keep in mind that we can -- but we don’t |
|
1517 | 1520 |
|
1518 | 1521 | to get: |
1519 | 1522 |
|
1520 | | -  |
| 1523 | +  |
1521 | 1524 |
|
1522 | 1525 | And we can change field of view to get: |
1523 | 1526 |
|
1524 | | -  |
| 1527 | +  |
1525 | 1528 |
|
1526 | 1529 |
|
1527 | 1530 |
|
|
1546 | 1549 | computed (the image is projected upside down on the film). Graphics people usually use a thin lens |
1547 | 1550 | approximation. |
1548 | 1551 |
|
1549 | | -  |
| 1552 | +  |
1550 | 1553 |
|
1551 | 1554 | We also don’t need to simulate any of the inside of the camera. For the purposes of rendering an |
1552 | 1555 | image outside the camera, that would be unnecessary complexity. Instead I usually start rays from |
1553 | 1556 | the surface of the lens, and send them toward a virtual film plane, by finding the projection of the |
1554 | 1557 | film on the plane that is in focus (at the distance `focus_dist`). |
1555 | 1558 |
|
1556 | | -  |
| 1559 | +  |
1557 | 1560 |
|
1558 | 1561 | For that we just need to have the ray origins be on a disk around `lookfrom` rather than from a |
1559 | 1562 | point: |
|
1625 | 1628 |
|
1626 | 1629 | We get: |
1627 | 1630 |
|
1628 | | -  |
| 1631 | +  |
1629 | 1632 |
|
1630 | 1633 |
|
1631 | 1634 |
|
|
1677 | 1680 |
|
1678 | 1681 | This gives: |
1679 | 1682 |
|
1680 | | -  |
| 1683 | +  |
1681 | 1684 |
|
1682 | 1685 | An interesting thing you might note is the glass balls don’t really have shadows which makes them |
1683 | 1686 | look like they are floating. This is not a bug (you don’t see glass balls much in real life, where |
|
0 commit comments