八叉树

在写Ambient Occlusion时需要进行射线与物体的各个三角形的碰撞检测

但是逐个三角形进行判断需要进行大量的无意义计算

可以通过一个八叉树管理物体的包围盒空间

通过八叉树对物体的包围盒进行空间管理

这样判断射线是否与物体相交时可以省去大量的计算

当射线与某一个空间有相交的话,则进一步判断子空间与射线的相交性

八叉树_第1张图片


算法描述:

判断当前节点空间中的三角形数是否小于我们设置的一个阀值,若小于则该节点设置为叶子节点(即无子节点)

若大于该阀值,则将该空间分为8份,并判断出在各子空间中的三角形,然后再对其子节点进行判断


 将节点所在空间为分8份比较简单,假设当前节点所表示空间的包围盒为Bound

则Bound.Center为该包围盒的中心,Bound.Extents为该包围盒中心到各边的距离

八个子空间的长宽高都为父空间的一半,

八个子空间中心的坐标分别为Bound.Center向八个方向移动0.5f * Bound.Extents的距离



首先我们先分析下节点的结构体:

我们要通过八叉树管理空间,则所有的节点都需要维护一个包围盒,表示节点所表示的空间

且每个叶子空间中都应该维护一份索引列表,以便我们通过八叉树对物体进行碰撞检测等操作


结构体如下:

struct OctreeNode
{
    #pragma region Properties

     DirectX::BoundingBox Bounds;
     std::vector indices;           
     OctreeNode* Children[8];             //子节点
     
     bool Isleaf;                                   //是否为叶子节点

     #pragma endregion

     //......
};




并且定义一个函数将空间分为8份:

struct OctreeNode
{
    //.....
    
    void Subdivide( DirectX::BoundingBox box[8] )
    {
        // the half lenght of width , height , depth
        DirectX::XMFLOAT3 HalfExtent( 0.5f * Bounds.Extents.x ,
                                      0.5f * Bounds.Extents.y ,
                                      0.5f * Bounds.Extents.z );
        
        //
        // cacluate eight children node
        //

        // Top four quadrants
        box[0].Center = DirectX::XMFLOAT3( Bounds.Center.x + HalfExtent.x ,
                                           Bounds.Center.y + HalfExtent.y ,
                                           Bounds.Center.z + HalfExtent.z );
        box[0].Extents = HalfExtent;

        box[1].Center = DirectX::XMFLOAT3( Bounds.Center.x - HalfExtent.x ,
                                           Bounds.Center.y + HalfExtent.y ,
                                           Bounds.Center.z + HalfExtent.z );
        box[1].Extents = HalfExtent;

        box[2].Center = DirectX::XMFLOAT3( Bounds.Center.x - HalfExtent.x ,
                                           Bounds.Center.y + HalfExtent.y ,
                                           Bounds.Center.z - HalfExtent.z );
        box[2].Extents = HalfExtent;

        box[3].Center = DirectX::XMFLOAT3( Bounds.Center.x + HalfExtent.x ,
                                           Bounds.Center.y + HalfExtent.y ,
                                           Bounds.Center.z - HalfExtent.z );
        box[3].Extents = HalfExtent;


        // bottom for quadrants
        box[4].Center = DirectX::XMFLOAT3( Bounds.Center.x + HalfExtent.x ,
                                           Bounds.Center.y - HalfExtent.y ,
                                           Bounds.Center.z + HalfExtent.z );
        box[4].Extents = HalfExtent;

        box[5].Center = DirectX::XMFLOAT3( Bounds.Center.x - HalfExtent.x ,
                                           Bounds.Center.y - HalfExtent.y ,
                                           Bounds.Center.z + HalfExtent.z );
        box[5].Extents = HalfExtent;

        box[6].Center = DirectX::XMFLOAT3( Bounds.Center.x - HalfExtent.x ,
                                           Bounds.Center.y - HalfExtent.y ,
                                           Bounds.Center.z - HalfExtent.z );
        box[6].Extents = HalfExtent;

        box[7].Center = DirectX::XMFLOAT3( Bounds.Center.x + HalfExtent.x ,
                                           Bounds.Center.y - HalfExtent.y ,
                                           Bounds.Center.z - HalfExtent.z );
        box[7].Extents = HalfExtent;
    }
};



然后我们需要定义一个八叉树类为物体生成一棵八叉树,并且添加一些碰撞检测函数

class Octress
{
public:
	Octress();
	~Octress();

public:
	void Build(std::vector vertices , std::vector indices);

	void RayIntersect(DirectX::XMFLOAT3 RayPos , DirectX::XMFLOAT3 RayDir);

private:
	// 以该节点一棵生成八叉树
	void BuildOctree(OctreeNode* parent , std::vector indices);

	// 判断光线是否与当前节点所表示空间相交
	bool RayOctreeIntersect(OctreeNode* parent , DirectX::XMFLOAT3 RayPos , DirectX::XMFLOAT3 RayDir);

private:
	// 根节点
	OctreeNode* mRoot;

	// 维护一份顶点拷贝
	std::vector mVertices;
}


void Octree::Build(std::vector vertices , std::vector indices)
{
	// 保存一份顶点拷贝
	mVertices = vertices;

	// 首先根据顶点列表计算出该物体的包围盒
	...

	// 创建并填充root节点
	...

	// 通过根节点生成一棵八叉树
	BuildOctree(mRoot , indices);
}


bool BuildOctree(OctreeNode* parent , std::vector indices)
{
	// 判断当前节点的三角形数是否小于设定的阀值
	// 若小于,则设置该节点为叶子节点,不再分解
	if((indices.size() / 3) < threshold)
	{
		parent->indices = indices;
		parent->Isleaf = true;
	}
	else
	{
		// 继续分解空间
		DirectX::BoundingBox box[8];
		parent->Subdivide(box);

		// 将父空间的索引分配到相应的子空间中
		for(int i = 0; i < 8; i++)
		{
			parent->Children[i] = new OctreeNode;
			parent->Children[i]->Bounds = box[i];

			// 遍历各个三角形,若与子空间相交
			// 则认为该三角形属于该子空间
			std::vector IntersectIndices;
			for(int j = 0; j < indices.size() / 3; j++)
			{
				// 得到当前三角形的三个顶点
				...
					
				// 判断该三角形是否与该子空间相交
				...

				//若相交,则将索引入栈
				IntersectIndices.push_back(...);
				...

				//递归分解该子节点
				BuildOctree(parent->Children[i] , IntersectIndices);
			}
			
		}
		
	}
}


你可能感兴趣的:(DirectX,11)