Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
top_down_sah_builder.h
Go to the documentation of this file.
1#ifndef BVH_V2_TOP_DOWN_SAH_BUILDER_H
2#define BVH_V2_TOP_DOWN_SAH_BUILDER_H
3
4#include "bvh/v2/bvh.h"
5#include "bvh/v2/vec.h"
6#include "bvh/v2/bbox.h"
8#include <stack>
9#if __has_include(<span>)
10#include <span>
11#else
12// Falling back to ROOT span
13#include "ROOT/span.hxx"
14#endif
15#include <algorithm>
16#include <optional>
17#include <numeric>
18#include <cassert>
19
20namespace bvh::v2 {
21
22/// Base class for all SAH-based, top-down builders.
23template <typename Node>
25protected:
26 using Scalar = typename Node::Scalar;
29
30public:
31 struct Config {
32 /// SAH heuristic parameters that control how primitives are partitioned.
34
35 /// Nodes containing less than this amount of primitives will not be split.
36 /// This is mostly to speed up BVH construction, and using large values may lead to lower
37 /// quality BVHs.
38 size_t min_leaf_size = 1;
39
40 /// Nodes that cannot be split based on the SAH and have a number of primitives larger than
41 /// this will be split using a fallback strategy. This should not happen often, but may
42 /// happen in worst-case scenarios or poorly designed scenes.
43 size_t max_leaf_size = 8;
44 };
45
46protected:
47 struct WorkItem {
48 size_t node_id;
49 size_t begin;
50 size_t end;
51
52 BVH_ALWAYS_INLINE size_t size() const { return end - begin; }
53 };
54
55 std::span<const BBox> bboxes_;
56 std::span<const Vec> centers_;
58
60 std::span<const BBox> bboxes,
61 std::span<const Vec> centers,
62 const Config& config)
63 : bboxes_(bboxes)
64 , centers_(centers)
65 , config_(config)
66 {
67 assert(bboxes.size() == centers.size());
68 assert(config.min_leaf_size <= config.max_leaf_size);
69 }
70
71 virtual std::vector<size_t>& get_prim_ids() = 0;
72 virtual std::optional<size_t> try_split(const BBox& bbox, size_t begin, size_t end) = 0;
73
74 BVH_ALWAYS_INLINE const std::vector<size_t>& get_prim_ids() const {
75 return const_cast<TopDownSahBuilder*>(this)->get_prim_ids();
76 }
77
79 const auto prim_count = bboxes_.size();
80
82 bvh.nodes.reserve((2 * prim_count) / config_.min_leaf_size);
83 bvh.nodes.emplace_back();
84 bvh.nodes.back().set_bbox(compute_bbox(0, prim_count));
85
86 std::stack<WorkItem> stack;
87 stack.push(WorkItem { 0, 0, prim_count });
88 while (!stack.empty()) {
89 auto item = stack.top();
90 stack.pop();
91
92 auto& node = bvh.nodes[item.node_id];
93 if (item.size() > config_.min_leaf_size) {
94 if (auto split_pos = try_split(node.get_bbox(), item.begin, item.end)) {
95 auto first_child = bvh.nodes.size();
96 node.index = Node::Index::make_inner(first_child);
97
98 bvh.nodes.resize(first_child + 2);
99
100 auto first_bbox = compute_bbox(item.begin, *split_pos);
101 auto second_bbox = compute_bbox(*split_pos, item.end);
102 auto first_range = std::make_pair(item.begin, *split_pos);
103 auto second_range = std::make_pair(*split_pos, item.end);
104
105 // For "any-hit" queries, the left child is chosen first, so we make sure that
106 // it is the child with the largest area, as it is more likely to contain an
107 // an occluder. See "SATO: Surface Area Traversal Order for Shadow Ray Tracing",
108 // by J. Nah and D. Manocha.
109 if (first_bbox.get_half_area() < second_bbox.get_half_area()) {
110 std::swap(first_bbox, second_bbox);
111 std::swap(first_range, second_range);
112 }
113
114 auto first_item = WorkItem { first_child + 0, first_range.first, first_range.second };
115 auto second_item = WorkItem { first_child + 1, second_range.first, second_range.second };
116 bvh.nodes[first_child + 0].set_bbox(first_bbox);
117 bvh.nodes[first_child + 1].set_bbox(second_bbox);
118
119 // Process the largest child item first, in order to minimize the stack size.
120 if (first_item.size() < second_item.size())
121 std::swap(first_item, second_item);
122
123 stack.push(first_item);
124 stack.push(second_item);
125 continue;
126 }
127 }
128
129 node.index = Node::Index::make_leaf(item.begin, item.size());
130 }
131
132 bvh.prim_ids = std::move(get_prim_ids());
133 bvh.nodes.shrink_to_fit();
134 return bvh;
135 }
136
137 BVH_ALWAYS_INLINE BBox compute_bbox(size_t begin, size_t end) const {
138 const auto& prim_ids = get_prim_ids();
139 auto bbox = BBox::make_empty();
140 for (size_t i = begin; i < end; ++i)
141 bbox.extend(bboxes_[prim_ids[i]]);
142 return bbox;
143 }
144};
145
146} // namespace bvh::v2
147
148#endif
Base class for all SAH-based, top-down builders.
BVH_ALWAYS_INLINE const std::vector< size_t > & get_prim_ids() const
virtual std::optional< size_t > try_split(const BBox &bbox, size_t begin, size_t end)=0
std::span< const Vec > centers_
BVH_ALWAYS_INLINE BBox compute_bbox(size_t begin, size_t end) const
std::span< const BBox > bboxes_
BVH_ALWAYS_INLINE TopDownSahBuilder(std::span< const BBox > bboxes, std::span< const Vec > centers, const Config &config)
virtual std::vector< size_t > & get_prim_ids()=0
Definition bbox.h:9
Definition bbox.h:9
#define BVH_ALWAYS_INLINE
Definition platform.h:19
static BVH_ALWAYS_INLINE constexpr BBox make_empty()
Definition bbox.h:40
static BVH_ALWAYS_INLINE Index make_inner(size_t first_child)
Definition index.h:75
static BVH_ALWAYS_INLINE Index make_leaf(size_t first_prim, size_t prim_count)
Definition index.h:70
SplitHeuristic< Scalar > sah
SAH heuristic parameters that control how primitives are partitioned.
size_t min_leaf_size
Nodes containing less than this amount of primitives will not be split.
size_t max_leaf_size
Nodes that cannot be split based on the SAH and have a number of primitives larger than this will be ...
BVH_ALWAYS_INLINE size_t size() const