1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-22 20:25:28 +08:00

fix images and videos

This commit is contained in:
aillieo
2017-07-02 21:27:50 +08:00
parent d6d23977a8
commit ff28c1cf4e
2 changed files with 46 additions and 25 deletions

View File

@@ -1,5 +1,11 @@
# 碰撞检测
| 原文 | [Collision detection](https://learnopengl.com/#!In-Practice/2D-Game/Collisions/Collision-detection) |
| ---- | ---------------------------------------- |
| 作者 | JoeydeVries |
| 翻译 | [aillieo](https://github.com/aillieo) |
| 校对 | 暂无 |
当试图判断两个物体之间是否有碰撞发生时,我们通常不使用物体本身的数据,因为这些物体常常会很复杂,这将导致碰撞检测变得很复杂。正因这一点,使用**重叠**在物体上的更简单的外形(通常有较简单明确的数学定义)来进行碰撞检测成为常用的方法。我们基于这些简单的外形来检测碰撞,这样代码会变得更简单且节省了很多性能。这些碰撞外形例如圆、球体、长方形和立方体等,与拥有上百个三角形的网格相比简单了很多。
虽然它们确实提供了更简单更高效的碰撞检测算法,但这些简单的碰撞外形拥有一个共同的缺点,这些外形通常无法完全包裹物体。产生的影响就是当检测到碰撞时,实际的物体并没有真正的碰撞。必须记住的是这些外形仅仅是真实外形的近似。
@@ -7,15 +13,18 @@
## AABB - AABB 碰撞
AABB代表的是与坐标轴对齐的边界框(bounding box)边界框是指与场景基础坐标轴2D中的是x和y轴对齐的长方形的碰撞外形。与坐标轴对齐意味着这个长方形没有经过旋转并且它的边线和场景中基础坐标轴平行例如左右边线和y轴平行。这些边界框总是和场景的坐标轴平行这使得所有的计算都变得更简单。下边是我们用一个AABB包裹一个球对象物体
![](../../../img/06/Breakout/05/02/collisions_ball_aabb.png)
Breakout中几乎所有的物体都是基于长方形的物体因此很理所应当地使用与坐标系对齐的边界框来进行碰撞检测。这就是我们接下来要做的。
有多种方式来定义与坐标轴对齐的边界框。其中一种定义AABB的方式是获取左上角点和右下角点的位置。我们定义的GameObject类已经包含了一个左上角点位置它的Position vector并且我们可以通过把左上角点的矢量加上它的尺寸Position` +`Size很容易地计算出右下角点。每个GameObject都包含一个AABB我们可以高效地使用它们碰撞。
那么我们如何判断碰撞呢当两个碰撞外形进入对方的区域时就会发生碰撞例如定义了第一个物体的碰撞外形以某种形式进入了第二个物体的碰撞外形。对于AABB来说很容易判断因为它们是与坐标轴对齐的对于每个轴我们要检测两个物体的边界在此轴向是否有重叠。因此我们只是简单地检查两个物体的水平边界是否重合以及垂直边界是否重合。如果水平边界**和**垂直边界都有重叠那么我们就检测到一次碰撞。
![](../../../img/06/Breakout/05/02/collisions_overlap.png)
将这一概念转化为代码也是很直白的。我们对两个轴都检测是否重叠,如果都重叠就返回碰撞:
@@ -81,25 +90,31 @@ void Game::Update(GLfloat dt)
}
```
此时如果我们运行代码,球会与每个砖块进行碰撞检测,如果砖块不是实心的,则表示砖块被销毁。如果运行游戏以下是你会看到的:
<video src="../../../../img/06/Breakout/05/02/collisions.mp4" controls="controls"></video>
## AABB - Circle collision detection
虽然碰撞检测确实生效,但并不是非常准确,因为球会在不直接接触到大多数砖块时与它们发生碰撞。我们来实现另一种碰撞检测技术。
## AABB - 圆碰撞检测
由于球是一个圆形的物体AABB或许不是球的最佳碰撞外形。碰撞的代码中将球视为长方形框因此常常会出现球碰撞了砖块但此时球精灵还没有接触到砖块。
![](../../../img/06/Breakout/05/02/collisions_ball_aabb_touch.png)
使用圆形碰撞外形而不是AABB来代表球会更合乎常理。因此我们在球对象中包含了Radius变量为了定义圆形碰撞外形我们需要的是一个位置矢量和一个半径。
![](../../../img/06/Breakout/05/02/collisions_circle.png)
这意味着我们不得不修改检测算法因为当前的算法只适用于两个AABB的碰撞。检测圆和AABB碰撞的算法会稍稍复杂关键点如下我们会找到AABB上距离圆最近的一个点如果圆到这一点的距离小于它的半径那么就产生了碰撞。
难点在于获取AABB上的最近点\(\bar{P}\)。下图展示了对于任意的AABB和圆我们如何计算该点
难点在于获取AABB上的最近点P¯P¯。下图展示了对于任意的AABB和圆我们如何计算该点
![](../../../img/06/Breakout/05/02/collisions_aabb_circle.png)
首先我们要获取球心C¯C¯与AABB中心B¯B¯的矢量差D¯D¯。接下来用AABB的半边长(half-extents)ww和h¯h¯来限制(clamp)矢量D¯D¯。长方形的半边长是指长方形的中心到它的边的距离简单的说就是它的尺寸除以2。这一过程返回的是一个总是位于AABB的边上的位置矢量除非圆心在AABB内部
首先我们要获取球心\(\bar{C}\)与AABB中心\(\bar{B}\)的矢量差\(\bar{D}\)。接下来用AABB的半边长(half-extents)\(w\)和\(\bar{h}\)来限制(clamp)矢量\(\bar{D}\)。长方形的半边长是指长方形的中心到它的边的距离简单的说就是它的尺寸除以2。这一过程返回的是一个总是位于AABB的边上的位置矢量除非圆心在AABB内部
限制运算把一个值**限制**在给定范围内,并返回限制后的值。通常可以表示为:
@@ -112,10 +127,12 @@ float clamp(float value, float min, float max) {
例如,值`42.0f`被限制到`6.0f``3.0f`之间会得到`6.0f`;而`4.20f`会被限制为`4.20f`
限制一个2D的矢量表示将其`x``y`分量都限制在给定的范围内。
这个限制后矢量P¯P¯就是AABB上距离圆最近的点。接下来我们需要做的就是计算一个新的差矢量D¯D¯它是圆心C¯C¯和P¯P¯的差矢量。
这个限制后矢量\(\bar{P}\)就是AABB上距离圆最近的点。接下来我们需要做的就是计算一个新的差矢量\(\bar{D'}\),它是圆心\(\bar{C}\)和\(\bar{P}\)的差矢量。
![](../../../img/06/Breakout/05/02/collisions_aabb_circle_radius_compare.png)
既然我们已经有了矢量D¯D¯,我们就可以比较它的长度和圆的半径以判断是否发生了碰撞。
既然我们已经有了矢量\(\bar{D'}\),我们就可以比较它的长度和圆的半径以判断是否发生了碰撞。
这一过程通过下边的代码来表示:
@@ -145,9 +162,11 @@ GLboolean CheckCollision(BallObject &one, GameObject &two) // AABB - Circle coll
我们创建了CheckCollision的一个重载函数用于专门处理一个BallObject和一个GameObject的情况。因为我们并没有在对象中保存碰撞外形的信息因此我们必须为其计算首先计算球心然后是AABB的半边长及中心。
使用这些碰撞外形的参数,我们计算出矢量差D¯D¯然后得到限制后的值并与AABB中心相加得到最近的点P¯P¯。然后计算出圆心和最近点的矢量差D¯D¯并返回两个外形是否碰撞。
使用这些碰撞外形的参数,我们计算出矢量差\(\bar{D}\)然后得到限制后的值并与AABB中心相加得到最近的点\(\bar{P}\)。然后计算出圆心和最近点的矢量差\(\bar{D'}\)并返回两个外形是否碰撞。
之前我们调用CheckCollision时将球对象作为其第一个参数因此现在CheckCollision的重载变量会自动生效我们无需修改任何代码。现在的结果会比之前的碰撞检测算法更准确。
<video src="../../../../img/06/Breakout/05/02/collisions_circle.mp4" controls="controls"></video>
看起来生效了,但仍缺少一些东西。我们准确地检测了所有碰撞,但碰撞并没有对球产生任何反作用。我们需要在碰撞时产生一些**反作用**,例如当碰撞发生时,更新球的位置和/或速度。这将是[下一个]()教程的主题。
看起来生效了,但仍缺少一些东西。我们准确地检测了所有碰撞,但碰撞并没有对球产生任何反作用。我们需要在碰撞时产生一些**反作用**,例如当碰撞发生时,更新球的位置和/或速度。这将是[下一个](./03 Collision resolution.md)教程的主题。

View File

@@ -1,25 +1,28 @@
# 碰撞处理
| 原文 | [Collision resolution](https://learnopengl.com/#!In-Practice/2D-Game/Collisions/Collision-resolution) |
| ---- | ---------------------------------------- |
| 作者 | JoeydeVries |
| 翻译 | [aillieo](https://github.com/aillieo) |
| 校对 | 暂无 |
上个教程的最后,我们得到了一种有效的碰撞检测方案。但是球对检测到的碰撞不会有反作用;它仅仅是径直穿过所有的砖块。我们希望球会从撞击到的砖块**反弹**。此教程将讨论如何使用AABB-圆碰撞方案实现这项称为碰撞处理 (collision resolution)的功能。
当碰撞发生时,我们希望出现两个现象:重新定位球,以免它进入另一个物体,其次是改变球的速度方向,使它看起来像是物体的反弹。
### Collision repositioning
### 碰撞重定位
为了把球对象定位到碰撞的AABB的外部我们必须明确球侵入碰撞框的距离。为此我们要回顾上一节教程中的示意图
此时球少量进入了AABB所以检测到了碰撞。我们现在希望将球从移出AABB的外形使其仅仅碰触到AABB像是没有碰撞一样。为了确定需要将球从AABB中移出多少距离我们需要找回矢量R¯R¯它代表的是侵入AABB的程度。为得到R¯R¯我们用球的半径减去V¯V¯。矢量V¯V¯是最近点P¯P¯和球心C¯C¯的差矢量。
![](../../../img/06/Breakout/05/03/collisions_aabb_circle_resolution.png)
有了R¯R¯之后我们将球的位置偏移R¯R¯就将球直接放置在与AABB紧邻的位置此时球已经被重定位到合适的位置
此时球少量进入了AABB所以检测到了碰撞。我们现在希望将球从移出AABB的外形使其仅仅碰触到AABB像是没有碰撞一样。为了确定需要将球从AABB中移出多少距离我们需要找回矢量\(\bar{R}\)它代表的是侵入AABB的程度。为得到\(\bar{R}\)我们用球的半径减去\(\bar{V}\)。矢量\(\bar{V}\)是最近点\(\bar{P}\)和球心\(\bar{C}\)的差矢量
有了\(\bar{R}\)之后我们将球的位置偏移\(\bar{R}\)就将球直接放置在与AABB紧邻的位置此时球已经被重定位到合适的位置。
### Collision direction
@@ -36,7 +39,7 @@
但是如何判断球撞击AABB的方向呢解决这一问题有很多种方法其中之一是对于每个砖块使用4个AABB而不是1个AABB并把它们放置到砖块的每个边上。使用这种方法我们可以确定被碰撞的是哪个AABB和哪个边。但是有一种使用点乘(dot product)的更简单的方法。
您或许还记得[transformations](https://learnopengl.com/#!Getting-started/Transformations)教程中点乘可以得到两个正交化的矢量的夹角。如果我们定义指向北、南、西和东的四个矢量,然后计算它们和给定矢量的夹角会怎么样?由这四个方向矢量和给定的矢量点乘积的结果中的最高值(点乘积的最大值为`1.0f`,代表`0`度角)即是矢量的方向。
您或许还记得[transformations](../../../01 Getting started/07 Transformations.md)教程中点乘可以得到两个正交化的矢量的夹角。如果我们定义指向北、南、西和东的四个矢量,然后计算它们和给定矢量的夹角会怎么样?由这四个方向矢量和给定的矢量点乘积的结果中的最高值(点乘积的最大值为`1.0f`,代表`0`度角)即是矢量的方向。
这一过程如下代码所示:
@@ -81,13 +84,13 @@ enum Direction {
```
既然我们已经知道了如何获得R¯R¯以及如何判断球撞击AABB的方向我们开始编写碰撞处理的代码。
既然我们已经知道了如何获得\(\bar{R}\)以及如何判断球撞击AABB的方向我们开始编写碰撞处理的代码。
### AABB - Circle collision resolution
为了计算碰撞处理所需的数值我们要从碰撞的函数中获取更多的信息而不只只是一个`true``false`因此我们要返回一个包含更多信息的tuple这些信息即是碰撞发生时的方向及差矢量R¯R¯)。你可以在头文件`<tuple>`中找到`tuple`
为了计算碰撞处理所需的数值我们要从碰撞的函数中获取更多的信息而不只只是一个`true``false`因此我们要返回一个包含更多信息的tuple这些信息即是碰撞发生时的方向及差矢量\(\bar{R}\))。你可以在头文件`<tuple>`中找到`tuple`
为了更好组织代码我们把碰撞相关的数据使用typedef定义为Collision
@@ -158,7 +161,7 @@ void Game::DoCollisions()
}
```
不要被函数的复杂度给吓到因为它仅仅是我们目前为止的概念的直接转化。首先我们会检测碰撞如果发生了碰撞且砖块不是实心的那么就销毁砖块。然后我们从tuple中获取到了碰撞的方向dir以及表示V¯V¯的差矢量diff_vector最终完成碰撞处理。
不要被函数的复杂度给吓到因为它仅仅是我们目前为止的概念的直接转化。首先我们会检测碰撞如果发生了碰撞且砖块不是实心的那么就销毁砖块。然后我们从tuple中获取到了碰撞的方向dir以及表示\(\bar{V}\)的差矢量diff_vector最终完成碰撞处理。
我们首先检查碰撞方向是水平还是垂直并据此反转速度。如果是水平方向我们从diff_vector的x分量计算侵入量RR并根据碰撞方向用球的位置矢量加上或减去它。垂直方向的碰撞也是如此但是我们要操作各矢量的y分量。
@@ -206,7 +209,7 @@ void Game::DoCollisions()
无论你有没有注意到,但当运行代码时,球和玩家挡板的碰撞处理仍旧有一个大问题。以下的视频清楚地展示了将会出现的现象:
<video src="../../../../img/06/Breakout/05/03/collisions_sticky_paddle.mp4" controls="controls"></video>
这种问题称为粘板问题(sticky paddle issue)出现的原因是玩家挡板以较高的速度移向球导致球的中心进入玩家挡板。由于我们没有考虑球的中心在AABB内部的情况游戏会持续试图对所有的碰撞做出响应当球最终脱离时已经对`y`向速度翻转了多次,以至于无法确定球在脱离后是向上还是向下运动。
@@ -225,8 +228,6 @@ Ball->Velocity.y = -1 * abs(Ball->Velocity.y);
### 底部边界
与经典的Breakout内容相比唯一缺少的就是失败条件了失败会重置关卡和玩家。在Game类的Update函数中我们要检查球是否接触到了底部边界如果接触到就重置游戏。
@@ -245,8 +246,9 @@ void Game::Update(GLfloat dt)
ResetLevel和ResetPlayer函数直接重新加载关卡并重置对象的各变量值为原始的值。现在游戏看起来应该是这样的
<video src="../../../../img/06/Breakout/05/03/collisions_complete.mp4" controls="controls"></video>
就是这样了我们创建完成了一个有相似机制的经典Breakout游戏的复制版。这里你可以找到Game类的源代码[header](https://learnopengl.com/code_viewer.php?code=in-practice/breakout/game_collisions.h), [code](https://learnopengl.com/code_viewer.php?code=in-practice/breakout/game_collisions)。
就是这样了我们创建完成了一个有相似机制的经典Breakout游戏的复制版。这里你可以找到Game类的源代码[header](https://learnopengl.com/code_viewer.php?code=in-practice/breakout/game_collisions.h)[code](https://learnopengl.com/code_viewer.php?code=in-practice/breakout/game_collisions)。
## A few notes
## 一些注意事项