前言

在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项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

常见包围盒

包围球(Bounding Box)

一个球体只需要使用圆心坐标和半径就可以表示。结构体的一部分如下:

  1. struct BoundingSphere
  2. {
  3. XMFLOAT3 Center; // 球体中心坐标
  4. float Radius; // 球体半径
  5. // 构造函数
  6. BoundingSphere() : Center(0,0,0), Radius( 1.f ) {}
  7. XM_CONSTEXPR BoundingSphere(const XMFLOAT3& center, float radius )
  8. : Center(center), Radius(radius) {}
  9. BoundingSphere(const BoundingSphere& sp )
  10. : Center(sp.Center), Radius(sp.Radius) {}
  11. // ...
  12. // 静态创建方法
  13. static void CreateMerged(BoundingSphere& Out, const BoundingSphere& S1, const BoundingSphere& S2 );
  14. static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingBox& box );
  15. static void CreateFromBoundingBox(BoundingSphere& Out, const BoundingOrientedBox& box );
  16. static void CreateFromPoints(BoundingSphere& Out, size_t Count, const XMFLOAT3* pPoints, size_t Stride);
  17. static void CreateFromFrustum(BoundingSphere& Out, const BoundingFrustum& fr );
  18. };

其中BoundingSphere::CreateMergerd静态方法将场景中的两个包围球体用一个更大的包围球紧紧包住。

BoundingSphere::CreateFromBoundingBox静态方法则是从AABB盒或OBB盒创建出外接包围球。

然后还需要着重说明一下BoundingSphere::CreateFromPoints静态方法的使用,因为通常情况下我们是用自定义的结构体来描述一个顶点,然后再使用的顶点数组,所以该方法也适用于从顶点数组创建出一个包围球。

参数Count说明了顶点的数目。

参数pPoints需要填上的是顶点数组第一个元素中位置向量的地址。

参数Stride即可以说是顶点结构体的字节大小,也可以说是跳到下一个元素中的位置向量需要偏移的字节数。

下面是一个使用示例:

  1. struct VertexPosNormalTex
  2. {
  3. XMFLOAT3 pos;
  4. XMFLOAT3 normal;
  5. XMFLOAT3 tex;
  6. };
  7. VertexPosNormalTex vertices[20];
  8. // 省略初始化操作...
  9. BoundingSphere sphere;
  10. 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的每个分量的含义为中心位置到该分量对应轴的两个面的距离(距离是相等的)。

该碰撞库使用的则是第二种表述方式,但也支持用第一种方式来构建。结构体的一部分如下:

  1. struct BoundingBox
  2. {
  3. static const size_t CORNER_COUNT = 8; // 边界点数目
  4. XMFLOAT3 Center; // 盒中心点
  5. XMFLOAT3 Extents; // 中心点到每个面的距离
  6. // 构造函数
  7. BoundingBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ) {}
  8. XM_CONSTEXPR BoundingBox(const XMFLOAT3& center, const XMFLOAT3& extents)
  9. : Center(center), Extents(extents) {}
  10. BoundingBox(const BoundingBox& box) : Center(box.Center), Extents(box.Extents) {}
  11. // ...
  12. // 静态创建方法
  13. static void CreateMerged(BoundingBox& Out, const BoundingBox& b1, const BoundingBox& b2 );
  14. static void CreateFromSphere(BoundingBox& Out, const BoundingSphere& sh );
  15. static void XM_CALLCONV CreateFromPoints(BoundingBox& Out, FXMVECTOR pt1, FXMVECTOR pt2 );
  16. static void CreateFromPoints(BoundingBox& Out, size_t Count, const XMFLOAT3* pPoints,size_t Stride );
  17. };

BoundingBox::CreateMerged静态方法创建一个最小的AABB盒,能够同时包含这两个AABB盒。

BoundingBox::CreateFromSphere静态方法给球体创建外接立方体包围盒。

BoundingBox::CreateFromPoints静态方法中的参数pt1pt2即可以为包围盒某一斜对角线上的两个顶点,也可以是一个包含所有点中xyz分量最大值和最小值的两个构造点。

有向包围盒(Oriented bounding box)

对某些物体来说,在经过一系列变换后它的包围盒也需要随之改变。但例如一些默认情况下宽度、深度值比高度大得多的物体,比如飞机、书本等,经过变换后它的AABB盒可能会变得特别大,暴露了许多空余的位置,从而不能很好地将物体包住。

因为AABB盒的边界是轴对齐的,没有办法记录旋转属性。这时候我们可以考虑使用OBB盒,除了包含AABB盒应当记录的信息外,它还记录了旋转相关的信息。结构体部分如下:

  1. struct BoundingOrientedBox
  2. {
  3. static const size_t CORNER_COUNT = 8; // 边界点数目
  4. XMFLOAT3 Center; // 盒中心点
  5. XMFLOAT3 Extents; // 中心点到每个面的距离
  6. XMFLOAT4 Orientation; // 单位旋转四元数(物体->世界)
  7. // 构造函数
  8. BoundingOrientedBox() : Center(0,0,0), Extents( 1.f, 1.f, 1.f ), Orientation(0,0,0, 1.f ) {}
  9. XM_CONSTEXPR BoundingOrientedBox(const XMFLOAT3& _Center, const XMFLOAT3& _Extents, const XMFLOAT4& _Orientation)
  10. : Center(_Center), Extents(_Extents), Orientation(_Orientation) {}
  11. BoundingOrientedBox(const BoundingOrientedBox& box)
  12. : Center(box.Center), Extents(box.Extents), Orientation(box.Orientation) {}
  13. // ...
  14. // 静态创建方法
  15. static void CreateFromBoundingBox(BoundingOrientedBox& Out, const BoundingBox& box );
  16. static void CreateFromPoints(BoundingOrientedBox& Out, size_t Count,
  17. const XMFLOAT3* pPoints, size_t Stride );
  18. };

其中BoundingOrientedBox::CreateFromBoundingBox静态方法创建出跟AABB盒和一样的OBB盒,并且单位旋转四元数是默认的(即没有产生旋转)。

包围视锥体(Bounding Frustum)

为了描述一个视锥体,一种方式是以数学的形式指定视锥体的六个边界平面:左/右平面,顶/底平面,近/远平面。这里假定六个视锥体平面是朝向内部的。

虽然我们可以用六个4D平面向量来存储一个视锥体,但这还是有更节省空间的表示方法。

首先在物体坐标系中,取摄像头的位置为原点,正前方观察方向为Z轴,右方向为X轴,上方向为Y轴,这样就可以得到右(左)平面投影到zOx平面下的直线斜率为X/Z(-X/Z),以及上(下)平面投影到zOy平面下的直线斜率为Y/Z(-Y/Z)。同时也可以得到近(远)平面到原点的距离,也即是对应的Z值。

然后使用一个3D位置向量和单位旋转四元数来表示视锥体在世界中的位置和朝向。这样我们也可以描述一个视锥体了。

下面展示了视锥体包围盒结构体的部分内容:

  1. struct BoundingFrustum
  2. {
  3. static const size_t CORNER_COUNT = 8;
  4. XMFLOAT3 Origin; // 摄像机在世界中的位置,物体坐标系下默认会设为(0.0f, 0.0f, 0.0f)
  5. XMFLOAT4 Orientation; // 单位旋转四元数
  6. float RightSlope; // 右平面投影到zOx平面的直线斜率+X/Z
  7. float LeftSlope; // 左平面投影到zOx平面的直线斜率-X/Z
  8. float TopSlope; // 上平面投影到zOy平面的直线斜率+Y/Z
  9. float BottomSlope; // 下平面投影到zOy平面的直线斜率-Y/Z
  10. float Near, Far; // Z值对应近(远)平面到摄像机物体坐标系原点的距离
  11. // 构造函数
  12. BoundingFrustum() : Origin(0,0,0), Orientation(0,0,0, 1.f), RightSlope( 1.f ), LeftSlope( -1.f ),
  13. TopSlope( 1.f ), BottomSlope( -1.f ), Near(0), Far( 1.f ) {}
  14. XM_CONSTEXPR BoundingFrustum(const XMFLOAT3& _Origin, const XMFLOAT4& _Orientation,
  15. float _RightSlope, float _LeftSlope, float _TopSlope, float _BottomSlope,
  16. float _Near, float _Far)
  17. : Origin(_Origin), Orientation(_Orientation),
  18. RightSlope(_RightSlope), LeftSlope(_LeftSlope), TopSlope(_TopSlope), BottomSlope(_BottomSlope),
  19. Near(_Near), Far(_Far) {}
  20. BoundingFrustum(const BoundingFrustum& fr)
  21. : Origin(fr.Origin), Orientation(fr.Orientation), RightSlope(fr.RightSlope), LeftSlope(fr.LeftSlope),
  22. TopSlope(fr.TopSlope), BottomSlope(fr.BottomSlope), Near(fr.Near), Far(fr.Far) {}
  23. BoundingFrustum(CXMMATRIX Projection) { CreateFromMatrix( *this, Projection ); }
  24. // ...
  25. // 静态创建方法
  26. static void XM_CALLCONV CreateFromMatrix(BoundingFrustum& Out, FXMMATRIX Projection);
  27. }

通常情况下,我们会通过传递投影矩阵来创建一个包围视锥体,而不是直接指定上面的这些信息。

包围盒的相交、包含、碰撞检测及变换

包围盒与平面的相交检测

对于包围盒与平面的相交检测,返回结果使用了枚举类型PlaneIntersectionType来描述相交情况:

  1. enum PlaneIntersectionType
  2. {
  3. FRONT = 0, // 包围盒在平面的正面区域
  4. INTERSECTING = 1, // 包围盒与平面有相交
  5. BACK = 2, // 包围盒在平面的背面区域
  6. };

上面提到的四种包围盒都具有重载方法Intersects用于检测该包围盒与平面的相交情况:

  1. 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来描述包含情况:

  1. enum ContainmentType
  2. {
  3. DISJOINT = 0, // 两个包围盒相互分离
  4. INTERSECTS = 1, // 两个包围盒有相交
  5. CONTAINS = 2, // 两个包围盒存在包含关系
  6. };

这四种包围盒相互之间都有对应的方法来测试:

  1. ContainmentType Contains(const BoundingSphere& sp) const;
  2. ContainmentType Contains(const BoundingBox& box) const;
  3. ContainmentType Contains(const BoundingOrientedBox& box) const;
  4. ContainmentType Contains(const BoundingFrustum& fr) const;

包围盒与包围盒的碰撞检测

如果我们只需要检查两个包围盒之间是否发生碰撞(相交和包含都算),则可以使用下面的这些方法。四种包围盒相互之间都能进行碰撞测试:

  1. bool Intersects(const BoundingSphere& sh) const;
  2. bool Intersects(const BoundingBox& box) const;
  3. bool Intersects(const BoundingOrientedBox& box) const;
  4. bool Intersects(const BoundingFrustum& fr) const;

包围盒的变换

四种包围盒都包含下面两个方法,一个是任意矩阵的变换,另一个是构造世界矩阵的变换(这里用BoundingVolume来指代这四种包围盒):

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

要注意的是,第一个参数都是用于输出变换后的包围盒,Rotation则是单位旋转四元数。

包围盒的其它方法

获取包围盒的八个顶点

除了包围球外的其它包围盒都拥有方法GetCorners

  1. void GetCorners(XMFLOAT3* Corners) const;

这里要求传递的参数Corners是一个可以容纳元素个数至少为8的数组。

获取包围视锥体的六个平面

BoundingFrustum::GetPlanes方法可以获取视锥体六个平面的平面向量:

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

包围视锥体在检测是否包含某一包围盒的时候内部会调用待测包围盒的ContainedBy静态重载方法,参数为视锥体提供的六个平面。故下面的方法通常我们不会直接用到:

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

三角形、射线

三角形的表示需要用到三个坐标点向量,而射线的表示则需要一个Origin向量(射线起点)和一个Direction向量(射线方向),其中Direction是单位向量。

射线(三角形)与非包围盒的相交检测

下面这三个常用的方法都在名称空间DirectX::TriangleTests中(ContainedBy函数不会直接使用故不列出来):

  1. namespace TriangleTests
  2. {
  3. bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, FXMVECTOR V0, GXMVECTOR V1,
  4. HXMVECTOR V2, float& Dist );
  5. // 射线与三角形的相交检测
  6. bool XM_CALLCONV Intersects(FXMVECTOR A0, FXMVECTOR A1, FXMVECTOR A2, GXMVECTOR B0, HXMVECTOR B1,
  7. HXMVECTOR B2 );
  8. // 三角形与三角形的相交检测
  9. PlaneIntersectionType XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2,
  10. GXMVECTOR Plane );
  11. // 平面与三角形的相交检测
  12. // 忽略...
  13. };

其中Dist返回的是射线起点到交点的距离,若没有检测到相交,Dist的值为0.0f

射线(三角形)与包围盒的相交检测

四种包围盒都包含了下面的两个方法:

  1. bool XM_CALLCONV Intersects(FXMVECTOR Origin, FXMVECTOR Direction, float& Dist) const;
  2. // 射线与包围盒的相交检测
  3. bool XM_CALLCONV Intersects(FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2) const;
  4. // 三角形与包围盒的相交检测

演示程序

关于碰撞检测库的演示可以在下面的链接找到,这里就没有必要再写一个演示程序了:

DirectX SDK Samples

下载(克隆)到本地后找到Collision文件夹,选择合适的解决方案打开并编译运行即可。这里我选择的是Collision_Desktop_2017_Win10.sln。成功运行的话效果如下:

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11 With Windows SDK--18 使用DirectXCollision库进行碰撞检测的更多相关文章

  1. 粒子系统与雨的效果 (DirectX11 with Windows SDK)

    前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...

  2. DirectX11 With Windows SDK--00 目录

    前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...

  3. DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪

    前言 这一章将了解如何在DirectX 11利用硬件实例化技术高效地绘制重复的物体,以及使用视锥体裁剪技术提前将位于视锥体外的物体进行排除. 在此之前需要额外了解的章节如下: 章节回顾 18 使用Di ...

  4. DirectX11 With Windows SDK--21 鼠标拾取

    前言 拾取是一项非常重要的技术,不论是电脑上用鼠标操作,还是手机的触屏操作,只要涉及到UI控件的选取则必然要用到该项技术.除此之外,一些类似魔兽争霸3.星际争霸2这样的3D即时战略游戏也需要通过拾取技 ...

  5. DirectX11 With Windows SDK--06 DirectXMath数学库

    前言 xnamath.h原本是位于DirectX SDK的一个数学库,但是现在Windows SDK包含的数学库已经抛弃掉原来的xnamath.h,并演变成了现在的DirectXMath.h.其实本质 ...

  6. DirectX11 With Windows SDK--07 添加光照与常用几何模型

    前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的光照模型.除此之外,该项目还用到了多个常量缓冲区,因此还会提及HLSL的常量缓冲区打包规则以及如何设置多 ...

  7. DirectX11 With Windows SDK--10 摄像机类

    前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html 由于考虑后续的项目需要有一个比较好的演示环境 ...

  8. DirectX11 With Windows SDK--26 计算着色器:入门

    前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群 ...

  9. DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用

    前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用. ...

随机推荐

  1. Cs231n-assignment 1作业笔记

    KNN assignment1 KNN讲解参见: https://blog.csdn.net/u014485485/article/details/79433514?utm_source=blogxg ...

  2. BZOJ 3684 大朋友和多叉树

    BZOJ 3684 大朋友和多叉树 Description 我们的大朋友很喜欢计算机科学,而且尤其喜欢多叉树.对于一棵带有正整数点权的有根多叉树,如果它满足这样的性质,我们的大朋友就会将其称作神犇的: ...

  3. 手把手教你实现Android RecyclerView上拉加载功能

    摘要 一直在用到RecyclerView时都会微微一颤,因为一直都没去了解怎么实现上拉加载,受够了每次去Github找开源引入,因为感觉就为了一个上拉加载功能而去引入一大堆你不知道有多少BUG的代码, ...

  4. B. Obtaining the String(模拟)

    比较水的模拟 思路:就是模拟题意 注意:把数组开大点,开始wa了几次就是这个原因 #include<iostream> #include<string> #include< ...

  5. spring中基于注解使用AOP

    本文内容:spring中如何使用注解实现面向切面编程,以及如何使用自定义注解. 一个场景 比如用户登录,每个请求发起之前都会判断用户是否登录,如果每个请求都去判断一次,那就重复地做了很多事情,只要是有 ...

  6. linux查询日志常用命令,经常更新

    1.grep命令 grep -c "查询内容" filename    ------c,是小写,可以知道你要查询的内容在这个文件中是否存在 grep -C 10 "查询内 ...

  7. JavaScript日历控件开发

    概述 在开篇之前,先附上日历的代码地址和演示地址,代码是本文要分析的代码,演示效果是本文要实现的效果 代码地址:https://github.com/aspwebchh/javascript-cont ...

  8. codeforces#983 B. XOR-pyramid (dp)

    参考博客:https://www.01hai.com/note/av137952. 题意:首先定义 (b代表一个数组) 给出一个区间,l,r,求它最大的连续子序列的函数值 分析: 定义dp[x][y] ...

  9. CodeForces Round #553 Div2

    A. Maxim and Biology 代码: #include <bits/stdc++.h> using namespace std; int N; string s; int mi ...

  10. Unit 6.标准文档流,浮动,清除浮动以及margin塌陷问题

    一. 什么是标准文档流 文本流其实就是文档的读取和输出顺序,也就是我们通常看到的由左到右.由上而下的读取和输出形式,在网页中每个元素都是按照这个顺序进行排序和显示的,而float和position两个 ...