日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

BVH树的构建与遍历

發布時間:2023/12/13 综合教程 31 生活家
生活随笔 收集整理的這篇文章主要介紹了 BVH树的构建与遍历 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在計算機圖形學中,BVH樹是一種空間劃分的數據結構,廣泛運用于光線追蹤。今天來講述一下它的建立和遍歷方法。

BVH樹的建立

BVH樹的建立分為以下幾步:
1.遍歷當前場景中的所有物體,存儲下它們的每一個圖元(primitive,例如三角形、圓形等);對每一個圖元,計算它們的包圍盒。
2.遞歸構建BVH樹。
BVH樹是一種二叉樹,每一個節點記錄了它自己的包圍盒。對于葉子節點,它存儲了它所包含的所有圖元;對于非葉子節點,記錄了它所包含的孩子節點。節點的定義如下:

struct BVHBuildNode {
    BVHBuildNode* children[2];
    BoundingBox boundingbox;
    int splitAxis, firstPrimeOffset, nPrimitives;
    void initLeaf(int first, int n, const BoundingBox&b);
    void initInterior(int axis, BVHBuildNode*c0, BVHBuildNode*c1);
};

接下來展示遞歸建立BVH樹的代碼:

BVHBuildNode* BVHManager::recursiveBuild(int start, int end, int* totalnodes, std::vector<Primitive*>& ordered_prims)
{
	BVHBuildNode* node = nullptr;
	(*totalnodes)++;
	int nPrimitives = end - start;
	BoundingBox bounds;
	for (int i = start; i < end; i++)
		bounds = BoundingBox::Union(bounds, primitives[i]->getBoundingBox());
	if (nPrimitives == 1)
		node = createLeafNode(start, end, totalnodes, ordered_prims, bounds);
	else if(nPrimitives > 1)
	{
		int dim = bounds.maximumExtent();
		if(bounds.getTopFromDim(dim)==bounds.getBottomFromDim(dim))
			node = createLeafNode(start, end, totalnodes, ordered_prims, bounds);
		else
		{
			int mid = partitionPrimitivesWithSAH(start, end, dim, bounds);
			if(mid < 0)
				node = createLeafNode(start, end, totalnodes, ordered_prims, bounds);
			else {
				node = new BVHBuildNode;
				node->initInterior(dim,
					recursiveBuild(start, mid, totalnodes, ordered_prims),
					recursiveBuild(mid, end, totalnodes, ordered_prims));
			}
		}
	}
	return node;
}

這里最重要的步驟就是給定一個節點及其包圍盒,如何對它進行空間劃分。在這里我們采用SAH(Surface Area Heuristic)算法。該算法首先尋找boundingbox中跨度最長的一個軸作為用來分割的軸,然后沿著該軸N等分為一個個塊,最后根據代價公式遍歷每一個塊,如圖所示:

我們要做的是尋找出從哪一個塊開始分割,使得代價最小。代價公式如下所示:

A和B是由當前的包圍盒分割出的兩個子模塊,t(trav)和t(isect)我們可以當做是常數,pA和pB代表光線打到兩個子塊的概率,我們用兩個子塊相對于父親的面積來計算。
這樣一來,就可以寫出計算SAH的代碼:

int BVHManager::partitionPrimitivesWithSAH(int start, int end, int dim, BoundingBox& bounds)
{
	int nPrimitives = end - start;
	int nBuckets = BVHManager::nBuckets;
	if (nPrimitives <= 4)
		return partitionPrimitivesWithEquallySizedSubsets(start, end, dim);
	else
	{
		for (int i = start; i < end; i++)
		{
			BoundingBox prim_bounds = primitives[i]->getBoundingBox();
			int b = nBuckets * 
				(prim_bounds.getCenterValFromDim(dim) - bounds.getBottomFromDim(dim)) /
				(bounds.getTopFromDim(dim) - bounds.getBottomFromDim(dim));
			if (b == nBuckets)
				b--;
			buckets[b].count++;
			buckets[b].bounds = BoundingBox::Union(buckets[b].bounds, prim_bounds);
		}
		float cost[BVHManager::nBuckets - 1];
		for (int i = 0; i < nBuckets - 1; i++)
		{
			BoundingBox b0, b1;
			int count0 = 0, count1 = 0;
			for (int j = 0; j <= i; j++)
			{
				b0 = BoundingBox::Union(b0, buckets[j].bounds);
				count0 += buckets[j].count;
			}

			for (int j = i+1; j < BVHManager::nBuckets; j++)
			{
				b1 = BoundingBox::Union(b1, buckets[j].bounds);
				count1 += buckets[j].count;
			}
			float val0 = count0 ? count0 * b0.surfaceArea() : 0.0f;
			float val1 = count1 ? count1 * b1.surfaceArea() : 0.0f;

			cost[i] = 0.125f + (val0 + val1) / bounds.surfaceArea();
		}

		float min_cost = cost[0];
		int min_ind = 0;
		for (int i = 0; i < BVHManager::nBuckets -1; i++)
		{
			if (cost[i] < min_cost)
			{
				min_cost = cost[i];
				min_ind = i;
			}
		}
		if (nPrimitives > maxPrimsInNode || min_cost < nPrimitives)
		{
			Primitive** p = std::partition(&primitives[start], &primitives[end - 1] + 1,
				[=](const Primitive* pi) {
				int b = nBuckets *
					(pi->getBoundingBox().getCenterValFromDim(dim) - bounds.getBottomFromDim(dim)) /
					(bounds.getTopFromDim(dim) - bounds.getBottomFromDim(dim));
				if (b == nBuckets)
					b--;
				return b <= min_ind;
			});
			return p - &primitives[0];
		}
		else
			return -1;
	}
}

經過上面的步驟后,就可以對空間進行劃分,建立出SAH樹。
建立完BVH樹后,為了節省空間和提高遍歷的性能,我們需要將這個二叉樹的結構壓縮到一個線性數組中。做到:
1.初始節點是數組中第一個元素
2.對于非葉子節點,它的第一個孩子就是數組中的下一個元素,同時它會存儲第二個孩子的索引
3.對于葉子節點,它會記錄自己包含的圖元
下圖是線性化二叉樹的示意圖:

具體的線性化二叉樹節點定義及建立過程如下:

struct LinearBVHNode {
	BoundingBox boundingbox;
	union 
	{
		int primitivesOffset;  // leaf
		int secondChildOfset;  // interior
	};
	int nPrimitives;
	int axis;
};

int BVHManager::flattenBVHTree(BVHBuildNode* node, int* offset)
{
	LinearBVHNode& lnode = linear_nodes[*offset];
	lnode.boundingbox = node->boundingbox;
	int myOffset = (*offset)++;

	if (node->nPrimitives > 0)
	{
		lnode.primitivesOffset = node->firstPrimeOffset;
		lnode.nPrimitives = node->nPrimitives;
	}
	else
	{
		lnode.axis = node->splitAxis;
		lnode.nPrimitives = 0;
		flattenBVHTree(node->children[0], offset);
		lnode.secondChildOfset = flattenBVHTree(node->children[1], offset);
	}
	return myOffset;
}

經過上面的一系列步驟,我們就將BVH樹建立了起來,可以用于實戰了。

BVH樹的遍歷

在進行投射光線,尋找場景中的交點時,就可以遍歷BVH樹來加速。BVH樹的遍歷和線性化二叉樹的遍歷基本一致,代碼如下:

float min_t = -1.0f;
auto bvh_nodes = objectManager.getBVHManager()->getBvhNodes();
std::stack<int> nodesToVisit;
int cur_node_index = 0;
while (true)
{
	LinearBVHNode node = bvh_nodes[cur_node_index];
	if (node.boundingbox.isIntersect(ray, XMMatrixIdentity()))
	{
		if (node.nPrimitives > 0)
		{
			for (int i = 0; i < node.nPrimitives; i++)
			{
				float t=-1.0f;
				Primitive* p = objectManager.getBVHManager()->getPrimitive(node.primitivesOffset + i);
				IntersectInfo it;
				if (p->is_intersect(ray, t, it) && (min_t < 0.0f || t < min_t))
				{
					min_t = t;
					info = it;
				}
			}
			if (nodesToVisit.empty())
				break;
			cur_node_index = nodesToVisit.top();
			nodesToVisit.pop();
		}
		else
		{
		if(ray.dirIsNeg(node.axis))
		{
			nodesToVisit.push(cur_node_index + 1);
			cur_node_index = node.secondChildOfset;

		}
		else
		{
			nodesToVisit.push(node.secondChildOfset);
			cur_node_index++;
		}
		}
	    }
	else
	{
		if (nodesToVisit.empty())
			break;
		cur_node_index = nodesToVisit.top();
		nodesToVisit.pop();
	}
}

注意代碼中有一處判斷光線的方向是否為負的地方,它是為了讓當前最接近光線方向的孩子包圍盒首先被搜尋,這樣在搜尋第二個孩子的時候,如果進行的包圍盒相交判斷得到t值比之前存儲的最小t值大的話,就無需再進一步深入該子節點進行相交檢測,可以節省一定的計算量。
如圖就是我根據一個Mesh建立出的BVH樹的包圍盒:

經我測試,加入BVH樹后,對于上圖的Mesh,進行光線相交檢測的速度提高了近25倍。

總結

以上是生活随笔為你收集整理的BVH树的构建与遍历的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。