首页 > 代码库 > [图形学] 简化的bsp树

[图形学] 简化的bsp树


简介

        bsp树是一种空间分割树,它主要用于游戏中的场景管理,尤其是室内场景的管理。

        它的本质是二叉树,也就是用一个面把空间分割成两部分,分割出的空间则继续用面分割,以此类推,直到到达特定递归深度,或者空间中不再有物体或只剩下一个物体(得到凸包/凸多面体)。

        最终,叶结点对应场景中的物体,内部结点存储分割面。物体被“收纳”到各个包围盒中。


应用


        bsp树对应的应用主要有两个方面:

        (1) 确定物体的遮挡关系,可视化处理。

        (2) 碰撞检测的广阶段。


       首先,要明确的一点是,bsp树是在设计地图时自动生成的树,它随之被保存到磁盘,也就是事先进行了预处理。在加载场景的时候,我们直接读入bsp文件,而不是重新生成。

      这也就意味着,作为预处理技术,bsp树只能处理静态的场景。


      bsp树本身的结构非常简单,但是这并不代表着bsp树的编程非常容易。其复杂性主要体现在:

      (1) bsp树不是独立存在的,它需要与前期的地图编辑器和后期的场景剔除/碰撞检测结合在一起。而这两者都是非常复杂的项目。

      (2) 选择合适的分割面,使得树尽可能平衡,并且能在恰当的时候停止分割。


       对于场景剔除而言,重点就是判断物体的前后关系,而这种空间拓扑关系在bsp树中已有了明显的体现。我们从根结点开始,根据摄像机所在位置和分割平面进行对比,很容易就能判断出结点的两个子空间与视点的前后关系。我们认为与视点在同一侧的为前面,在不同侧的为后面。

       对于碰撞检测而言,对所有物体都两两进行碰撞检测十分耗时,我们可以首先对物体进行初步排查,如果不处在同一个叶结点(包围盒),那么一定不会发生碰撞,通过简单的遍历树避免了繁琐的计算。


具体实现


      bsp树的编程比较复杂,在这里对bsp树做了最简化。之后的代码仅作为练习用,目的是更好地掌握bsp树。

      如果希望学习可用于商业引擎的bsp树,可参考quake3的地图编辑器。

      主要参考了《实时渲染》一书中给出的一个BSP结构,如下:

        技术分享

       简化部分 :

      (1) 使用二维,而不是三维。

      (2) 手工输入包围盒,而不是自动生成。包围盒为AABB包围盒(轴向),不支持上图中的凹多边形。

      (3) 叶结点和内部结点共用一个数据结构。其中内部结点存储了方向(水平或竖直)以及分割线;叶结点存储了对应场景为实心还是空心。

      (4) 沿着包围盒的边界进行分割(和图中所示相同),选择分割线的方法比较简单:

             1.分别选出水平和竖直方向的最优分割线。(判断标准:与空间中心最接近的包围盒边界线)

             2.如果最优水平分割线和场景中物体相交,而最优竖直分割线和场景中物体不相交,那么优先选择最优竖直分割线,反之亦然。

                如果都相交或都不相交,那么优先选择距离中心点更近的(按相对比例来算)

                如果选择的分割线与包围盒相交,那么把这个包围盒根据分割线拆成两个包围盒。

     (5) 退出条件(达到之一):

             1.达到最大分割层数,直接生成两个叶结点,返回。

             2.空间里没有物体了,返回空叶结点。

             3.空间里只剩下一个物体了,返回满叶结点。


            详细介绍已在代码注释中体现。


           技术分享

            结点数据结构


样例

        技术分享

           蓝线:第一次分割 ; 紫线:第二次分割 ; 黄线:第三次分割

        包围盒:

        (4,10,4,16)

        (10,24,4,9)

        (7,19,23,27)

        (22,28,13,24)

      

        bsp树

        技术分享


代码

bsp.h

#pragma once

#include<vector>

class bspTree
{
private:
	struct bspNode {
		bspNode* left;
		bspNode* right;
		bool isLeaf;//是否是叶结点
		bool isSolid;//是否实心
		bool isHori;//是否水平
		float data;//分割线
	};//结点数据结构
	struct box_t {
		float xmin;
		float xmax;
		float ymin;
		float ymax;
		box_t();
		void set(float x1, float x2, float y1, float y2);
	};//包围盒数据结构
	bspNode* root;//根节点
	std::vector<box_t>box;//包围盒容器
	float xmin, xmax, ymin, ymax;//整个场景的轴向包围盒
	int boxNum;//包围盒的个数
	int layer;//树的最大深度
	int layer_count;//记录当前层数
	bspNode* createEmptyNode();//生成空叶结点
	bspNode* createSolidNode();//生成非空叶结点
	void split(float& data_x, float& data_y, float& dis_x, float& dis_y,
		float xmin, float xmax, float ymin, float ymax);//寻找最优分割线
	bspNode* genNode(bool isFull_1, bool isFull_2, int layer_count,
		float xmin, float xmax, float ymin, float ymax, float data,bool isHori);//生成新的结点
	bspNode* build(int layer_count, float xmin, float xmax,
		float ymin, float ymax);//创建新的结点
	bool isIntersect(float xmin, float xmax, float ymin, float ymax, float data, bool isHori,std::vector<int>& id,int& num);
	//某一空间中的分割线是否与空间中的一个包围盒相交
	void traversal(bspNode* t);//前序遍历
	bool inBox(float x1, float x2, float y1, float y2, int id);//包围盒id是否完全处在某个空间中
	void checkIsFull(bool& isFull_x_1, bool& isFull_x_2, bool& isFull_y_1, bool& isFull_y_2,
		float xmin, float xmax, float ymin, float ymax, float data_x, float data_y);//检测分割出的两个区域是否为满/空
	bool isIntersect(float x1, float x2, float y1, float y2, int id);//包围盒id是否与某个空间有交集
public:
	bspTree(float x1, float x2, float y1, float y2, int l);//构造
	//前四个参数为场景包围盒,l为最大递归深度
	void add(float x1, float x2, float y1, float y2);//添加包围盒
	void build();//创建bsp树
	void print();//前序遍历输出
	void levelOrder();//层次遍历输出
};


bsp.cpp

#include"bsp.h"
#include<algorithm>
#include<queue>

bspTree::box_t::box_t() { }
void bspTree::box_t::set(float x1, float x2, float y1, float y2)
{
	xmin = x1;
	xmax = x2;
	ymin = y1;
	ymax = y2;
}

bspTree::bspTree(float x1, float x2, float y1, float y2, int l)
{
	xmin = x1;
	xmax = x2;
	ymin = y1;
	ymax = y2;
	layer = l;
	boxNum = 0;
	layer_count = 0;
	root = nullptr;
}

void bspTree::add(float x1, float x2, float y1, float y2)
{
	box_t b;
	boxNum++;
	b.set(x1, x2, y1, y2);
	box.push_back(b);
}
//某一空间中的分割线是否与空间中的一个包围盒相交
bool bspTree::isIntersect(float xmin,float xmax,float ymin,float ymax,float data, bool isHori, std::vector<int>& id,int& num)
{
	bool flag = false;//记录是否存在交
	//分割线是水平的
	if (isHori) {
		//遍历所有包围盒
		for (int i = 0; i < boxNum; i++) {
			//如果包围盒完全处在空间中
			if (inBox(xmin, xmax, ymin, ymax, i)) {
				num++;//记录包围盒个数+1
				if (data > box[i].xmin && data < box[i].xmax) { //存在交
					id.push_back(i);//记录包围盒id
					flag = true;//存在交 为真
				}
			}
		}
	}
	//分割线是竖直的
	else if (!isHori) {
		//遍历所有包围盒
		for (int i = 0; i < boxNum; i++) {
			//如果包围盒完全处在空间中
			if (inBox(xmin, xmax, ymin, ymax, i)) {
				num++;//记录包围盒个数+1
				if(data > box[i].ymin&&data < box[i].ymax) {//存在交
					id.push_back(i);//记录包围盒id
					flag = true;//存在交 为真
		    	}
			}
		}
	}

	return flag;
}

//包围盒id是否完全处在某个空间中
bool bspTree::inBox(float x1, float x2, float y1, float y2,int id)
{
	return box[id].xmin >= x1 && box[id].xmax<=x2 &&
		box[id].ymin>=y1 && box[id].ymax <= y2;
}

//寻找最优分割线
void bspTree::split(float& data_x, float& data_y, float& dis_x, float& dis_y,
	float xmin, float xmax, float ymin, float ymax)
{
	float d = 10000;
	//先计算竖直方向

	//遍历所有包围盒
	for (int i = 0; i < boxNum; i++) {
		//如果包围盒完全处在空间中
		if (inBox(xmin,xmax,ymin,ymax,i)) {
			//计算包围盒边界线到中心的距离
			d = box[i].xmin - ((xmax - xmin) / 2 + xmin);
			if (d < 0)d = -d;
			//如果有更小的距离,更新距离和分割线
			if (d < dis_x) {
				dis_x = d;
				data_x = box[i].xmin;
			}

			//计算包围盒边界线到中心的距离
			d = box[i].xmax - ((xmax - xmin) / 2 + xmin);
			if (d < 0) d = -d;
			//如果有更小的距离,更新距离和分割线
			if (d < dis_x) {
				dis_x = d;
				data_x = box[i].xmax;
			}
		}
	}
	//再计算水平方向
	//遍历所有包围盒
	for (int i = 0; i < boxNum; i++) {
		//如果包围盒完全处在空间中
		if (inBox(xmin, xmax, ymin, ymax, i)) {
			//计算包围盒边界线到中心的距离
			d = box[i].ymin - ((ymax - ymin) / 2 + ymin);
			if (d < 0)d = -d;
			//如果有更小的距离,更新距离和分割线
			if (d < dis_y) {
				dis_y = d;
				data_y = box[i].ymin;
			}
			//计算包围盒边界线到中心的距离
			d = box[i].ymax - ((ymax - ymin) / 2 + ymin);
			if (d < 0)d = -d;
			//如果有更小的距离,更新距离和分割线
			if (d < dis_y) {
				dis_y = d ;
				data_y = box[i].ymax;
			}
		}
	}
	//计算相对距离
	dis_x /= xmax - xmin;
	dis_y /= ymax - ymin;
}

//创建空叶结点
bspTree::bspNode* bspTree::createEmptyNode()
{
	bspNode* node = new bspNode();
	node->left = nullptr;
	node->right = nullptr;
	node->isLeaf = true;
	node->isSolid = false;
	node->data = http://www.mamicode.com/0.0f;>
main.cpp

#include "bsp.h"
#include<stdlib.h>
int main()
{
	bspTree* t = new bspTree(1,33,1,33,3);
	t->add(4, 10, 4, 16);
	t->add(10, 24, 4, 9);
	t->add(7, 19, 23, 27);
	t->add(22, 28, 13, 24);
	t->build();
	t->print();
	printf("\n");
	t->levelOrder();
	system("pause");
}



[图形学] 简化的bsp树