1. 程式人生 > >DirectX11 With Windows SDK--18 使用DirectXCollision庫進行碰撞檢測

DirectX11 With Windows SDK--18 使用DirectXCollision庫進行碰撞檢測

視錐 amp 撞庫 href 定義 方法 描述 關系 .cn

前言

在DirectX SDK中,碰撞檢測的相關函數位於xnacollision.h中。但是現在,前面所實現的相關函數都已經轉移到Windows SDK的DirectXCollision.h中,並且處於名稱空間DirectX內。這裏面主要包含了四種包圍盒(Bounding Volumes),並且是以類的形式實現的:

  1. BoundingSphere類--包圍球(Bounding Box)
  2. BoundingBox類--軸對齊包圍盒(Axis-aligned bounding box),又稱AABB盒
  3. BoundingOrientedBox類--有向包圍盒(Oriented bounding box),又稱OBB盒
  4. BoundingFrustum類--包圍視錐體(Bounding Frustum)

除此之外裏面還包含有三角形(射線)與其余物體的碰撞檢測。

後續的項目將會使用該碰撞庫。

DirectX11 With Windows SDK完整目錄

Github項目源碼

常見包圍盒

包圍球(Bounding Box)

技術分享圖片

一個球體只需要使用圓心坐標和半徑就可以表示。結構體的一部分如下:

struct BoundingSphere
{
    XMFLOAT3 Center;            // 球體中心坐標
    float Radius;               // 球體半徑
    
    // 構造函數
    BoundingSphere() : Center(0,0,0), Radius( 1.f ) {}
    XM_CONSTEXPR BoundingSphere(const XMFLOAT3& center, float radius )
        : Center(center), Radius(radius) {}
    BoundingSphere(const BoundingSphere& sp )
        : Center(sp.Center), Radius(sp.Radius) {}
        
    // ...
    
    // 靜態創建方法
    static void CreateMerged(BoundingSphere& Out, const BoundingSphere& S1, const BoundingSphere& S2 );

    static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingBox& box );
    static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingOrientedBox& box );

    static void CreateFromPoints(BoundingSphere& Out, size_t Count, const XMFLOAT3* pPoints, size_t Stride);

    static void CreateFromFrustum(BoundingSphere& Out, const BoundingFrustum& fr );

};

其中BoundingSphere::CreateMergerd靜態方法將場景中的兩個包圍球體用一個更大的包圍球緊緊包住。

BoundingSphere::CreateFromBoundingBox靜態方法則是從AABB盒或OBB盒創建出外接包圍球。

然後還需要著重說明一下BoundingSphere::CreateFromPoints靜態方法的使用,因為通常情況下我們是用自定義的結構體來描述一個頂點,然後再使用的頂點數組,所以該方法也適用於從頂點數組創建出一個包圍球。

參數Count說明了頂點的數目。

參數pPoints需要填上的是頂點數組第一個元素中位置向量的地址。

參數Stride即可以說是頂點結構體的字節大小,也可以說是跳到下一個元素中的位置向量需要偏移的字節數。

下面是一個使用示例:

struct VertexPosNormalTex
{
    XMFLOAT3 pos;
    XMFLOAT3 normal;
    XMFLOAT3 tex;
};

VertexPosNormalTex vertices[20];
// 省略初始化操作...
BoundingSphere sphere;
BoundingSphere::CreateFromPoints(sphere, 20, &vertices[0].pos, sizeof(VertexPosNormalTex));

軸對齊包圍盒(Axis-aligned bounding box)

技術分享圖片

一個物體若擁有AABB盒,那AABB盒的六個面都會和物體緊緊貼靠在一起。它可以用兩個點描述:Vmax和Vmin。其中Vmax的含義是:分別取物體所有頂點中x, y, z分量下的最大值以構成該頂點。Vmin的含義則是:分別取物體所有頂點中x, y, z分量下的最小值以構成該頂點。

獲取了這兩個點後,我們就可以用另一種表述方式:中心位置C和一個3D向量E,E的每個分量的含義為中心位置到該分量對應軸的兩個面的距離(距離是相等的)。

該碰撞庫使用的則是第二種表述方式,但也支持用第一種方式來構建。結構體的一部分如下:

struct BoundingBox
{
    static const size_t CORNER_COUNT = 8;   // 邊界點數目

    XMFLOAT3 Center;            // 盒中心點
    XMFLOAT3 Extents;           // 中心點到每個面的距離
    
    // 構造函數
    BoundingBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ) {}
    XM_CONSTEXPR BoundingBox(const XMFLOAT3& center, const XMFLOAT3& extents)
        : Center(center), Extents(extents) {}
    BoundingBox(const BoundingBox& box) : Center(box.Center), Extents(box.Extents) {}

    // ...
    
    // 靜態創建方法
    static void CreateMerged(BoundingBox& Out, const BoundingBox& b1, const BoundingBox& b2 );

    static void CreateFromSphere(BoundingBox& Out, const BoundingSphere& sh );

    static void XM_CALLCONV CreateFromPoints(BoundingBox& Out, FXMVECTOR pt1, FXMVECTOR pt2 );
    static void CreateFromPoints(BoundingBox& Out, size_t Count, const XMFLOAT3* pPoints,size_t Stride );
};

BoundingBox::CreateMerged靜態方法創建一個最小的AABB盒,能夠同時包含這兩個AABB盒。

BoundingBox::CreateFromSphere靜態方法給球體創建外接立方體包圍盒。

BoundingBox::CreateFromPoints靜態方法中的參數pt1pt2即可以為包圍盒某一斜對角線上的兩個頂點,也可以是一個包含所有點中xyz分量最大值和最小值的兩個構造點。

有向包圍盒(Oriented bounding box)

技術分享圖片

對某些物體來說,在經過一系列變換後它的包圍盒也需要隨之改變。但例如一些默認情況下寬度、深度值比高度大得多的物體,比如飛機、書本等,經過變換後它的AABB盒可能會變得特別大,暴露了許多空余的位置,從而不能很好地將物體包住。

技術分享圖片

因為AABB盒的邊界是軸對齊的,沒有辦法記錄旋轉屬性。這時候我們可以考慮使用OBB盒,除了包含AABB盒應當記錄的信息外,它還記錄了旋轉相關的信息。結構體部分如下:

struct BoundingOrientedBox
{
    static const size_t CORNER_COUNT = 8;   // 邊界點數目

    XMFLOAT3 Center;            // 盒中心點
    XMFLOAT3 Extents;           // 中心點到每個面的距離
    XMFLOAT4 Orientation;       // 單位旋轉四元數(物體->世界)

    // 構造函數
    BoundingOrientedBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ), Orientation(0,0,0, 1.f ) {}
    XM_CONSTEXPR BoundingOrientedBox(const XMFLOAT3& _Center, const XMFLOAT3& _Extents, const XMFLOAT4& _Orientation)
        : Center(_Center), Extents(_Extents), Orientation(_Orientation) {}
    BoundingOrientedBox(const BoundingOrientedBox& box)
        : Center(box.Center), Extents(box.Extents), Orientation(box.Orientation) {}

    // ...
    
    // 靜態創建方法
    static void CreateFromBoundingBox(BoundingOrientedBox& Out, const BoundingBox& box );

    static void CreateFromPoints(BoundingOrientedBox& Out, size_t Count,
        const XMFLOAT3* pPoints, size_t Stride );
};

其中BoundingOrientedBox::CreateFromBoundingBox靜態方法創建出跟AABB盒和一樣的OBB盒,並且單位旋轉四元數是默認的(即沒有產生旋轉)。

包圍視錐體(Bounding Frustum)

為了描述一個視錐體,一種方式是以數學的形式指定視錐體的六個邊界平面:左/右平面,頂/底平面,近/遠平面。這裏假定六個視錐體平面是朝向內部的。

技術分享圖片

雖然我們可以用六個4D平面向量來存儲一個視錐體,但這還是有更節省空間的表示方法。

首先在物體坐標系中,取攝像頭的位置為原點,正前方觀察方向為Z軸,右方向為X軸,上方向為Y軸,這樣就可以得到右(左)平面投影到zOx平面下的直線斜率為X/Z(-X/Z),以及上(下)平面投影到zOy平面下的直線斜率為Y/Z(-Y/Z)。同時也可以得到近(遠)平面到原點的距離,也即是對應的Z值。

然後使用一個3D位置向量和單位旋轉四元數來表示視錐體在世界中的位置和朝向。這樣我們也可以描述一個視錐體了。

下面展示了視錐體包圍盒結構體的部分內容:

struct BoundingFrustum
{
    static const size_t CORNER_COUNT = 8;

    XMFLOAT3 Origin;            // 攝像機在世界中的位置,物體坐標系下默認會設為(0.0f, 0.0f, 0.0f)
    XMFLOAT4 Orientation;       // 單位旋轉四元數

    float RightSlope;           // 右平面投影到zOx平面的直線斜率+X/Z
    float LeftSlope;            // 左平面投影到zOx平面的直線斜率-X/Z
    float TopSlope;             // 上平面投影到zOy平面的直線斜率+Y/Z
    float BottomSlope;          // 下平面投影到zOy平面的直線斜率-Y/Z
    float Near, Far;            // Z值對應近(遠)平面到攝像機物體坐標系原點的距離

    // 構造函數
    BoundingFrustum() : Origin(0,0,0), Orientation(0,0,0, 1.f), RightSlope( 1.f ), LeftSlope( -1.f ),
                        TopSlope( 1.f ), BottomSlope( -1.f ), Near(0), Far( 1.f ) {}
    XM_CONSTEXPR BoundingFrustum(const XMFLOAT3& _Origin, const XMFLOAT4& _Orientation,
                    float _RightSlope, float _LeftSlope, float _TopSlope, float _BottomSlope,
                    float _Near, float _Far)
        : Origin(_Origin), Orientation(_Orientation),
          RightSlope(_RightSlope), LeftSlope(_LeftSlope), TopSlope(_TopSlope), BottomSlope(_BottomSlope),
          Near(_Near), Far(_Far) {}
    BoundingFrustum(const BoundingFrustum& fr)
        : Origin(fr.Origin), Orientation(fr.Orientation), RightSlope(fr.RightSlope), LeftSlope(fr.LeftSlope),
          TopSlope(fr.TopSlope), BottomSlope(fr.BottomSlope), Near(fr.Near), Far(fr.Far) {}
    BoundingFrustum(CXMMATRIX Projection) { CreateFromMatrix( *this, Projection ); }

    // ...
    // 靜態創建方法
    static void XM_CALLCONV CreateFromMatrix(BoundingFrustum& Out, FXMMATRIX Projection);

}

通常情況下,我們會通過傳遞投影矩陣來創建一個包圍視錐體,而不是直接指定上面的這些信息。

包圍盒的相交、包含、碰撞檢測及變換

包圍盒與平面的相交檢測

對於包圍盒與平面的相交檢測,返回結果使用了枚舉類型PlaneIntersectionType來描述相交情況:

enum PlaneIntersectionType
{
    FRONT = 0,              // 包圍盒在平面的正面區域
    INTERSECTING = 1,       // 包圍盒與平面有相交
    BACK = 2,               // 包圍盒在平面的背面區域
};

上面提到的四種包圍盒都具有重載方法Intersects用於檢測該包圍盒與平面的相交情況:

PlaneIntersectionType XM_CALLCONV Intersects(FXMVECTOR Plane) const;

正/背面的判定取決於一開始平面法向量的設定。比如一個中心在原點,棱長為2的正方體,與平面-z+2=0(對應4D平面向量(0.0f,0.0f,-1.0f,2.0f), 平面法向量(0.0f,0.0f,-1.0f) )的相交結果為:物體在平面的正面區域。

包圍盒與包圍盒的包含檢測

對於兩個包圍盒的包含檢測,返回結果使用了枚舉類型ContainmentType來描述包含情況:

enum ContainmentType
{
    DISJOINT = 0,       // 兩個包圍盒相互分離
    INTERSECTS = 1,     // 兩個包圍盒有相交
    CONTAINS = 2,       // 兩個包圍盒存在包含關系
};

這四種包圍盒相互之間都有對應的方法來測試:

ContainmentType Contains(const BoundingSphere& sp) const;
ContainmentType Contains(const BoundingBox& box) const;
ContainmentType Contains(const BoundingOrientedBox& box) const;
ContainmentType Contains(const BoundingFrustum& fr) const;

包圍盒與包圍盒的碰撞檢測

如果我們只需要檢查兩個包圍盒之間是否發生碰撞(相交和包含都算),則可以使用下面的這些方法。四種包圍盒相互之間都能進行碰撞測試:

bool Intersects(const BoundingSphere& sh) const;
bool Intersects(const BoundingBox& box) const;
bool Intersects(const BoundingOrientedBox& box) const;
bool Intersects(const BoundingFrustum& fr) const;

包圍盒的變換

四種包圍盒都包含下面兩個方法,一個是任意矩陣的變換,另一個是構造世界矩陣的變換(這裏用BoundingVolume來指代這四種包圍盒):

void XM_CALLCONV Transform(BoundingVolume& Out, FXMMATRIX M ) const;
void XM_CALLCONV Transform(BoundingVolume& Out, float Scale, FXMVECTOR Rotation, FXMVECTOR Translation) const;

要註意的是,第一個參數都是用於輸出變換後的包圍盒,Rotation則是單位旋轉四元數。

包圍盒的其它方法

獲取包圍盒的八個頂點

除了包圍球外的其它包圍盒都擁有方法GetCorners

void GetCorners(XMFLOAT3* Corners) const;

這裏要求傳遞的參數Corners是一個可以容納元素個數至少為8的數組。

獲取包圍視錐體的六個平面

BoundingFrustum::GetPlanes方法可以獲取視錐體六個平面的平面向量:

void GetPlanes(XMVECTOR* NearPlane, XMVECTOR* FarPlane, XMVECTOR* RightPlane,
    XMVECTOR* LeftPlane, XMVECTOR* TopPlane, XMVECTOR* BottomPlane) const;

包圍視錐體在檢測是否包含某一包圍盒的時候內部會調用待測包圍盒的ContainedBy靜態重載方法,參數為視錐體提供的六個平面。故下面的方法通常我們不會直接用到:

ContainmentType XM_CALLCONV ContainedBy(FXMVECTOR Plane0, FXMVECTOR Plane1, FXMVECTOR Plane2,
    GXMVECTOR Plane3, HXMVECTOR Plane4, HXMVECTOR Plane5 ) const;
// Test frustum against six planes (see BoundingFrustum::GetPlanes)

三角形、射線

三角形的表示需要用到三個坐標點向量,而射線的表示則需要一個Origin向量(射線起點)和一個Direction向量(射線方向)。

射線(三角形)與非包圍盒的相交檢測

下面這三個常用的方法都在名稱空間DirectX::TriangleTests中(ContainedBy函數不會直接使用故不列出來):

namespace TriangleTests
{
    bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, FXMVECTOR V0, GXMVECTOR V1, 
        HXMVECTOR V2, float& Dist );
        // 射線與三角形的相交檢測

    bool XM_CALLCONV Intersects(FXMVECTOR A0, FXMVECTOR A1, FXMVECTOR A2, GXMVECTOR B0, HXMVECTOR B1, 
        HXMVECTOR B2 );
        // 三角形與三角形的相交檢測

    PlaneIntersectionType XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2, 
        GXMVECTOR Plane );
        // 平面與三角形的相交檢測

    // 忽略...
};

其中Dist返回的是射線起點到交點的距離,若沒有檢測到相交,Dist的值為0.0f

射線(三角形)與包圍盒的相交檢測

四種包圍盒都包含了下面的兩個方法:

bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, float& Dist) const;
// 射線與包圍盒的相交檢測
bool XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2) const;
// 三角形與包圍盒的相交檢測

演示程序

關於碰撞檢測庫的演示可以在下面的鏈接找到,這裏就沒有必要再寫一個演示程序了:

DirectX SDK Samples

下載(克隆)到本地後找到Collision文件夾,選擇合適的解決方案打開並編譯運行即可。這裏我選擇的是Collision_Desktop_2017_Win10.sln。成功運行的話效果如下:

技術分享圖片

DirectX11 With Windows SDK完整目錄

Github項目源碼

DirectX11 With Windows SDK--18 使用DirectXCollision庫進行碰撞檢測