|
374 | 374 | sticking it in this chapter so the code can run faster, and because it refactors `hittable` a |
375 | 375 | little, and when I add rectangles and boxes we won't have to go back and refactor them. |
376 | 376 |
|
377 | | -The ray-object intersection is the main time-bottleneck in a ray tracer, and the time is linear with |
378 | | -the number of objects. But it’s a repeated search on the same model, so we ought to be able to make |
| 377 | +Ray-object intersection is the main time-bottleneck in a ray tracer, and the run time is linear with |
| 378 | +the number of objects. But it’s a repeated search on the same scene, so we ought to be able to make |
379 | 379 | it a logarithmic search in the spirit of binary search. Because we are sending millions to billions |
380 | | -of rays on the same model, we can do an analog of sorting the model, and then each ray intersection |
381 | | -can be a sublinear search. The two most common families of sorting are to 1) divide the space, and |
382 | | -2) divide the objects. The latter is usually much easier to code up and just as fast to run for most |
383 | | -models. |
| 380 | +of rays into the same scene, we can sort the objects in the scene, and then each ray intersection |
| 381 | +can be a sublinear search. The two most common methods of sorting are to 1) subdivide the space, and |
| 382 | +2) subdivide the objects. The latter is usually much easier to code up, and just as fast to run for |
| 383 | +most models. |
384 | 384 |
|
385 | 385 |
|
386 | 386 | The Key Idea |
387 | 387 | ------------- |
388 | | -The key idea of a bounding volume over a set of primitives is to find a volume that fully encloses |
389 | | -(bounds) all the objects. For example, suppose you computed a sphere that bounds 10 objects. Any ray |
390 | | -that misses the bounding sphere definitely misses all ten objects inside. If the ray hits the |
391 | | -bounding sphere, then it might hit one of the ten objects. So the bounding code is always of the |
392 | | -form: |
| 388 | +The key idea of creating bounding volumes for a set of primitives is to find a volume that fully |
| 389 | +encloses (bounds) all the objects. For example, suppose you computed a sphere that bounds 10 |
| 390 | +objects. Any ray that misses the bounding sphere definitely misses all ten objects inside. If the |
| 391 | +ray hits the bounding sphere, then it might hit one of the ten objects. So the bounding code is |
| 392 | +always of the form: |
393 | 393 |
|
394 | 394 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
395 | 395 | if (ray hits bounding object) |
|
398 | 398 | return false |
399 | 399 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
400 | 400 |
|
401 | | -A key thing is we are dividing objects into subsets. We are not dividing the screen or the volume. |
402 | | -Any object is in just one bounding volume, but bounding volumes can overlap. |
| 401 | +Note that we will use these bounding volumes to group the objects in the scene into subgroups. We |
| 402 | +are *not* dividing the screen or the scene space. We want any given object to be in just one |
| 403 | +bounding volume, though bounding volumes can overlap. |
403 | 404 |
|
404 | 405 |
|
405 | 406 | Hierarchies of Bounding Volumes |
|
432 | 433 | To get that all to work we need a way to make good divisions, rather than bad ones, and a way to |
433 | 434 | intersect a ray with a bounding volume. A ray bounding volume intersection needs to be fast, and |
434 | 435 | bounding volumes need to be pretty compact. In practice for most models, axis-aligned boxes work |
435 | | -better than the alternatives, but this design choice is always something to keep in mind if you |
436 | | -encounter unusual types of models. |
| 436 | +better than the alternatives (such as the spherical bounds mentioned above), but this design choice |
| 437 | +is always something to keep in mind if you encounter other types of bounding models. |
437 | 438 |
|
438 | | -From now on we will call axis-aligned bounding rectangular parallelepiped (really, that is what they |
439 | | -need to be called if precise) _axis-aligned bounding boxes_, or AABBs. Any method you want to use to |
440 | | -intersect a ray with an AABB is fine. And all we need to know is whether or not we hit it; we don’t |
441 | | -need hit points or normals or any of the stuff we need to display the object. |
| 439 | +From now on we will call axis-aligned bounding rectangular parallelepipeds (really, that is what |
| 440 | +they need to be called if we're being precise) _axis-aligned bounding boxes_, or AABBs. (In the |
| 441 | +code, you will also come across the naming abbreviation "bbox" for "bounding box".) Any method you |
| 442 | +want to use to intersect a ray with an AABB is fine. And all we need to know is whether or not we |
| 443 | +hit it; we don’t need hit points or normals or any of the stuff we need to display the object. |
442 | 444 |
|
443 | 445 | <div class='together'> |
444 | 446 | Most people use the “slab” method. This is based on the observation that an n-dimensional AABB is |
445 | | -just the intersection of $n$ axis-aligned intervals, often called “slabs”. An interval is just the |
446 | | -points between two endpoints, _e.g._, $x$ such that $3 < x < 5$, or more succinctly $x$ in $(3,5)$. |
447 | | -In 2D, two intervals overlapping makes a 2D AABB (a rectangle): |
| 447 | +just the intersection of $n$ axis-aligned intervals, often called “slabs”. Recall that an interval |
| 448 | +is just the points within two endpoints, for example, $x$ such that $3 \leq x \leq 5$, or more |
| 449 | +succinctly $x$ in $[3,5]$. In 2D, an AABB (a rectangle) is defined by the overlap two intervals: |
448 | 450 |
|
449 | 451 | ![Figure [2d-aabb]: 2D axis-aligned bounding box](../images/fig-2.02-2d-aabb.jpg) |
450 | 452 |
|
451 | 453 | </div> |
452 | 454 |
|
453 | | -For a ray to hit one interval we first need to figure out whether the ray hits the boundaries. For |
454 | | -example, again in 2D, this is the ray parameters $t_0$ and $t_1$. (If the ray is parallel to the |
455 | | -plane, its intersection with the plane will be undefined.) |
| 455 | +To determine if a ray hits one interval, we first need to figure out whether the ray hits the |
| 456 | +boundaries. For example, in 1D, ray intersection with two planes will yield the ray parameters $t_0$ |
| 457 | +and $t_1$. (If the ray is parallel to the planes, its intersection with any plane will be |
| 458 | +undefined.) |
456 | 459 |
|
457 | 460 | ![Figure [ray-slab]: Ray-slab intersection](../images/fig-2.03-ray-slab.jpg) |
458 | 461 |
|
459 | | -In 3D, those boundaries are planes. The equations for the planes are $x = x_0$ and $x = x_1$. Where |
460 | | -does the ray hit that plane? Recall that the ray can be thought of as just a function that given a |
461 | | -$t$ returns a location $\mathbf{P}(t)$: |
| 462 | +How do we find the intersection between a ray and a plane? Recall that the ray is just defined by a |
| 463 | +function that--given a parameter $t$--returns a location $\mathbf{P}(t)$: |
462 | 464 |
|
463 | 465 | $$ \mathbf{P}(t) = \mathbf{A} + t \mathbf{b} $$ |
464 | 466 |
|
|
467 | 469 |
|
468 | 470 | $$ x_0 = A_x + t_0 b_x $$ |
469 | 471 |
|
470 | | -Thus $t$ at that hitpoint is: |
| 472 | +So $t$ at the intersection is given by |
471 | 473 |
|
472 | 474 | $$ t_0 = \frac{x_0 - A_x}{b_x} $$ |
473 | 475 |
|
|
476 | 478 | $$ t_1 = \frac{x_1 - A_x}{b_x} $$ |
477 | 479 |
|
478 | 480 | <div class='together'> |
479 | | -The key observation to turn that 1D math into a hit test is that for a hit, the $t$-intervals need |
480 | | -to overlap. For example, in 2D the green and blue overlapping only happens if there is a hit: |
| 481 | +The key observation to turn that 1D math into a 2D or 3D hit test is this: if a ray intersects the |
| 482 | +box bounded by all pairs of planes, then all $t$-intervals will overlap. For example, in 2D the |
| 483 | +green and blue overlapping only happens if the ray intersects the bounded box: |
481 | 484 |
|
482 | 485 | ![Figure [ray-slab-interval]: Ray-slab $t$-interval overlap |
483 | 486 | ](../images/fig-2.04-ray-slab-interval.jpg) |
484 | 487 |
|
| 488 | +In this figure, the upper ray intervals to not overlap, so we know the ray does _not_ hit the 2D box |
| 489 | +bounded by the green and blue planes. The lower ray intervals _do_ overlap, so we know the lower ray |
| 490 | +_does_ hit the bounded box. |
| 491 | + |
485 | 492 | </div> |
486 | 493 |
|
487 | 494 |
|
|
490 | 497 | The following pseudocode determines whether the $t$ intervals in the slab overlap: |
491 | 498 |
|
492 | 499 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
493 | | - compute (tx0, tx1) |
494 | | - compute (ty0, ty1) |
495 | | - return overlap?( (tx0, tx1), (ty0, ty1)) |
| 500 | + interval_x ← compute_intersection_x (ray, x0, x1) |
| 501 | + interval_y ← compute_intersection_y (ray, y0, y1) |
| 502 | + return overlaps(interval_x, interval_y) |
496 | 503 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
497 | 504 |
|
498 | 505 | <div class='together'> |
499 | | -That is awesomely simple, and the fact that the 3D version also works is why people love the slab |
500 | | -method: |
| 506 | +That is awesomely simple, and the fact that the 3D version trivially extends the above is why people |
| 507 | +love the slab method: |
501 | 508 |
|
502 | 509 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
503 | | - compute (tx0, tx1) |
504 | | - compute (ty0, ty1) |
505 | | - compute (tz0, tz1) |
506 | | - return overlap ? ((tx0, tx1), (ty0, ty1), (tz0, tz1)) |
| 510 | + interval_x ← compute_intersection_x (ray, x0, x1) |
| 511 | + interval_y ← compute_intersection_y (ray, y0, y1) |
| 512 | + interval_z ← compute_intersection_z (ray, z0, z1) |
| 513 | + return overlaps(interval_x, interval_y, interval_z) |
507 | 514 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
508 | 515 |
|
509 | 516 | </div> |
510 | 517 |
|
511 | | -There are some caveats that make this less pretty than it first appears. First, suppose the ray is |
512 | | -travelling in the negative $\mathbf{x}$ direction. The interval $(t_{x0}, t_{x1})$ as computed above |
513 | | -might be reversed, _e.g._ something like $(7, 3)$. Second, the divide in there could give us |
514 | | -infinities. And if the ray origin is on one of the slab boundaries, we can get a `NaN`. There are |
515 | | -many ways these issues are dealt with in various ray tracers’ AABB. (There are also vectorization |
516 | | -issues like SIMD which we will not discuss here. Ingo Wald’s papers are a great place to start if |
517 | | -you want to go the extra mile in vectorization for speed.) For our purposes, this is unlikely to be |
518 | | -a major bottleneck as long as we make it reasonably fast, so let’s go for simplest, which is often |
519 | | -fastest anyway! First let’s look at computing the intervals: |
| 518 | +There are some caveats that make this less pretty than it first appears. Consider again the 1D |
| 519 | +equations for $t_0$ and $t_1$: |
520 | 520 |
|
521 | | - $$ t_{x0} = \frac{x_0 - A_x}{b_x} $$ |
522 | | - $$ t_{x1} = \frac{x_1 - A_x}{b_x} $$ |
| 521 | + $$ t_0 = \frac{x_0 - A_x}{b_x} $$ |
| 522 | + $$ t_1 = \frac{x_1 - A_x}{b_x} $$ |
523 | 523 |
|
524 | | -One troublesome thing is that perfectly valid rays will have $b_x = 0$, causing division by zero. |
525 | | -Some of those rays are inside the slab, and some are not. Also, the zero will have a ± sign when |
526 | | -using IEEE floating point. The good news for $b_x = 0$ is that $t_{x0}$ and $t_{x1}$ will both be +∞ |
527 | | -or both be -∞ if not between $x_0$ and $x_1$. So, using min and max should get us the right answers: |
| 524 | +First, suppose the ray is traveling in the negative $\mathbf{x}$ direction. The interval $(t_{x0}, |
| 525 | +t_{x1})$ as computed above might be reversed, like $(7, 3)$ for example. Second, the denominator |
| 526 | +$b_x$ could be zero, yielding infinite values. And if the ray origin lies on one of the slab |
| 527 | +boundaries, we can get a `NaN`, since both the numerator and the denominator can be zero. Also, the |
| 528 | +zero will have a ± sign when using IEEE floating point. |
| 529 | + |
| 530 | +The good news for $b_x = 0$ is that $t_{x0}$ and $t_{x1}$ will be equal: both +∞ or -∞, if not |
| 531 | +between $x_0$ and $x_1$. So, using min and max should get us the right answers: |
528 | 532 |
|
529 | 533 | $$ t_{x0} = \min( |
530 | 534 | \frac{x_0 - A_x}{b_x}, |
|
536 | 540 | \frac{x_1 - A_x}{b_x}) |
537 | 541 | $$ |
538 | 542 |
|
539 | | -The remaining troublesome case if we do that is if $b_x = 0$ and either $x_0 - A_x = 0$ or $x_1 - |
540 | | -A_x = 0$ so we get a `NaN`. In that case we can probably accept either hit or no hit answer, but |
541 | | -we’ll revisit that later. |
| 543 | +The remaining troublesome case if we do that is if $b_x = 0$ and either $x_0 - A_x = 0$ or |
| 544 | +$x_1 - A_x = 0$ so we get a `NaN`. In that case we can arbitrarily interpret that as either hit or |
| 545 | +no hit, but we’ll revisit that later. |
542 | 546 |
|
543 | | -Now, let’s look at that overlap function. Suppose we can assume the intervals are not reversed (so |
544 | | -the first value is less than the second value in the interval) and we want to return true in that |
545 | | -case. The boolean overlap that also computes the overlap interval $(f, F)$ of intervals $(d, D)$ and |
546 | | -$(e, E)$ would be: |
| 547 | +Now, let’s look at the pseudo-function `overlaps`. Suppose we can assume the intervals are not |
| 548 | +reversed, and we want to return true when the intervals overlap. The boolean `overlaps()` function |
| 549 | +computes the overlap of the $t$ intervals `t_interval1` and `t_interval2`, and uses that to |
| 550 | +determine if that overlap is non-empty: |
547 | 551 |
|
548 | 552 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
549 | | - bool overlap(d, D, e, E, f, F) |
550 | | - f = max(d, e) |
551 | | - F = min(D, E) |
552 | | - return (f < F) |
| 553 | + bool overlaps(t_interval1, t_interval2) |
| 554 | + t_min ← max(t_interval1.min, t_interval2.min) |
| 555 | + t_max ← min(t_interval1.max, t_interval2.max) |
| 556 | + return t_min < t_max |
553 | 557 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
554 | 558 |
|
555 | | -If there are any `NaN`s running around there, the compare will return false so we need to be sure |
556 | | -our bounding boxes have a little padding if we care about grazing cases (and we probably should |
557 | | -because in a ray tracer all cases come up eventually). Here's the implementation: |
| 559 | +If there are any `NaN`s running around there, the compare will return false, so we need to be sure |
| 560 | +our bounding boxes have a little padding if we care about grazing cases (and we probably _should_ |
| 561 | +because in a ray tracer all cases come up eventually). |
| 562 | + |
| 563 | +<div class='together'> |
| 564 | +To accomplish this, we'll first add a new `interval` function `expand`, which pads an interval by a |
| 565 | +given amount: |
558 | 566 |
|
559 | 567 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
560 | 568 | class interval { |
|
577 | 585 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
578 | 586 | [Listing [interval-expand]: <kbd>[interval.h]</kbd> interval::expand() method] |
579 | 587 |
|
| 588 | +</div> |
| 589 | + |
| 590 | +<div class='together'> |
| 591 | +Now we have everything we need to implment the new AABB class. |
580 | 592 |
|
581 | 593 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ |
582 | 594 | #ifndef AABB_H |
|
591 | 603 | aabb() {} // The default AABB is empty, since intervals are empty by default. |
592 | 604 |
|
593 | 605 | aabb(const interval& ix, const interval& iy, const interval& iz) |
594 | | - : x(ix), y(iy), z(iz) { } |
| 606 | + : x(ix), y(iy), z(iz) |
| 607 | + { |
| 608 | + pad_to_minimums(); |
| 609 | + } |
595 | 610 |
|
596 | 611 | aabb(const point3& a, const point3& b) { |
597 | 612 | // Treat the two points a and b as extrema for the bounding box, so we don't require a |
598 | 613 | // particular minimum/maximum coordinate order. |
599 | 614 | x = interval(fmin(a[0],b[0]), fmax(a[0],b[0])); |
600 | 615 | y = interval(fmin(a[1],b[1]), fmax(a[1],b[1])); |
601 | 616 | z = interval(fmin(a[2],b[2]), fmax(a[2],b[2])); |
| 617 | + |
| 618 | + pad_to_minimums(); |
602 | 619 | } |
603 | 620 |
|
604 | 621 | const interval& axis(int n) const { |
|
620 | 637 | } |
621 | 638 | return true; |
622 | 639 | } |
| 640 | + |
| 641 | + private: |
| 642 | + |
| 643 | + void pad_to_minimums() { |
| 644 | + // Adjust the AABB so that no side is narrower than some delta, padding if necessary. |
| 645 | + |
| 646 | + double delta = 0.0001; |
| 647 | + if (x.size() < delta) x = x.expand(delta); |
| 648 | + if (y.size() < delta) y = y.expand(delta); |
| 649 | + if (z.size() < delta) z = z.expand(delta); |
| 650 | + } |
623 | 651 | }; |
624 | 652 |
|
625 | 653 | #endif |
626 | 654 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
627 | 655 | [Listing [aabb]: <kbd>[aabb.h]</kbd> Axis-aligned bounding box class] |
628 | 656 |
|
| 657 | +</div> |
| 658 | + |
629 | 659 |
|
630 | 660 | An Optimized AABB Hit Method |
631 | 661 | ----------------------------- |
|
659 | 689 | ... |
660 | 690 | }; |
661 | 691 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
662 | | - [Listing [aabb-hit]: <kbd>[aabb.h]</kbd> Axis-aligned bounding box hit function] |
| 692 | + [Listing [aabb-hit]: <kbd>[aabb.h]</kbd> Optional optimized AABB hit function] |
663 | 693 |
|
664 | 694 |
|
665 | 695 | Constructing Bounding Boxes for Hittables |
|
0 commit comments