BVH 层次包围盒加速光线追踪

项目目标

实现 BVH(Bounding Volume Hierarchy,层次包围盒) 加速结构,将暴力 O(n) 的光线-场景求交优化到 O(log n),并通过实际渲染对比验证性能提升。

这是光线追踪系列(02-06 至今)的重要里程碑——解决了随场景规模增大性能急剧下降的根本问题。

核心概念

为什么需要 BVH?

朴素光线追踪对每条光线都要遍历场景中所有物体求交,时间复杂度是 O(n)。当场景有 1000 个球体时,每条光线需要做 1000 次求交测试。

BVH 通过构建层次树结构解决这个问题:

1
2
3
4
5
             [根节点 AABB]
/ \
[左子树 AABB] [右子树 AABB]
/ \ / \
[球A AABB] [球B AABB] ... ...

光线先与包围盒求交(很快),如果不相交就跳过整棵子树,大幅减少无效测试。

实现过程

1. AABB 轴对齐包围盒

核心是高效的 Slab 方法求交:

1
2
3
4
5
6
7
8
9
10
11
12
bool AABB::intersect(const Ray& ray, double t_min, double t_max) const {
for (int i = 0; i < 3; i++) {
double inv_d = 1.0 / ray.direction[i];
double t0 = (min_pt[i] - ray.origin[i]) * inv_d;
double t1 = (max_pt[i] - ray.origin[i]) * inv_d;
if (inv_d < 0) std::swap(t0, t1);
t_min = std::max(t_min, t0);
t_max = std::min(t_max, t1);
if (t_max <= t_min) return false; // 提前退出
}
return true;
}

每个轴两次乘法,极其高效。

2. SAH 构建策略

最朴素的构建是按中值分割——但 SAH(Surface Area Heuristic,表面积启发式)更优。

SAH 的思路:最优的分割应该最小化后续求交的期望代价。数学上等价于:

1
Cost(split) = 0.125 + (count_left × Area_left + count_right × Area_right) / Area_parent

实现时用 12 个桶均匀分割候选点,选代价最小的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sah_split(...) {
const int NUM_BUCKETS = 12;
Bucket buckets[NUM_BUCKETS];

// 分配球体到桶
for (int i = start; i < end; i++) {
int b = bucket_index(spheres[i].center, axis);
buckets[b].count++;
buckets[b].bbox = AABB::merge(...);
}

// 评估每个分割点代价
double costs[NUM_BUCKETS - 1];
for (int i = 0; i < NUM_BUCKETS - 1; i++) {
costs[i] = sah_cost(left_buckets, right_buckets);
}

// 找最小代价
return best_split(costs);
}

3. BVH 遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool intersect_node(int node_idx, ...) {
// 先测 AABB,不中则整棵子树跳过
if (!node.bbox.intersect(ray, t_min, t_max)) return false;

if (node.is_leaf) {
return spheres[node.sphere_idx].intersect(...);
}

// 先左后右,维护当前最近距离
bool hit_left = intersect_node(node.left, ...);
if (hit_left) t_max = rec_left.t; // 剪枝!
bool hit_right = intersect_node(node.right, ...);

return hit_left || hit_right;
}

关键优化:找到左子树的交点后,更新 t_max,使右子树测试更容易被剪枝。

渲染结果

高质量最终渲染(800×450,84个球体,BVH加速)

BVH加速光线追踪渲染结果

场景包含:漫反射球、金属球、玻璃球,景深效果,天空渐变。

BVH vs 暴力遍历 对比(左:BVH,右:暴力)

BVH与暴力遍历对比

两者渲染结果完全相同(正确性验证),但性能有差异。

BVH 结构层级可视化(俯视图)

BVH层级结构可视化

不同颜色代表不同 BVH 深度(红=第0层,绿=第1层,蓝=第2层,黄=第3层),白点是球体位置。

性能数据

指标 BVH 加速 暴力遍历
渲染时间 171ms 224ms
平均测试/光线 28.5 54(固定)
BVH 节点数 107
加速比 1.31x

:50个球体场景相对较小,BVH 的优势在大场景(1000+ 物体)才真正显现。理论上 BVH 的测试次数是 O(log n),而暴力是 O(n)。

技术总结

学到的核心知识

  1. AABB Slab 法:3轴各算两个交点,取最大 t_min 和最小 t_max,简洁高效
  2. SAH 启发式:不只考虑数量均分,而是最小化期望代价,树质量显著提升
  3. 退化处理的重要性:当所有质心重合时,SAH 会退化,需要回退到简单分割
  4. 剪枝机制:更新 t_max 是 BVH 效率的关键,减少右子树不必要的测试

理论复杂度 vs 实测

  • 理论:BVH 遍历 O(log n),构建 O(n log n)
  • 实测 50 球:BVH 测试 28.5次/光线 vs 理论 log2(54) ≈ 5.8
  • 差距原因:路径追踪中光线会弹射多次,且 BVH 不是完美平衡树

下一步

  • BVH + 更多基元(三角形网格)
  • 迭代 BVH 遍历(减少递归开销)
  • QBVH(4叉 BVH,SIMD 友好)
  • 动态场景的 BVH 重建/重构

代码仓库

GitHub: https://github.com/chiuhoukazusa/daily-coding-practice/tree/main/2026/03/03-01-BVH-Accelerated-Ray-Tracer


完成时间: 2026-03-01 05:35
迭代次数: 1 次(一次编译通过)
编译器: g++ (C++17, -O2)