您现在的位置是:首页 >技术交流 >C++ 构建 BVH 树网站首页技术交流
C++ 构建 BVH 树
简介C++ 构建 BVH 树
在 C++ 中构建 BVH 树 (Bounding Volume Hierarchy)涉及几个关键步骤,主要包括定义包围盒、构建树结构、递归划分空间等。这里我们将从基础的概念出发,介绍如何使用 C++ 构建 BVH 树。
1. 定义包围盒(Bounding Volume)
在 BVH 中,每个节点通常包含一个包围盒,常见的包围盒类型是 AABB(Axis-Aligned Bounding Box),它是与坐标轴对齐的矩形盒子。
首先,我们定义一个简单的 AABB 类来表示包围盒:
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits>
struct Vec3 {
float x, y, z;
Vec3() : x(0), y(0), z(0) {}
Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
// 向量减法
Vec3 operator-(const Vec3& other) const {
return Vec3(x - other.x, y - other.y, z - other.z);
}
// 向量加法
Vec3 operator+(const Vec3& other) const {
return Vec3(x + other.x, y + other.y, z + other.z);
}
// 计算向量的最大值
static Vec3 max(const Vec3& a, const Vec3& b) {
return Vec3(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z));
}
// 计算向量的最小值
static Vec3 min(const Vec3& a, const Vec3& b) {
return Vec3(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z));
}
};
struct AABB {
Vec3 min, max; // 包围盒的最小和最大坐标
AABB() {
min = Vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
max = Vec3(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
}
AABB(const Vec3& min, const Vec3& max) : min(min), max(max) {}
// 更新包围盒
void expand(const Vec3& point) {
min = Vec3::min(min, point);
max = Vec3::max(max, point);
}
// 判断两个包围盒是否相交
bool intersect(const AABB& other) const {
return (max.x >= other.min.x && min.x <= other.max.x) &&
(max.y >= other.min.y && min.y <= other.max.y) &&
(max.z >= other.min.z && min.z <= other.max.z);
}
};
2. 定义树节点结构
BVH 树的节点可以是两种类型:内部节点(包含子节点)和叶子节点(包含物体或三角形)。在这里我们定义一个简单的树节点类:
struct BVHNode {
AABB bbox; // 包围盒
BVHNode* left; // 左子节点
BVHNode* right; // 右子节点
std::vector<int> objects; // 对应物体的索引(叶子节点)
// 叶子节点构造
BVHNode(const AABB& bbox, const std::vector<int>& objects)
: bbox(bbox), left(nullptr), right(nullptr), objects(objects) {}
// 内部节点构造
BVHNode(const AABB& bbox, BVHNode* left, BVHNode* right)
: bbox(bbox), left(left), right(right) {}
};
3. 构建 BVH 树
构建 BVH 树的核心步骤是递归地分割物体集合,并为每个子集构建包围盒。通常的构建方法是基于分割轴的选择,常见的策略包括:
- 中位数分割(Median Split):将物体集按某一轴(通常是 X、Y 或 Z)上的中位数进行分割。
- 表面面积启发式(SAH):通过计算不同分割方式的表面面积,选择最佳的分割方案。
这里我们先实现一个简单的 中位数分割:
class BVH {
public:
BVH(const std::vector<Vec3>& vertices) : vertices(vertices) {}
BVHNode* build(std::vector<int>& objects) {
return buildRecursive(objects, 0, objects.size());
}
private:
std::vector<Vec3> vertices;
BVHNode* buildRecursive(std::vector<int>& objects, int start, int end) {
if (end - start <= 1) {
// 如果只有一个物体,返回叶子节点
AABB bbox = computeBoundingBox(objects, start, end);
return new BVHNode(bbox, {objects[start]});
}
// 计算包围盒
AABB bbox = computeBoundingBox(objects, start, end);
// 寻找分割轴,这里简单使用X轴
int axis = 0; // 0代表X轴,1代表Y轴,2代表Z轴
std::sort(objects.begin() + start, objects.begin() + end, [this, axis](int a, int b) {
return vertices[a][axis] < vertices[b][axis];
});
// 中位数分割
int mid = (start + end) / 2;
// 递归构建左右子树
BVHNode* left = buildRecursive(objects, start, mid);
BVHNode* right = buildRecursive(objects, mid, end);
// 创建并返回当前节点
return new BVHNode(bbox, left, right);
}
// 计算物体集合的包围盒
AABB computeBoundingBox(const std::vector<int>& objects, int start, int end) {
AABB bbox;
for (int i = start; i < end; ++i) {
bbox.expand(vertices[objects[i]]);
}
return bbox;
}
};
4. 例子:构建 BVH 树
现在我们可以使用上面的类来构建一个简单的 BVH 树。假设我们有一些物体的顶点位置(vertices),以及它们在这些顶点上所代表的物体索引(objects)。
int main()
{
// 假设这些是三维物体的顶点位置
std::vector<Vec3> vertices = {
Vec3(1.0f, 2.0f, 3.0f),
Vec3(4.0f, 5.0f, 6.0f),
Vec3(7.0f, 8.0f, 9.0f),
Vec3(2.0f, 3.0f, 1.0f),
Vec3(5.0f, 6.0f, 7.0f)
};
// 假设物体的索引
std::vector<int> objects = {0, 1, 2, 3, 4};
BVH bvh(vertices);
BVHNode* root = bvh.build(objects);
std::cout << "BVH Tree Built!" << std::endl;
return 0;
}
5. 后续优化与扩展
- 分割策略:目前的实现是简单的中位数分割,可以考虑实现表面面积启发式(SAH)优化来选择最佳的分割方式。
- 内存管理:如果对象较多,可以使用智能指针(如
std::unique_ptr)来管理内存。 - 动态更新:当场景物体发生变化时,BVH树的更新策略可以是重建树,或通过增量方式更新树。
这个简单的实现展示了如何使用 C++ 构建一个基础的 BVH 树。对于真实的应用,你可以根据需要优化树的构建过程,选择不同的分割策略以及考虑性能的平衡。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。





QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
U8W/U8W-Mini使用与常见问题解决
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结