[图形学] 简化的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 = 0.0f;
	return node;
}

//创建非空叶结点
bspTree::bspNode* bspTree::createSolidNode()
{
	bspNode* node = new bspNode();
	node->left = nullptr;
	node->right = nullptr;
	node->isLeaf = true;
	node->isSolid = true;
	node->data = 0.0f;
	return node;
}

//生成结点
bspTree::bspNode* bspTree::genNode(bool isFull_1,bool isFull_2,int layer_count,
	float xmin,float xmax,float ymin,float ymax,float data,bool isHori)
{
	bspNode* node = new bspNode();//申请
	if (!root) { //指定根
		root = node;
	}
	//如果没有到达最大的深度
	if (layer != layer_count) {
		//如果区域1是满的
		if (isFull_1) {
			//递归创建
			if(isHori)node->left = build(layer_count + 1,xmin, xmax,ymin,data);
			else node->left = build(layer_count + 1, xmin, data, ymin, ymax);
		}
		//如果区域1是空的
		else {
			//直接创建空叶结点,不继续递归
			node->left = createEmptyNode();
		}
		//如果区域2是满的
		if (isFull_2) {
			//递归创建
			if(isHori)node->right = build(layer_count + 1, xmin, xmax,data,ymax);
			else node->right = build(layer_count + 1, data, xmax, ymin, ymax);
		}
		//如果区域2是空的
		else {
			//直接创建空叶结点,不继续递归
			node->right = createEmptyNode();
		}
	}
	//如果达到了最大深度
	else if (layer ==layer_count) {
		//如果区域1是满的
		if (isFull_1) {
			//直接创建满叶结点,不继续递归
			node->left = createSolidNode();
		}
		//如果区域1是空的
		else {
			//直接创建空叶结点,不继续递归
			node->left = createEmptyNode();
		}
		//如果区域2是满的
		if (isFull_2) {
			//直接创建满叶结点,不继续递归
			node->right = createSolidNode();
		}
		//如果区域2是空的
		else {
			//直接创建空叶结点,不继续递归
			node->right = createEmptyNode();
		}
	}
	//设置结点基本信息
	node->isLeaf = false;
	node->isHori = isHori;
	node->data = data;
	return node;
}

//包围盒id是否与某个空间有交集
bool bspTree::isIntersect(float x1, float x2, float y1, float y2, int id)
{
	//两种情况:
	// 1. 水平,竖直方向都各有至少一条边界线落在区域内(不含恰好落在区域边界)
	// 2. 水平方向两条边界线都落在区域边界,或竖直方向两条边界线都落在区域边界
	return ((box[id].xmin > x1 && box[id].xmin<x2 ||
		box[id].xmax>x1 && box[id].xmax<x2||
		box[id].xmin == x1 && box[id].xmax==x2) &&
		(box[id].ymin>y1&&box[id].ymin<y2 ||
		box[id].ymax>y1&&box[id].ymax < y2)||
		box[id].ymin==y1&&box[id].ymax==y2);
}

//检测分割出的两个区域是否为满/空
void bspTree::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)
{
	//遍历所有包围盒,如果有包围盒与该空间存在交集,那么这个空间就是满的
	for (int i = 0; i < boxNum; i++) {
		if (!isFull_x_1 && isIntersect(xmin, data_x, ymin, ymax,i)) {
			isFull_x_1 = true;
		}
		if (!isFull_x_2 && isIntersect(data_x, xmax, ymin, ymax, i)) {
			isFull_x_2 = true;
		}
		if (!isFull_y_1 && isIntersect(xmin, xmax, ymin, data_y, i)) {
			isFull_y_1 = true;
		}
		if (!isFull_y_2 && isIntersect(xmin, xmax, data_y, ymax, i)) {
			isFull_y_2 = true;
		}
	}
	return;
}

//创建bsp树
bspTree::bspNode* bspTree::build(int layer_count, float xmin, float xmax,
	float ymin, float ymax)
{
	//printf("%f %f %f %f\n", xmin, xmax, ymin, ymax);
	//超过递归深度直接返回NULL
	if (layer_count == layer + 1)return nullptr;
	bspNode* node = nullptr;

	//初始化一些变量:距离,分割线,是否相交,子空间空/满状态,相交包围盒的id,空间内包围盒的个数
	float dis_x = 10000;
	float dis_y = 10000;
	float data_x = -1;
	float data_y = -1;
	bool isIntersect_x;
	bool isIntersect_y;
	bool isFull_x_1 = false;
	bool isFull_x_2 = false;
	bool isFull_y_1 = false;
	bool isFull_y_2 = false;
	std::vector<int>id_x;
	std::vector<int>id_y;
	int num_x = 0;
	int num_y = 0;

	split(data_x, data_y, dis_x, dis_y, xmin, xmax, ymin, ymax);//找到预备的最优分裂线

	//两者未赋值,说明没有可以选择的包围盒,也就是空间是空的,直接返回空叶节点
	if (data_x == -1 && data_y == -1) {
		return createEmptyNode();
	}

	//判断最优分裂线与包围盒是否相交
	isIntersect_x = isIntersect(xmin,xmax,ymin,ymax,data_x, true,id_x,num_x);
	isIntersect_y = isIntersect(xmin,xmax,ymin,ymax,data_y, false,id_y,num_y);
	//判断分割的子空间为空/满
	checkIsFull(isFull_x_1, isFull_x_2, isFull_y_1, isFull_y_2, xmin, xmax, ymin, ymax, data_x, data_y);
	//空间中只有一个物体,直接返回满叶结点
	if (num_x == 1)return createSolidNode();

	//竖直分割线相交,水平分割线不相交,选择水平分隔线
	if (isIntersect_x && !isIntersect_y) {
		node = genNode(isFull_y_1, isFull_y_2, layer_count, xmin, xmax, ymin, ymax, data_y,true);
	}
	//竖直分割线不相交,水平分割线相交,选择竖直分隔线
	else if (!isIntersect_x && isIntersect_y) {
		node = genNode(isFull_x_1, isFull_x_2, layer_count, xmin, xmax, ymin, ymax, data_x,false);
	}
	//都相交 或都不相交,选择距离中心近的
	else {
		//竖直更近
		if (dis_x < dis_y) {
			//如果存在相交,分裂包围盒
			if (isIntersect_x) {
				for (int i = 0; i < id_x.size(); i++) {
					float x1 = box[id_x[i]].xmin;
					float x2 = box[id_x[i]].xmax;
					float y1 = box[id_x[i]].ymin;
					float y2 = box[id_x[i]].ymax;
					boxNum++;
					box[id_x[i]].set(x1, data_x, y1, y2);
					box_t b;
					b.set(data_x, x2, y1, y2);
					box.push_back(b);
				}
				id_x.clear();
			}
			node = genNode(isFull_x_1, isFull_x_2, layer_count, xmin, xmax, ymin, ymax, data_x,false);
		}
		//水平更近
		else {
			//如果存在相交,分裂包围盒
			if (isIntersect_y) {
				for (int i = 0; i < id_y.size(); i++) {
					float x1 = box[id_y[i]].xmin;
					float x2 = box[id_y[i]].xmax;
					float y1 = box[id_y[i]].ymin;
					float y2 = box[id_y[i]].ymax;
					boxNum++;
					box[id_y[i]].set(x1, x2, y1, data_y);
					box_t b;
					b.set(x1, x2, data_y, y2);
					box.push_back(b);
				}
				id_y.clear();
			}
			node = genNode(isFull_y_1, isFull_y_2, layer_count, xmin, xmax, ymin, ymax, data_y,true);
		}
	}
	return node;
}

//创建入口
void bspTree::build()
{
	build(1, xmin, xmax, ymin, ymax);
}

//前序输出
void bspTree::print()
{
	traversal(root);
}

//前序
void bspTree::traversal(bspNode* t)
{
	if (!t)return;
	if (t->data != 0)printf("%f ", t->data);
	else printf("leaf:%d", t->isSolid);
	if (t->isHori)printf("h\n");
	else printf("v\n");
	traversal(t->left);
	traversal(t->right);
}

//层序
void bspTree::levelOrder()
{
	std::queue<bspNode*>q;
	q.push(root);
	while (!q.empty()) {
		bspNode* t = q.front();
		if(t->data!=0)printf("%f ", t->data);
		else printf("leaf:%d", t->isSolid);
		if (t->isHori)printf("h\n");
		else printf("v\n");
		q.pop();
		if (t->left != nullptr)q.push(t->left);
		if (t->right != nullptr)q.push(t->right);
	}
	return;
}

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");
}
时间: 2024-10-12 02:23:31

[图形学] 简化的bsp树的相关文章

Python爬取CSDN博客文章

之前解析出问题,刚刚看到,这次仔细审查了 0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.windows下环境搭建 3.java里连接redis数据库 4.关于认证 5.redis高级功能总结1.基础底层数据结构1.1.简单动态字符串SDS定义: ...47分钟前1 url :http://blog.csdn.net/youyou1543724847/

【转载】计算机图形学框架

原文: 计算机图形学框架 应用 基本图形生成算法 图元光栅化标准 直线要直 图元终点要准 图元生成的亮度.色泽粗细要均匀 快速计算 直线光栅化算法 逐点比较法 数值微分法 中点Bresenham算法 圆的光栅化算法 简单方程产生圆弧 Bresenham算法产生圆弧 多边形填充 扫描线填充 宽图元 复制像素画宽图元 移动画笔画宽图元 3D数学基础 坐标系 向量 矩阵 空间集合运算 集合形体的表达 几何体之间的关系 图形变换 二维及三维图形几何变换 二维图形几何变换 平移变换 比例变换 旋转变换 错

图形学复习

交互式计算机图形学(第五版)1-7章课后题答案 计算机图形学基础答案全 计算机图形学考试重点计算题 What is the difference between Gouraud and Phong shading? Gouraud shading (AKA Smooth Shading) is a per-vertex color computation. What this means is that the vertex shader must determine a color for e

[图形学] 《Real-Time Rendering》碰撞检测(一)

原文有35页,容我慢慢翻译,第一部分翻译了10页 reference:<Real-Time Rendering> 目录 17   前言 17.1 和射线的碰撞检测 17.2 使用BSP树的动态碰撞检测 17.3 一般层次的碰撞检测 17.3.1 分层的构建 17.3.2 不同层之间的碰撞检测 17.3.3 代价函数 17.4 OBB树 17.5 多重物体碰撞检测系统 17.5.1 广阶段的碰撞检测 17.5.2 总结 17.6 更多样的话题 17.6.1 及时碰撞检测 17.6.2 距离查询

数学之美--计算机图形学中的数学方法论

我的github: https://github.com/jackyblf 我的公众号: 早期研究3D程序开发的程序员最大的情怀就是从无到有,自己实现一个游戏引擎.当时的我也是充满着这种复古情怀!! 以前的demo大部分都用c++写的,现在尽力全部改成html5+canvas/webgl版本 正在改写demo,编写文章,敬请期待 09年台式机out of order了,一直到去年年底,买了个硬盘数据线,把以前的代码全部拷贝出来了,万幸没损坏!!! 08年的vs2005代码,在vs2015中,竟然

动态树 Link-Cut Trees

动态树 动态树问题, 即要求我们维护一个由若干棵子结点无序的有根树组成的森林. 要求这个数据结构支持对树的分割.合并,对某个点到它的根的路径的某些操作,以及对某个点的子树进行的某些操作. 在这里我们考虑一个简化的动态树问题,它只包含对树的形态的操作和对某个点到根的路径的操作: 维护一个数据结构,支持以下操作: • MAKE TREE() — 新建一棵只有一个结点的树. • CUT(v) — 删除 v 与它的父亲结点 parent(v) 的边,相当于将点 v 的子树分离了出来. • JOIN(v,

树状数组(Binary Indexed Tree,BIT)

树状数组(Binary Indexed Tree) 前面几篇文章我们分享的都是关于区间求和问题的几种解决方案,同时也介绍了线段树这样的数据结构,我们从中可以体会到合理解决方案带来的便利,对于大部分区间问题,线段树都有其绝对的优势,今天这篇文章,我们就来欣赏由线段树变形的另外一个数据结构--树状数组,树状数组通常也用于解决区间求和.单点更新的问题,而且效率比线段树高一些(树状数组区间求和和单点更新的时间复杂度均为o(log n)),相对而言,线段树的应用范围可能更广泛一些.但不得不承认,树状数组确

《计算机图形学》学习笔记 0

书:https://book.douban.com/subject/1392483/ 版本:第三版 内容: 图形系统当前的硬件和软件组成.分形几何.光线追踪.样条.光照模型.表面绘制.计算机动画.虚拟现实.图形算法的并行实现.反走样.超二次曲面.BSP树.粒子系统.基于物理的建模.科学计算可视化.辐射度.凹凸映射和变形 主要扩展的内容: 动画.对象表示.三维观察流水线.光照模型.表面绘制技术.纹理映射. 数学基础: 解析几何.线性代数.向量和张量分析.复数.四元数.微积分初步.数值分析.

BSP地图文件格式

参考:http://www.cnblogs.com/zengqh/archive/2012/08/03/2621307.html 及irrlicht0.1源代码CQ3LevelMesh struct tBSPHeader {     s32 strID;         // This should always be 'IBSP'     s32 version;       // This should be 0x2e for Quake 3 files }; Entities(实体) 存储