|
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