-
Notifications
You must be signed in to change notification settings - Fork 904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Book 2] BVH leaf optimisation (object_span == 2 slower) #890
Comments
Been a while since I've been in the BVH code, but your comment about ordering the left and right is a good one, particularly since the axis is chosen randomly ( On the null hittable, I think it would be better to construct a single public static member in the Ironically, three days ago I would have suggested a non-abstract For now, I'd like to see some concrete timing results of such a change, and I keep a running log of performance as the code evolves. Please create an experimental branch off |
Contributor invite sent. |
I initially started with a non-abstract hittable class but decided to place it into a separate class as it clarifies the intent of the class and the design pattern being used. Also default implementations can be problematic when someone extends the abstract class but forgets to implement the required functions and the default implementation is not desirable in the general case. My initial line of thought was (1) but upon writing this comment I thought about (2).
Edit: |
If the A static factory constructor could solve it as it enables the check to only performed once instead of for each recursive creation of the #include <stdexcept>
static std::shared_ptr<hittable> bvh_node::create(const hittable_list& scene)
{
if (scene.objects.size() <= 1)
throw std::invalid_argument("bvh_node::create: hittable_list has 1 or fewer objects.");
return std::make_shared<bvh_node>(world);
} It will now return an error at runtime instead of infinite recursion. hittable_list scene;
hittable_list empty_list;
scene.add(bvh_node::create(empty_list)); // will throw error above |
The test branch
if (object_span == 3) {
left = std::make_shared<bvh_node>(objects, start, start+2);
right = objects[start+2];
} else if (object_span == 2) {
left = objects[start];
right = objects[start+1];
} else {
std::sort(objects.begin() + start, objects.begin() + end, comparator);
auto mid = start + object_span/2;
left = make_shared<bvh_node>(objects, start, mid);
right = make_shared<bvh_node>(objects, mid, end);
} Let me know if you would like me to add the safe guard against the empty or single element |
I need to read through this. Seems like you've discovered an excellent little improvement. I'll circle back around once I get some time. |
Just realized (or more likely, forgot) that Johann's code is on the |
Okay, quick thoughts,
Is that the source is deliberately a bit dodgy, with the understanding that the user could optimize the code later (potentially using some sort of check for 3 elements). Which is what you've done. This is one of those examples where I think a naive (dumb) solution is the right teaching tool, and attempts to optimize should not create more stuff to learn. Lastly, splitting the elements based on their index in the array is an objectively mediocre means of splitting a bvh. The one-step up solution is to sort the bvhs based on the surface area, which is a reasonably good proxy for percentage change that a ray will intersect. How much of a speed-up do you see without the O3 optimization? |
I just ran a test with and without the null hittable object for the book 2 final scene. Without the null hittable: 1:19.083 So I like the idea, but it really doesn't seem to offer any significant speedup. |
Related: (#804)
In the BVH code, the leaf nodes aren't placed in an AABB containing just one object when
object_span==2
. For the case of ray-sphere intersection, not placing the leaf nodes in AABBs improves performance slightly but, for more complicated ray-intersection tests, the AABB rejection can be very useful.*Note: In the figures below, BVH nodes perform the hit test with AABB and objects perform arbitrary hit tests.

Before:
After:

Proposal to change this section of code in
BVHNode
constructor:Add this class to
Hittable.h
(Null object pattern)I make the dummy a static const member of the BVH Node class to make sure only one dummy exists and cannot be changed.
static const std::shared_ptr<Hittable> dummy;
and then in theBVHNode.cpp
I add to initialise it:const std::shared_ptr<Hittable> BVHNode::dummy = std::make_shared<NullHittable>();
This solution is cleaner and faster than performing the bounding_box collision test in, for example the Sphere::hit function, as each Hittable::hit child would be responsible to perform its own coarse collision first (error prone and code duplication).
Secondly, the
left->hit
right->hit
code can be left in tact with no conditionals and the dummy just returnsfalse
instead of performing the collision test on the same object twice, which depending on the complexity of the object can be slow. I agree with @trevordblack (#804) that branch code should be avoided but not that this is a non-issue, from my tests with optimisations turned on-O3
and a pseudo complex hit test. Return value caching would add more complexity than this approach and may lead to bugs and would be slower.Additionally, is the sorting even necessary in the case
(object_span == 2)
since right->hit is always called after left->hit.If you would like, I could create a pull request for this according to book's code style? My current implementation differs from that in the book and would need to be modified to fit the book's style.
The text was updated successfully, but these errors were encountered: