在写Ambient Occlusion时需要进行射线与物体的各个三角形的碰撞检测
但是逐个三角形进行判断需要进行大量的无意义计算
可以通过一个八叉树管理物体的包围盒空间
通过八叉树对物体的包围盒进行空间管理
这样判断射线是否与物体相交时可以省去大量的计算
当射线与某一个空间有相交的话,则进一步判断子空间与射线的相交性
算法描述:
判断当前节点空间中的三角形数是否小于我们设置的一个阀值,若小于则该节点设置为叶子节点(即无子节点)
若大于该阀值,则将该空间分为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);
}
}
}
}