线性BVH树并行构建方法
playerzhou
单条回复
线性BVH树并行构建方法
背景
- 传统BVH树采用递归的方式实现,不利于并行实现
前置步骤
构造一个排序好的叶子节点序列
目的
- 有利于合并操作,由于叶节点已经排序完毕,只需合并相邻叶节点或非叶节点即可构造出新的父节点
- 不需要重新进行排序操作来构造划分,从而使得构建无法并行化
要求
- 相邻叶子节点要尽可能靠近
做法
- 每一个AABB提供一个point(这里采用包围盒重心)
- 将该point坐标转换为Morton Code
- 根据Morton Code进行排序
原理
- 相当于将空间划分成一个个体素,每一个Morton Code代表一个体素
- 可类比于2D空间中的Z型曲线,大部分相临编码的体素在空间中相邻
- 可通过前缀将若干个小体素合并成大体素,符合BVH树的合并需求
缺点
- 该划分方式假定绝大部分三角面片足够小,仅存在于point附近的几个体素内
- 对于部分大的三角面片,查询效果较差,但依旧能够保证鲁棒性
BVH树合并标准
- 根据前缀和进行合并,有相同前缀和的的节点属于同一个非叶节点
- 从底向上前缀和逐渐变小,从而构建出树的层级、
- 如图所示
构建算法
伪代码
概念解释
-
叶子节点
- 每一个物体作为最小单元存储在叶子节点中
- 同时存储物体的包围盒信息,包括Morton Code
-
非叶节点
- 通过合并叶子节点/非叶节点得到
- 存储合并后的包围盒信息,包括Morton Code前缀
-
节点存储
- 使用两个数组分别存储叶子节点和非叶节点
- 数组L:存储叶子节点的数组
- 数组I:存储非叶节点的数组
- N:叶子节点数组大小,N-1:非叶节点数组大小
-
下标
- 叶子节点下标:代表在L中的位置
- 非叶节点下标
- 代表在I中位置
- 根节点下标=0
- 若该非叶节点为其父节点的左孩子,则非叶节点下标=所包含最右端叶子节点的下标
- 若该非叶节点为其父节点的右孩子,则非叶节点下标=所包含最左端叶子节点的下标
-
δ(i, j):
- 返回L[i]和L[j]节点的Morton Code的最长公共前缀长度
-
分割位置γ
- 若非叶节点K所包含的叶节点下标范围为[l,r]
- 则其左右子树包含的叶节点下标范围分别为[l,γ],[γ+1,r]
算法解析
// 该算法的目的是找到I[i]的左右子树,从而将其合并
for each internal node with index i ∈ [0,n n 2] in parallel
// Determine direction of the range (+1 or -1)
// 判断方向,若i为左孩子,L[i]为I[i]最右元素,则d=1,若i为右孩子,L[i]为I[i]最左元素,d=-1
d ← sign(δ(i,i+1) ) δ(i,i i 1))
// Compute upper bound for the length of the range
// 计算前缀长度的下界,由于i-d必然不属于i子树,假设i子树的前缀为δ_i,因此δ(i,i-d)<δ_i
δ_min ← δ(i,i-d)
l_max ← 2
// 由于i子树内的前缀长度必然大于δ_min,因此若δ(i,i+lmax*d)<=δ_min,则i+lmax*d不在i子树中
// 因此该循环找到一个下界,即i子树的另一个端点必然属于[i,i+lmax*d)
while δ(i,i+lmax*d) > δ_min do
lmax ← lmax*2
// Find the other end using binary search
// 使用二分查找找到I[i]的另一个端点j
l ← 0
for t ← {lmax/2,lmax/4,...,1} do
if δ(i,i+ (l +t)· d) > δmin then
l ← l +t
j ← i+l · d
// Find the split position using binary search
// 接下来查找I[i]的划分位置γ
// 同样使用二分查找的方式查找,这里由于方向问题,最后还要进行处理一下
// 若d=1,则查找到的结果是γ,反之则是γ+1,故此时需要再减去1
δnode ← δ(i, j)
s ← 0
for t ← {l/2, l/4,...,1} do
if δ(i,i+ (s+t)· d) > δnode then
s ← s+t
γ ← i+s· d +min(d,0)
// Output child pointers
// 判断子节点属于I数组还是L数组
if min(i, j) = γ then left ← Lγ else left ← Iγ
if max(i, j) = γ+1 then right ← Lγ+1 else right ← Iγ+1
// 合并结果
Ii ← (left,right)
end for
后置步骤
合并AABB
- 并行从所有叶子节点出发进行迭代更新
- 为每个非叶节点设置标记位
- 每次访问到非叶节点,采用原子操作将其递增,若访问非叶节点两次则合并该节点的两个子节点(由于每个非叶节点有且只有两个子节点,因此这样可以保证子节点的aabb都已更新完毕),反之则结束迭代
- 继续迭代该节点的父节点
- 缺点:每次迭代后,就有一半线程结束迭代闲置,但该操作耗时不大,这样的损失是可以接受的
查询
- 可采用BFS或DFS的方式进行查询,若查到某一个包围盒相交,则继续查询其子节点是否相交,直至查询到叶子节点
👍