Bounding Volume Hierarchy (BHV)
Bounding volume hierarchy (BVH) is an advanced data structure widely used in computer graphics, especially in ray tracing algorithms. The primary function of BVH is to effectively organize geometric data, such as polygons or triangles that compose a 3D model, to accelerate the process of ray-object intersection tests
1. Definition of Bounding Volume Hierarchy (BVH):
Each node of a tree is associated with a subset of primitives of the objects together with a bounding volume (BV) that encloses this subset with the smallest instance of some specified class of shape.
A bounding volume can take various forms, such as a sphere, a box (AABB - Axis Aligned Bounding Box), or an oriented bounding box (OBB), depending on the application’s specific requirements.
2. How to construct BVH
The process of constructing a bounding volume hierarchy is explained below.
-
Object partitioning: It involves partitioning the objects in the scene into two groups. Several strategies to do this include a median or a surface area heuristic (SAH).
-
Bounding volume creation: After partitioning, a bounding volume is created for each group, encapsulating all the objects within.
-
Recursive splitting: The process is repeated recursively, further splitting each group and creating bounding volumes until each group contains only one object.
Here,
-
Slide 1: It shows a polygon with 5 intersection rays.
-
Suppose that the polygon is made of 6000 triangles.
-
The ray-object intersection test with the 5 rays would be:
5×6000 = 30000
-
-
Slide 2: It shows a polygon with the bounding volume (a square).
-
We need to check for intersection with the simple shape first.
-
Since, only 2 rays are intersecting the bounding volume, the number of ray-object intersection tests can be reduced to:
2×6000 = 12000
-
In these two parts of code, we construct the BVH using two different methods: The BVH is constructed by calling bvh::v2::DefaultBuilder<node_type>::build with the bounding boxes, centers, and configuration. The result is stored in M_bvh, which is a std::unique_ptr pointing to the constructed BVH object.
typename bvh::v2::DefaultBuilder<node_type>::Config config; switch ( this->M_quality ) { default: case BVHEnum::Quality::High: config.quality = bvh::v2::DefaultBuilder<node_type>::Quality::High; break; case BVHEnum::Quality::Medium: config.quality = bvh::v2::DefaultBuilder<node_type>::Quality::Medium; break; case BVHEnum::Quality::Low: config.quality = bvh::v2::DefaultBuilder<node_type>::Quality::Low; break; } M_bvh = std::make_unique<backend_bvh_type>( bvh::v2::DefaultBuilder<node_type>::build(/*thread_pool,*/ bboxes, centers, config) );
The buildTree method constructs the BVH by recursively traversing the primitives. It uses a stack to build the BVH tree by splitting the primitives based on their centroids and creating internal nodes or leaves depending on the number of primitives.
void buildTree() { if ( M_rootNode ) return; M_rootNode = std::make_unique<BVHNode>(); std::stack<std::tuple<BVHNode*,int,int,int>> stack; stack.push( std::make_tuple(M_rootNode.get(),0,0,this->M_primitiveInfo.size()) ); // TODO case only one 1 element while ( !stack.empty() ) { auto [currentNode,cut_dimension,start_index_primitive,end_index_primitive] = stack.top(); stack.pop(); int nPrimitives = end_index_primitive - start_index_primitive; auto [bound_min_node,bound_max_node] = nPrimitives > 0 ? this->bounds( start_index_primitive,end_index_primitive ) : std::make_tuple( vector_realdim_type{}, vector_realdim_type{}); if ( nPrimitives <= 1 ) { // Create a leaf, since there is only one primitive in the list int firstPrimOffset = M_orderedPrims.size(); for (int i = start_index_primitive; i < end_index_primitive; ++i) { int primNum = this->M_primitiveInfo[i].meshEntity().id(); M_orderedPrims.push_back(primNum); } currentNode->updateForUse( firstPrimOffset, nPrimitives, -1, bound_min_node, bound_max_node ); } else { CHECK( start_index_primitive >=0 && end_index_primitive <= this->M_primitiveInfo.size() ) << start_index_primitive << " " << end_index_primitive; auto mid = (start_index_primitive + end_index_primitive) / 2; std::nth_element(&this->M_primitiveInfo[start_index_primitive], &this->M_primitiveInfo[mid], &this->M_primitiveInfo[end_index_primitive-1]+1, [cut_dimension=cut_dimension](primitiveinfo_type const&a, primitiveinfo_type const& b) { return a.centroid()[cut_dimension] < b.centroid()[cut_dimension]; }); int next_cut_dimension=(cut_dimension+1)%nRealDim; auto childNode0 = currentNode->setChild( 0, std::make_unique<BVHNode>() ); stack.push( std::make_tuple(childNode0, next_cut_dimension, start_index_primitive, mid) ); auto childNode1 = currentNode->setChild( 1, std::make_unique<BVHNode>() ); stack.push( std::make_tuple( childNode1, next_cut_dimension, mid, end_index_primitive ) ); currentNode->updateForUse( -1, nPrimitives, next_cut_dimension, bound_min_node, bound_max_node ); } } }