Skip to content

Commit 9f3838a

Browse files
committed
BVH optimization: split on the longest bbox axis
I see about an 18% speedup on my box. Resolves #1007
1 parent 4d50ef5 commit 9f3838a

File tree

5 files changed

+156
-10
lines changed

5 files changed

+156
-10
lines changed

books/RayTracingTheNextWeek.html

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,10 @@
375375
<div class='together'>
376376
This gives the following result:
377377

378+
<div id="image-bouncing-spheres">
378379
![<span class='num'>Image 1:</span> Bouncing spheres
379380
](../images/img-2.01-bouncing-spheres.png class='pixel')
381+
</div>
380382

381383
</div>
382384

@@ -926,13 +928,14 @@
926928
public:
927929
...
928930
bvh_node(const std::vector<shared_ptr<hittable>>& src_objects, size_t start, size_t end) {
929-
auto objects = src_objects; // Create a modifiable array of the source scene objects
930-
931931
int axis = random_int(0,2);
932+
932933
auto comparator = (axis == 0) ? box_x_compare
933934
: (axis == 1) ? box_y_compare
934935
: box_z_compare;
935936

937+
auto objects = src_objects; // A modifiable array of the source scene objects
938+
936939
size_t object_span = end - start;
937940

938941
if (object_span == 1) {
@@ -1038,6 +1041,111 @@
10381041
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10391042
[Listing [random-spheres-bvh]: <kbd>[main.cc]</kbd> Random spheres, using BVH]
10401043

1044+
The rendered image should be identical to the non-BVH version shown in
1045+
[image 1](#image-bouncing-spheres). However, if you time the two versions, the BVH version should be
1046+
faster. I see a speedup of almost _six and a half times_ the prior version.
1047+
1048+
1049+
Another BVH Optimization
1050+
-------------------------
1051+
We can speed up the BVH optimization a bit more. Instead of choosing a random splitting axis, let's
1052+
split the longest axis of the enclosing bounding box to get the most subdivision. The change is
1053+
straight-forward, but we'll add a few things to the `aabb` class in the process.
1054+
1055+
The first task is to construct an axis-aligned bounding box of the span of objects in the BVH
1056+
constructor. Basically, we'll construct the `bvh_node`s bounding box from this span by initializing
1057+
the bounding box to empty, and then augmenting it with each bounding box in the span of objects.
1058+
1059+
We don't have a way yet to express an empty bounding box, so we'll imagine one for now and
1060+
implementing it shortly.
1061+
1062+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1063+
class bvh_node : public hittable {
1064+
public:
1065+
...
1066+
bvh_node(const std::vector<shared_ptr<hittable>>& src_objects, size_t start, size_t end) {
1067+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
1068+
// Build the bounding box of the span of source objects.
1069+
bbox = aabb::empty;
1070+
for (int object_index=start; object_index < end; ++object_index)
1071+
bbox = aabb(bbox, src_objects[object_index]->bounding_box());
1072+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1073+
1074+
...
1075+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1076+
[Listing [object-span-bbox]: <kbd>[bvh.h]</kbd> Building the bbox for the span of BVH objects]
1077+
1078+
Now that we have the bounding box, set the splitting axis to the one with the longest side. Again,
1079+
we'll imagine a function that does that for us: `aabb::longest_axis()`. Finally, since we're
1080+
computing the bounding box of the object span up front, we can delete the original line that
1081+
computed it as the union of the left and right sides.
1082+
1083+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1084+
class bvh_node : public hittable {
1085+
public:
1086+
...
1087+
bvh_node(const std::vector<shared_ptr<hittable>>& src_objects, size_t start, size_t end) {
1088+
// Build the bounding box of the span of source objects.
1089+
bbox = aabb::empty;
1090+
for (int object_index=start; object_index < end; ++object_index)
1091+
bbox = aabb(bbox, src_objects[object_index]->bounding_box());
1092+
1093+
1094+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
1095+
int axis = bbox.longest_axis();
1096+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1097+
1098+
auto comparator = (axis == 0) ? box_x_compare
1099+
: (axis == 1) ? box_y_compare
1100+
: box_z_compare;
1101+
1102+
...
1103+
1104+
1105+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ delete
1106+
bbox = aabb(left->bounding_box(), right->bounding_box());
1107+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1108+
}
1109+
1110+
...
1111+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1112+
[Listing [object-span-bbox]: <kbd>[bvh.h]</kbd> Building the bbox for the span of BVH objects]
1113+
1114+
Now to implement the empty `aabb` code and the new `aabb::longest_axis()` function:
1115+
1116+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1117+
class aabb {
1118+
public:
1119+
...
1120+
1121+
1122+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++ highlight
1123+
int longest_axis() const {
1124+
// Returns the index of the longest axis of the bounding box.
1125+
1126+
if (x.size() > y.size())
1127+
return x.size() > z.size() ? 0 : 2;
1128+
else
1129+
return y.size() > z.size() ? 1 : 2;
1130+
}
1131+
1132+
static const aabb empty, universe;
1133+
};
1134+
1135+
const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty);
1136+
const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe);
1137+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
1138+
1139+
...
1140+
1141+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1142+
[Listing [aabb-empty-and-axis]: <kbd>[aabb.h]</kbd>
1143+
New aabb constants and longest_axis() function]
1144+
1145+
As before, you should see identical results to [image 1](#image-bouncing-spheres), but rendering a
1146+
little bit faster. On my system, this yields something like an additional 18% render speedup. Not
1147+
bad for a little extra work.
1148+
10411149

10421150

10431151
Texture Mapping

src/TheNextWeek/aabb.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,22 @@ class aabb {
7272
}
7373
return true;
7474
}
75+
76+
int longest_axis() const {
77+
// Returns the index of the longest axis of the bounding box.
78+
79+
if (x.size() > y.size())
80+
return x.size() > z.size() ? 0 : 2;
81+
else
82+
return y.size() > z.size() ? 1 : 2;
83+
}
84+
85+
static const aabb empty, universe;
7586
};
7687

88+
const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty);
89+
const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe);
90+
7791
aabb operator+(const aabb& bbox, const vec3& offset) {
7892
return aabb(bbox.x + offset.x(), bbox.y + offset.y(), bbox.z + offset.z());
7993
}

src/TheNextWeek/bvh.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "rtweekend.h"
1515

16+
#include "aabb.h"
1617
#include "hittable.h"
1718
#include "hittable_list.h"
1819

@@ -24,13 +25,19 @@ class bvh_node : public hittable {
2425
bvh_node(const hittable_list& list) : bvh_node(list.objects, 0, list.objects.size()) {}
2526

2627
bvh_node(const std::vector<shared_ptr<hittable>>& src_objects, size_t start, size_t end) {
27-
auto objects = src_objects; // Create a modifiable array of the source scene objects
28+
// Build the bounding box of the span of source objects.
29+
bbox = aabb::empty;
30+
for (int object_index=start; object_index < end; ++object_index)
31+
bbox = aabb(bbox, src_objects[object_index]->bounding_box());
32+
33+
int axis = bbox.longest_axis();
2834

29-
int axis = random_int(0,2);
3035
auto comparator = (axis == 0) ? box_x_compare
3136
: (axis == 1) ? box_y_compare
3237
: box_z_compare;
3338

39+
auto objects = src_objects; // A modifiable array of the source scene objects
40+
3441
size_t object_span = end - start;
3542

3643
if (object_span == 1) {
@@ -50,8 +57,6 @@ class bvh_node : public hittable {
5057
left = make_shared<bvh_node>(objects, start, mid);
5158
right = make_shared<bvh_node>(objects, mid, end);
5259
}
53-
54-
bbox = aabb(left->bounding_box(), right->bounding_box());
5560
}
5661

5762
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {

src/TheRestOfYourLife/aabb.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,22 @@ class aabb {
7272
}
7373
return true;
7474
}
75+
76+
int longest_axis() const {
77+
// Returns the index of the longest axis of the bounding box.
78+
79+
if (x.size() > y.size())
80+
return x.size() > z.size() ? 0 : 2;
81+
else
82+
return y.size() > z.size() ? 1 : 2;
83+
}
84+
85+
static const aabb empty, universe;
7586
};
7687

88+
const aabb aabb::empty = aabb(interval::empty, interval::empty, interval::empty);
89+
const aabb aabb::universe = aabb(interval::universe, interval::universe, interval::universe);
90+
7791
aabb operator+(const aabb& bbox, const vec3& offset) {
7892
return aabb(bbox.x + offset.x(), bbox.y + offset.y(), bbox.z + offset.z());
7993
}

src/TheRestOfYourLife/bvh.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "rtweekend.h"
1515

16+
#include "aabb.h"
1617
#include "hittable.h"
1718
#include "hittable_list.h"
1819

@@ -24,13 +25,19 @@ class bvh_node : public hittable {
2425
bvh_node(const hittable_list& list) : bvh_node(list.objects, 0, list.objects.size()) {}
2526

2627
bvh_node(const std::vector<shared_ptr<hittable>>& src_objects, size_t start, size_t end) {
27-
auto objects = src_objects; // Create a modifiable array of the source scene objects
28+
// Build the bounding box of the span of source objects.
29+
bbox = aabb::empty;
30+
for (int object_index=start; object_index < end; ++object_index)
31+
bbox = aabb(bbox, src_objects[object_index]->bounding_box());
32+
33+
int axis = bbox.longest_axis();
2834

29-
int axis = random_int(0,2);
3035
auto comparator = (axis == 0) ? box_x_compare
3136
: (axis == 1) ? box_y_compare
3237
: box_z_compare;
3338

39+
auto objects = src_objects; // A modifiable array of the source scene objects
40+
3441
size_t object_span = end - start;
3542

3643
if (object_span == 1) {
@@ -50,8 +57,6 @@ class bvh_node : public hittable {
5057
left = make_shared<bvh_node>(objects, start, mid);
5158
right = make_shared<bvh_node>(objects, mid, end);
5259
}
53-
54-
bbox = aabb(left->bounding_box(), right->bounding_box());
5560
}
5661

5762
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {

0 commit comments

Comments
 (0)