From 034a1bdb7d7a85455f83987cde6fc5e6b8d0482b Mon Sep 17 00:00:00 2001 From: Steve Hollasch Date: Mon, 14 Aug 2023 23:31:34 -0700 Subject: [PATCH] BVH optimization: split on the longest bbox axis I see about an 18% speedup on my box. Resolves #1007 --- books/RayTracingTheNextWeek.html | 112 ++++++++++++++++++++++++++++++- src/TheNextWeek/aabb.h | 14 ++++ src/TheNextWeek/bvh.h | 13 ++-- src/TheRestOfYourLife/aabb.h | 14 ++++ src/TheRestOfYourLife/bvh.h | 13 ++-- 5 files changed, 156 insertions(+), 10 deletions(-) diff --git a/books/RayTracingTheNextWeek.html b/books/RayTracingTheNextWeek.html index 0bbc9704..6da3fdf4 100644 --- a/books/RayTracingTheNextWeek.html +++ b/books/RayTracingTheNextWeek.html @@ -372,8 +372,10 @@
This gives the following result: +
![Image 1: Bouncing spheres ](../images/img-2.01-bouncing-spheres.png class='pixel') +
@@ -923,13 +925,14 @@ public: ... bvh_node(const std::vector>& src_objects, size_t start, size_t end) { - auto objects = src_objects; // Create a modifiable array of the source scene objects - int axis = random_int(0,2); + auto comparator = (axis == 0) ? box_x_compare : (axis == 1) ? box_y_compare : box_z_compare; + auto objects = src_objects; // A modifiable array of the source scene objects + size_t object_span = end - start; if (object_span == 1) { @@ -1035,6 +1038,111 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Listing [random-spheres-bvh]: [main.cc] Random spheres, using BVH] +The rendered image should be identical to the non-BVH version shown in +[image 1](#image-bouncing-spheres). However, if you time the two versions, the BVH version should be +faster. I see a speedup of almost _six and a half times_ the prior version. + + +Another BVH Optimization +------------------------- +We can speed up the BVH optimization a bit more. Instead of choosing a random splitting axis, let's +split the longest axis of the enclosing bounding box to get the most subdivision. The change is +straight-forward, but we'll add a few things to the `aabb` class in the process. + +The first task is to construct an axis-aligned bounding box of the span of objects in the BVH +constructor. Basically, we'll construct the `bvh_node`s bounding box from this span by initializing +the bounding box to empty, and then augmenting it with each bounding box in the span of objects. + +We don't have a way yet to express an empty bounding box, so we'll imagine one for now and +implementing it shortly. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + class bvh_node : public hittable { + public: + ... + bvh_node(const std::vector>& src_objects, size_t start, size_t end) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + // Build the bounding box of the span of source objects. + bbox = aabb::empty; + for (int object_index=start; object_index < end; ++object_index) + bbox = aabb(bbox, src_objects[object_index]->bounding_box()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + + ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + [Listing [object-span-bbox]: [bvh.h] Building the bbox for the span of BVH objects] + +Now that we have the bounding box, set the splitting axis to the one with the longest side. Again, +we'll imagine a function that does that for us: `aabb::longest_axis()`. Finally, since we're +computing the bounding box of the object span up front, we can delete the original line that +computed it as the union of the left and right sides. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + class bvh_node : public hittable { + public: + ... + bvh_node(const std::vector>& src_objects, size_t start, size_t end) { + // Build the bounding box of the span of source objects. + bbox = aabb::empty; + for (int object_index=start; object_index < end; ++object_index) + bbox = aabb(bbox, src_objects[object_index]->bounding_box()); + + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + int axis = bbox.longest_axis(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + + auto comparator = (axis == 0) ? box_x_compare + : (axis == 1) ? box_y_compare + : box_z_compare; + + ... + + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ delete + bbox = aabb(left->bounding_box(), right->bounding_box()); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + } + + ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + [Listing [object-span-bbox]: [bvh.h] Building the bbox for the span of BVH objects] + +Now to implement the empty `aabb` code and the new `aabb::longest_axis()` function: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + class aabb { + public: + ... + + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight + int longest_axis() const { + // Returns the index of the longest axis of the bounding box. + + if (x.size() > y.size()) + return x.size() > z.size() ? 0 : 2; + else + return y.size() > z.size() ? 1 : 2; + } + + static const aabb empty, universe; + }; + + const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty); + const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ + + ... + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + [Listing [aabb-empty-and-axis]: [aabb.h] + New aabb constants and longest_axis() function] + +As before, you should see identical results to [image 1](#image-bouncing-spheres), but rendering a +little bit faster. On my system, this yields something like an additional 18% render speedup. Not +bad for a little extra work. + Texture Mapping diff --git a/src/TheNextWeek/aabb.h b/src/TheNextWeek/aabb.h index 462d97f2..5ffa38c7 100644 --- a/src/TheNextWeek/aabb.h +++ b/src/TheNextWeek/aabb.h @@ -72,8 +72,22 @@ class aabb { } return true; } + + int longest_axis() const { + // Returns the index of the longest axis of the bounding box. + + if (x.size() > y.size()) + return x.size() > z.size() ? 0 : 2; + else + return y.size() > z.size() ? 1 : 2; + } + + static const aabb empty, universe; }; +const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty); +const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe); + aabb operator+(const aabb& bbox, const vec3& offset) { return aabb(bbox.x + offset.x(), bbox.y + offset.y(), bbox.z + offset.z()); } diff --git a/src/TheNextWeek/bvh.h b/src/TheNextWeek/bvh.h index 9d66cda9..2740f881 100644 --- a/src/TheNextWeek/bvh.h +++ b/src/TheNextWeek/bvh.h @@ -13,6 +13,7 @@ #include "rtweekend.h" +#include "aabb.h" #include "hittable.h" #include "hittable_list.h" @@ -24,13 +25,19 @@ class bvh_node : public hittable { bvh_node(const hittable_list& list) : bvh_node(list.objects, 0, list.objects.size()) {} bvh_node(const std::vector>& src_objects, size_t start, size_t end) { - auto objects = src_objects; // Create a modifiable array of the source scene objects + // Build the bounding box of the span of source objects. + bbox = aabb::empty; + for (int object_index=start; object_index < end; ++object_index) + bbox = aabb(bbox, src_objects[object_index]->bounding_box()); + + int axis = bbox.longest_axis(); - int axis = random_int(0,2); auto comparator = (axis == 0) ? box_x_compare : (axis == 1) ? box_y_compare : box_z_compare; + auto objects = src_objects; // A modifiable array of the source scene objects + size_t object_span = end - start; if (object_span == 1) { @@ -50,8 +57,6 @@ class bvh_node : public hittable { left = make_shared(objects, start, mid); right = make_shared(objects, mid, end); } - - bbox = aabb(left->bounding_box(), right->bounding_box()); } bool hit(const ray& r, interval ray_t, hit_record& rec) const override { diff --git a/src/TheRestOfYourLife/aabb.h b/src/TheRestOfYourLife/aabb.h index 462d97f2..5ffa38c7 100644 --- a/src/TheRestOfYourLife/aabb.h +++ b/src/TheRestOfYourLife/aabb.h @@ -72,8 +72,22 @@ class aabb { } return true; } + + int longest_axis() const { + // Returns the index of the longest axis of the bounding box. + + if (x.size() > y.size()) + return x.size() > z.size() ? 0 : 2; + else + return y.size() > z.size() ? 1 : 2; + } + + static const aabb empty, universe; }; +const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty); +const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe); + aabb operator+(const aabb& bbox, const vec3& offset) { return aabb(bbox.x + offset.x(), bbox.y + offset.y(), bbox.z + offset.z()); } diff --git a/src/TheRestOfYourLife/bvh.h b/src/TheRestOfYourLife/bvh.h index 9d66cda9..2740f881 100644 --- a/src/TheRestOfYourLife/bvh.h +++ b/src/TheRestOfYourLife/bvh.h @@ -13,6 +13,7 @@ #include "rtweekend.h" +#include "aabb.h" #include "hittable.h" #include "hittable_list.h" @@ -24,13 +25,19 @@ class bvh_node : public hittable { bvh_node(const hittable_list& list) : bvh_node(list.objects, 0, list.objects.size()) {} bvh_node(const std::vector>& src_objects, size_t start, size_t end) { - auto objects = src_objects; // Create a modifiable array of the source scene objects + // Build the bounding box of the span of source objects. + bbox = aabb::empty; + for (int object_index=start; object_index < end; ++object_index) + bbox = aabb(bbox, src_objects[object_index]->bounding_box()); + + int axis = bbox.longest_axis(); - int axis = random_int(0,2); auto comparator = (axis == 0) ? box_x_compare : (axis == 1) ? box_y_compare : box_z_compare; + auto objects = src_objects; // A modifiable array of the source scene objects + size_t object_span = end - start; if (object_span == 1) { @@ -50,8 +57,6 @@ class bvh_node : public hittable { left = make_shared(objects, start, mid); right = make_shared(objects, mid, end); } - - bbox = aabb(left->bounding_box(), right->bounding_box()); } bool hit(const ray& r, interval ray_t, hit_record& rec) const override {