|
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