[toc]
# BVH
简单 BVH 图示
# BVH 性质
- 图元是叶子节点
- 每个图元只在一个层次中出现过
- 一个图元可能存在于多个空间划分中
- 节点数量有界
# BVH 构建
SplitMethod
: 构建 BVH 的方法
1 | enum class SplitMethod { SAH,HLBVH,Middle,EquialCounts } |
# 三个阶段
- 计算每个图元的边界信息,并保存到将用于构建 BVH 树的数组中
- 用选择的 SplitMethod 编码方法进行树的构建
- 将 BVH 树转换为无指针且紧凑的表示以供渲染使用
- 划分时需要保证图元边界的交叉尽可能的小
# 构建算法
# BVHPrimitiveInfo
初始化 BVH 中的每个图元信息
1 | // BVHAccel Local Declarations |
该结构记录了图元的包围盒信息 / 中心 / 以及图元在列表中的 Id 等信息 所有的图元按照图圆的输入顺序在 BVHAccel
中进行初始化
1 | // Initialize _primitiveInfo_ array for primitives |
其中
WorldBound()
函数是用来获取图元的 AABB 包围盒
选择对应的 Bvh 初始化算法进行构建
1 | std::vector<std::shared_ptr<Primitive>> orderedPrims; |
除了HLBVH算法单独编写外,其他三种BVH的构建都由recursiveBuild负责
BVHBuildNode
BVHBuildNode 表示一个 BVH
节点,每个节点都存有一个 Bounds
来表示包围盒,所有 Node 节点通过 MemoryArena
分配内存
# MemoryArena
构建 BVH 节点有这样一行代码:
1 | BVHBuildNode *node = arena.Alloc<BVHBuildNode>(); |
其中 Alloc 代码是这样
1 | template <typename T> |
内部会根据对齐规则对内存进行分配,分配的方式是直接在 MemoryArena 内部记录所有的内存,通过管理空闲块来进行新内存的分配,即自己编写的 malloc
# BVHBuildNode
- 每个 BVHBuildNode 都保存有 2 个孩子节点的指针槽
- 每个 BVHBuildNode 都保存有该节点的 Bounds
其中存储孩子节点的引用后,还会保存孩子节点的起始下标的总共有多少个图元 (以保证内存排布的紧凑)
recursiveBuild
1 | BVHBuildNode *BVHAccel::recursiveBuild( |
如果是最后一个图元,则标记为叶子节点
1 | if (nPrimitives == 1) { |
然后再判断是否是叶子节点,是的话,构建叶子节点,否则 第一步,获取长度最长的维度,按最长的维度来进行 BVH 的划分
1 | Bounds3f centroidBounds; |
第二部,计算中点
1 | int mid = (start + end) / 2; |
第三步,开始构建 BVH
3.1 如果当前轴 dim
的最大值等于最小值的话,记为叶子节点
1 | if (centroidBounds.pMax[dim] == centroidBounds.pMin[dim]) { |
3.2 按照 splitMethod
进行 BVH 的划分 3.2.1 计算图元组的中心 (对应维度)
1 | Float pmid = |
3.2.2 使用 partition 将图元组划分为两个区间
1 | template< class BidirIt, class UnaryPredicate > |
partition:
从首地址开始到最终地址,使用 p
对该区间进行划分,划分为满足与不满足的两个区间,同时返回第二个区间的手首地址指针
1 | BVHPrimitiveInfo *midPtr = std::partition( |
其中,Middle 划分算法的 Predicate
是如果当前节点在对应维度上的中点到该 Bounds 的中点小于当前 Bounds 维度的一半,则移动到满足区间 注意,最下面的:
1 | if (mid != start && mid != end) break; |
表示的是,Middle 划分方法如果发现几个 (大于 2) 图元有巨大的重叠包围框,Middle 方法可能无法将图元划分为两组 (即 midstart 或者 end), 这种情况下,就自行 Break, 尝试进行 SplitMethod::EqualCounts
方法再试一次
EqualCounts
1 | // Partition primitives into equally-sized subsets |
使用 n_th_element
它对数组排序使得中点指针处元素的位置是,如果数组完全排序,则所有中点之前的元素都比中点元素小且所有后面的元素都比它大。
# SAH 表面积启发式优化
上面的 Middle 划分对某些图元的划分效果不错,但实际应用中它们通常会选择性能较差的划分,导致光线要访问树的更多节点,因此带来渲染时不必要的低效光线 - 图元相交计算。 SAH
主要用来解决 大量图元划分方案中,哪个方案能带来来更好的划分方案/选择哪个划分空间可以带来更好的加速结构?
该模型有以下几个特征:
- 1. 估计执行光线相交测试的开销,包括穿行树的节点花的时间和为特定的图元划分进行光纤 - 图元相交测试花的时间 - 2. 得到每次构建的最小花销 - 3. 贪婪法构建每个层次节点最小化开销