光线求交加速算法:边界体积层次结构(Bounding Volume Hierarchies)1
BVH引入
光线和物体求交的加速算法中,最常见的是物体(图元)细分和空间细分。边界体积层次结构(BVH)是一种基于图元(primitives)细分的光线相交加速方法,其中,图元被划分为不相交集的层次结构。 (相反,空间细分通常将空间划分为不相交集的层次结构。)下图显示了一个简单场景的边界框层次结构:
其中,图元存储在叶中,每个节点存储其所有子节点图元的边界框。 因此,当光线穿过树时,只要它不与节点的边界相交,就可以跳过该节点下方的子树。
对于一个二叉树形式的BVH,其在叶子结点中存储一个单一的图元,该树节点总数为2n-1,其中n是图元数。将有n个叶节点和n-1个内部节点。如果叶子存储多个图元,则需要更少的节点。BVH比kd树更快速地构建,kd树通常比BVH提供更快的射线相交测试,但是建造时间要长得多。另一方面,与kd树相比,BVH通常在数值上更健壮,并且由于舍入误差而导致的交集遗漏更少。
构建BVH
BVH的构建分为三个阶段。
- 计算有关每个图元的边界信息并将其存储在数组中,该数组将在树构建期间使用。
- 使用相应的分割算法来构建树,本文主要介绍四种分区算法(SAH, HLBVH, Middle, EqualCounts) ,构建的结果是一棵二叉树,其中每个内部节点持有指向其子节点的指针,每个叶子节点持有对一个或多个基元的引用。
- 最后该树被转换为更紧凑(因此效率更高)的无指针表示,以在渲染期间使用。
我们根据这三个阶段写出BVH构建代码:
// BVH构造函数,构造BVH2叉树
BVHAccel::BVHAccel(std::vector<std::shared_ptr<Primitive>> p,
int maxPrimsInNode, SplitMethod splitMethod)
: maxPrimsInNode(std::min(255, maxPrimsInNode)),
splitMethod(splitMethod),
primitives(std::move(p)) {
// 利用图元构造BVH2叉树,从以下三个阶段进行。(+)表示代码未展开 (=)表示展开后
// 1.+初始化图元信息数组
// 2.+利用图元信息并使用相应的图元分割算法构建BVH
// 3.+树的节点用深度优先排列表示
}
对于要存储在BVH中的每个图元,我们将其边界框的质心,其完整边界框及其索引存储在BVHPrimitiveInfo结构体的图元数组中。该结构体如下:
struct BVHPrimitiveInfo {
BVHPrimitiveInfo(size_t primitiveNumber, const Bounds3f &bounds)
: primitiveNumber(primitiveNumber), bounds(bounds),
centroid(.5f * bounds.pMin + .5f * bounds.pMax) { }
size_t primitiveNumber;//图元索引
Bounds3f bounds;//图元边界框
Point3f centroid;//图元边界框的质心
};
我们初始化图元信息数组的代码则为:
//1.=初始化图元信息数组
std::vector<BVHPrimitiveInfo> primitiveInfo(primitives.size());
for (size_t i = 0; i < primitives.size(); ++i)
primitiveInfo[i] = { i, primitives[i]->WorldBound() };
接下来使用相应的图元分区算法以及图元信息构建BVH树,我们贴出代码:
// 2.=使用图元信息与相应的图元分割算法构建BVH树
MemoryArena arena(1024 * 1024);
int totalNodes = 0;
std::vector<std::shared_ptr<Primitive>> orderedPrims;
orderedPrims.reserve(primitives.size());
BVHBuildNode *root;//构建完成树的根节点
if (splitMethod == SplitMethod::HLBVH)
root = HLBVHBuild(arena, primitiveInfo, &totalNodes, orderedPrims);
else
root = recursiveBuild(arena, primitiveInfo, 0, primitives.size(),
&totalNodes, orderedPrims);
primitives.swap(orderedPrims);//orderedPrims是构造后排序过的图元,我们用其覆盖原图元
其中,我们把图元分区算法用枚举类型表示:
//SAH:表面积启发式法。HLBVH:线性边界体积层次结构法。Middle:中点分割法。EqualCounts:等数量分割法
enum class SplitMethod { SAH, HLBVH, Middle, EqualCounts };
HLBVH方法我们利用函数HLBVHBuild()构建,其他三个方法在函数recursiveBuild()中区分构建。MemoryArena为PBRT中内存管理类,用以优化内存分配。
每个BVHBuildNode代表BVH的一个节点。 所有节点都存储一个Bounds3f,它表示该节点下所有子级的边界框。 每