zoukankan      html  css  js  c++  java
  • 39. 游戏数学基础

     矢量是游戏开发中涉及到的最基本的对象。矢量有多种形式,可以是2D、3D或4D等。3D矢量是一个包含3个浮点数的结构,每个值代表矢量中不同的轴。这些值分别是x、y和z轴,可用于描述三维空间。矢量用于描述3D空间中的方向,它可能也是最常用的数学对象。

           矩阵是游戏开发中第二个最常用的数学对象。矩阵主要用于将矢量从一个坐标系转换到另一个坐标系,还可用于旋转和平移。矩阵由浮点数的2D数组组成。3×3矩阵由3行、3列共9个元素组成,4×4矩阵是由4行、4列共16个浮点元素组成的2D数组。

    四元组用于描述旋转。四元组是由4个浮点数w、x、y、z构成的结构,像4D矢量。虽然矩阵也可用于旋转,但使用四元组更好,因为四元组只有4个浮点数,而矩阵有16个浮点数(对4×4矩阵而言)。这意味着存储四元组所需的空间比存储矩阵所需的空间少,另外四元组的数学操作也要少些。这使得四元组的计算速度更块。在处理旋转时,四元组同样比矩阵更平滑。

           射线用于描述位置和方向。射线有原点,也就是射线开始的位置,方向就是射线指向的地方。射线包括两个矢量,一个代表位置,一个代表方向。通常,射线用于碰撞检测.

    平面是在区域上无限扩展的网格。可以将平面当作是沿地无限延伸的面。平面无限窄,而且没有边界。另一方面,多边形是封闭的区域,它有边界,而且大小有限。为了让多边形更像是多边形,它就要包含3个或更多的点。如果有两条连接的直线,那么除了两条直线外,什么也没有创建。将3条线段连接到一起就会形成一个三角形,这是计算机图形学中最常用的一种形状。

           物理学是个很庞大的领域。本书并没有足够的篇幅来详细介绍。本书实现的物理学可浓缩为3D场景中的重力和碰撞检测。

    8.2 矢量数学和回顾
           
    如前所述,矢量是游戏开发中涉及的最基本对象。3D矢量保存在3D空间中指定位置的3个轴上。这些轴分别是x轴、y轴和z轴,分别代表空间中的宽度、高度和深度。这些值可对3D环境中的任意物体定位。3D矢量是一个包含三个浮点数的结构。多数人使用标号为x、y、z的变量指定这三个值,而其他人使用三个浮点数构成的数组来指定。

    // Example 1
    struct Vector3D
    {
    float x, y, z;
    };

    // Example 2
    struct Vector3D
    {
    float v[3];
    };

      

    void VecAdd(Vector3D v1, Vector3D v2, Vector3D &out)
    {
    out.x = v1.x + v2.x;
    out.y = v1.y + v2.y;
    out.z = v1.z + v2.z;
    }

    void VecSub(Vector3D v1, Vector3D v2, Vector3D &out)
    {
    out.x = v1.x - v2.x;
    out.y = v1.y - v2.y;
    out.z = v1.z - v2.z;
    }

    void VecMultiply(Vector3D v1, Vector3D v2, Vector3D &out)
    {
    out.x = v1.x * v2.x;
    out.y = v1.y * v2.y;
    out.z = v1.z * v2.z;
    }

    void VecDivide(Vector3D v1, Vector3D v2, Vector3D &out)
    {
    out.x = v1.x / v2.x;
    out.y = v1.y / v2.y;
    out.z = v1.z / v2.z;
    }

    void VecEquals(Vector3D v1, Vector3D &out)
    {
    out.x = v1.x;
    out.y = v1.y;
    out.z = v1.z;
    }

      有的操作还可以实现游戏开发中很重要的内容。这些操作包括计算矢量长度(即幅度),计算两个矢量的点积,计算两个矢量的叉积及矢量归一化。

           同样可以通过将一个矢量的分量乘上另一个矢量的分量,并将乘积结果相加来计算矢量的长度。为了的得到矢量长度,可以使用执行速度很慢的sqrt()函数。该函数并没有那么糟糕,因为函数中只包含了sqrt()函数一次。但要牢记的是,平方根函数是CPU上执行速度最慢的一个函数。

    float VecLength(Vector3D v1)
    {
    return sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z);
    }

         两个矢量的点积用于度量两个方向的差。点积又称标量积,在游戏开发中的应用较广,尤其是在手动光照算法中得到了大量的应用。将所有对应分量的乘积相加到一起就得到了点积结果。这与计算矢量长度很相似,除了其中用到了平方根操作之外。程序清单8.5给出了计算两个矢量点积的示例。

    float VecDotProduct(Vector3D v1, Vector3D v2)
    {
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
    }

      叉积又名为矢量积,它是一个新矢量,通过查找与两个矢量正交的矢量而计算得到该矢量。这种特性在游戏开发中有很多用途,经常会看到它的使用。使用叉积的示例之一就是计算多边形的法线(多边形面朝的方向)。通过获取两个矢量中每个相反分量之间的差,可以计算出叉积。这意味着为了得到叉积的x值,必须将第一个矢量的y分量和第二个矢量的z分量相乘,然后减去第一个矢量的z分量和第二个矢量的y分量的乘积结果。程序清单8.6给出了计算每个分量叉积结果的方法。叉积又名外积。

    void VecCrossProduct(Vector3D v1, Vector3D v2, Vector3D &out)
    {
    out.x = (v1.y*v2.z) - (v1.z*v2.y);
    out.y = (v1.z*v2.x) - (v1.x*v2.z);
    out.z = (v1.x*v2.y) - (v1.y*v2.x);
    }

      将矢量归一化为单位长度,也就是说想要矢量具有相同的方向,而且矢量长度为1。这意味着计算矢量长度时,其长度为1。这在许多游戏开发程序中得到了应用,常用于光照中。

           为了归一化矢量,所要做的全部工作是得到矢量长度,然后对每个分量根据长度进行缩放。换句话说,使用计算矢量长度的相同代码得到长度,然后再用该长度除以每个分量。这样就可以对矢量进行缩放,矢量长度就等于1,由此可以得到归一化的矢量。程序清单8.7给出了具体的代码实现。

    void VecNormalize(Vector3D v1, Vector3D &out)
    {
    float inv_length = 1 / VecLength(v1);
    out.x = v1.x * inv_length;
    out.y = v1.y * inv_length;
    out.z = v1.z * inv_length;
    }

      

    Direct3D矢量
           
    可以选择使用Direct3D结构和函数以实现矢量数学。这样做很好,因为为了使用矢量数学,而无需完全理解或对其编码,同样还可以很大程度地提高程序性能。因为Microsoft公司的代码做了很好的优化,而且效率非常高。

       Direct3D矢量包括2D、3D和4D三个版本。诚如所知,每一维都确定了矢量结构中的轴数。程序清单8.8给出了这几类矢量的示例。

    D3DXVECTOR2 vec2;
    D3DXVECTOR3 vec3;
    D3DXVECTOR4 vec4;

      Direct3D矢量函数如预期一样工作。这些函数的好处就在于它们经过了专业优化,使用它们没有任何问题,而这都根据情况而定。如果正在编写只和Direct3D打交道的代码,尤其是如果读者数学功底不是很好,那么Direct3D矢量函数也许就是读者要考虑使用的对象。

    D3DXVECTOR3 v1, v2, result;
    float val;

    D3DXVec3Add(
    &result, &v1, &v2);
    D3DXVec3Subtract(
    &result, &v1, &v2);

    val
    = D3DXVec3Length(&v1);
    val
    = D3DXVec3Dot(&v1, &v2);
    D3DXVec3Cross(
    &result, &v1, &v2);
    D3DXVec3Normalize(
    &result, &v1);

      矩阵数学

           在计算机图形学中,诸如矩阵这样的对象可以控制物体的位置和旋转。矩阵是浮点数的2D数组,可以对矢量进行平移、旋转、缩放和变换。从根本上讲,矩阵是由行和列构成的表。矩阵有多种形式,如3×3、3×4和4×4。这里矩阵的数分别代表2D数组包含的行数和列数。例如,4×4矩阵由16个元素组成,因为4×4矩阵有4行,4列(4×4=16)。

           和其他如矢量这样的数学结构相比,矩阵的可视化表示比较困难。实际上,可以试着将矩阵当成对象考虑,它可以通过如旋转这样的操作更改或改变矢量。在3D游戏编程中,大部分工作都要和4×4矩阵打交道。对矩阵同样可以实现和矢量相类似的操作,诸如矩阵和其他矩阵或矢量的乘法。例如,在将已经旋转过的矩阵和一组矢量相乘时,得到的结果矢量将以在矩阵中指定的相同量旋转。用一个旋转了90°的矩阵乘上微个对象几何图形,那么该几何图形同样旋转90°。

     做矩阵乘法运算时,主要是将各个矩阵合并为一个矩阵。因此,可以用一个矩阵存储旋转数据,另一个矩阵存储平移矩阵。然后在准备使用这两个矩阵时,可以将它们合并成一个矩阵。然后可以使用这个最终矩阵乘上所有的矢量对象,以实现所有的旋转和平移,从而得到最终几何图形的位置和方向。用矩阵乘上矢量可以实现对原始几何图形的操作,并将其转换成新的表现形式。这是矩阵最常用的一点,读者可以做一些超出本书研究范围的工作。对矢量和矩阵做乘法,就是对矢量做变换。

           如前所述,4×4矩阵是一个由16个浮点数组成的结构。如果使用的是类,那么同样可以指定旋转、缩放、平移及变换矢量的成员函数,同样可以指定一个函数,计算两个不同的矩阵乘法。程序清单8.10给出了在简单C++类中实现的方法。

     1 class CMatrix4x4
    2 {
    3 public:
    4 CMatrix4x4();
    5
    6 void Identity();
    7
    8 void operator=(CMatrix4x4 &m);
    9 CMatrix4x4 operator*(CMatrix4x4 &m);
    10 Vector3D operator*(Vector3D &v);
    11
    12 void Translate(float x, float y, float z);
    13 void Rotate(double angle, int x, int y, int z);
    14
    15 float matrix[16];
    16 };

      

     1 void CMatrix4x4::Identity()
    2 {
    3 memset(this, 0, sizeof(CMatrix4x4));
    4 matrix[0] = 1.0f;
    5 matrix[5] = 1.0f;
    6 matrix[10] = 1.0f;
    7 matrix[15] = 1.0f;
    8 }
    9
    10
    11 void CMatrix4x4::operator =(CMatrix4x4 &m)
    12 {
    13 memcpy(this, &m, sizeof(m));
    14 }
    15
    16 CMatrix4x4 CMatrix4x4::operator *(CMatrix4x4 &m)
    17 {
    18 float *m = matrix;
    19 float *m2 = m.matrix;
    20
    21 return CMatrix4x4(m[0] * m2[0] + m[4] * m2[1] +
    22 m[8] * m2[2] + m[12] * m2[3],
    23 m[1] * m2[0] + m[5] * m2[1] +
    24 m[9] * m2[2] + m[13] * m2[3],
    25 m[2] * m2[0] + m[6] * m2[1] +
    26 m[18] * m2[2] + m[14] * m2[3],
    27 m[3] * m2[0] + m[7] * m2[1] +
    28 m[11] * m2[2] + m[15] * m2[3],
    29 m[0] * m2[4] + m[4] * m2[5] +
    30 m[8] * m2[6] + m[12] * m2[7],
    31 m[1] * m2[4] + m[5] * m2[5] +
    32 m[9] * m2[6] + m[13] * m2[7],
    33 m[2] * m2[4] + m[6] * m2[5] +
    34 m[10] * m2[6] + m[14] * m2[7],
    35 m[3] * m2[4] + m[7] * m2[5] +
    36 m[11] * m2[6] + m[15] * m2[7],
    37 m[0] * m2[8] + m[4] * m2[9] +
    38 m[8] * m2[10] + m[12] * m2[11],
    39 m[1] * m2[8] + m[5] * m2[9] +
    40 m[9] * m2[10] + m[13] * m2[11],
    41 m[2] * m2[8] + m[6] * m2[9] +
    42 m[10] * m2[10] + m[14] * m2[11],
    43 m[3] * m2[8] + m[7] * m2[9] +
    44 m[11] * m2[10] + m[15] * m2[11],
    45 m[0] * m2[12] + m[4] * m2[13] +
    46 m[8] * m2[4] + m[12] * m2[15],
    47 m[1] * m2[12] + m[5] * m2[13] +
    48 m[9] * m2[4] + m[13] * m2[15],
    49 m[2] * m2[12] + m[6] * m2[13] +
    50 m[10] * m2[4] + m[14] * m2[15],
    51 m[3] * m2[12] + m[7] * m2[13] +
    52 m[11] * m2[4] + m[15] * m2[15]);
    53 }
    54
    55 /*
    56 3D空间中矩阵的平移只是代码中要做的很简单的工作。所要做的全部工作就是使用想要平移矩阵的x、y、z值替换矩阵的最后一行。平移的x分量保存在第12个数组元素中,y分量保存在第13个数组元素中,z分量保存在第14个数组元素中。
    57 */
    58
    59 void CMatrix4x4::Translate(float x, float y, float z)
    60 {
    61 matrix[12] = x;
    62 matrix[13] = y;
    63 matrix[14] = z;
    64 matrix[15] = 1;
    65 }
    66
    67
    68 void CMatrix4x4::Rotate(double angle, int x, int y, int z)
    69 {
    70 float sine = (float)sin(angle);
    71 float cosine = (float)cos(angle);
    72
    73 if(x)
    74 {
    75 matrix[5] = cosine;
    76 matrix[6] = sine;
    77 matrix[9] = -sine;
    78 matrix[10] = cosine;
    79 }
    80 if(y)
    81 {
    82 matrix[0] = cosine;
    83 matrix[2] = -sine;
    84 matrix[8] = sine;
    85 matrix[10] = cosine;
    86 }
    87 if(z)
    88 {
    89 matrix[0] = cosine;
    90 matrix[1] = sine;
    91 matrix[4] = -sine;
    92 matrix[5] = cosine;
    93 }
    94 }

      Direct3D矩阵

           Direct3D中包含了类似于矢量对象的内置矩阵对象和函数。这些对象类型为D3DXMATRIX,它是针对16个字节排成一行的矩阵,读者可以使用D3DXMATRIX16。然后,可以用不同的D3D函数完成矩阵相关操作,如使用D3DXMatrixIdentity()函数创建单位矩阵,使用D3DXMatrixTranslation()函数实现平移。矩阵平移可以通过D3DXMatrixRotateAxis()函数实现。如果想围绕任意轴旋转,使用D3DXMatrixRotationYawPitchRoll()函数即可。可以根据偏航、倾斜和滚动量进行旋转,使用D3DXMatrixRotationX()、D3DXMatrixRotationY()和D3DXMatrixRotationZ()函数即可实现矢量按照各自的轴旋转。

           Direct3D矩阵像Direct3D矢量一样已经优化过。如果在游戏和引擎中只计划使用Direct3D,但开发人员没有任何矩阵数学背景知识和使用经验,也许可以考虑使用Direct3D矩阵。但如果不介意自己编写矩阵处理代码,而且如果想学习这方面的内容,那么可以考虑编写自己的实现代码。这样做可以了解矩阵的本质,有助于将来的学习。如果经证明Direct3D矩阵的处理速度总是比自己编写的代码的处理速度快,那么最好使用Direct3D矩阵。

      四元组数学
           
    旋转是游戏编程中最常用的操作之一。旋转用于改变物体方向,或是饶着场景中某个轴或具体的点转动,并且可以移动骨骼动画中要用到的骨架点。正因为旋转如此重要,尤其是在游戏开发中,所以提高它们的精确度、速度和效率具有很重要的意义。例如,就骨骼动画而言,存储效率和速度对系统也许是最重要的两个要素,这是由系统的复杂性和存储旋转数据所需的内存就决定的。

     曾有一段时间使用矩阵实现旋转,出现过问题。当存储矩阵时,每个4×4矩阵都需要16个浮点数。现在这通常并不是什么问题,因为内存非常便宜而且充裕。但如果是在一个内存有限的设备上,那么可用的字节数就变的异常珍贵。这些设备可以是移动设备,如手机、PDA和手持游戏机等。另一个问题或潜在的问题是,在使用矩阵时速度就会成为问题。在CPU上要完成的操作越多,完成任务所耗费的时间就越多。就精度而言,使用矩阵的主要问题出现在旋转操作上。有一种名为gimble lock的问题将导致矩阵无法准确计算旋转结果。该问题会产生无法预期的视觉结果,这样会导致在旋转物体时出现一堆问题。在按照多个轴将矩阵旋转到某个轴开始照向错误方向的点时,就会发生gimble lock情况。这通常会出现在几个旋转后,而且会导致错误结果。

           四元组是4D数学对象。四元组并不是4D矢量,但能当作4D矢量对待。四元组使用w、x、y、z取代了x、y、z和w。当谈及四元组的表述时,每个成分的顺序并没有什么影响。虽然读者可以对它们的编码处理,但顺序实际上没有什么影响。四元组是复数的扩展,或者在本例中,它们中的三个是复数。这些复数可以使用常用的比例数加上称为虚数部分的数描述。

    曾有一段时间使用矩阵实现旋转,出现过问题。当存储矩阵时,每个4×4矩阵都需要16个浮点数。现在这通常并不是什么问题,因为内存非常便宜而且充裕。但如果是在一个内存有限的设备上,那么可用的字节数就变的异常珍贵。这些设备可以是移动设备,如手机、PDA和手持游戏机等。另一个问题或潜在的问题是,在使用矩阵时速度就会成为问题。在CPU上要完成的操作越多,完成任务所耗费的时间就越多。就精度而言,使用矩阵的主要问题出现在旋转操作上。有一种名为gimble lock的问题将导致矩阵无法准确计算旋转结果。该问题会产生无法预期的视觉结果,这样会导致在旋转物体时出现一堆问题。在按照多个轴将矩阵旋转到某个轴开始照向错误方向的点时,就会发生gimble lock情况。这通常会出现在几个旋转后,而且会导致错误结果。

           四元组是4D数学对象。四元组并不是4D矢量,但能当作4D矢量对待。四元组使用w、x、y、z取代了x、y、z和w。当谈及四元组的表述时,每个成分的顺序并没有什么影响。虽然读者可以对它们的编码处理,但顺序实际上没有什么影响。四元组是复数的扩展,或者在本例中,它们中的三个是复数。这些复数可以使用常用的比例数加上称为虚数部分的数描述。

           使用四元组有多个好处。例如,它们的连接速度要比矩阵快,因为它们需要的数学运算较少。四元组要比矩阵小,只需要4个浮点数;而4×4矩阵需要16个浮点数,这使得四元组只有4×4矩阵大小的1/4。对四元组内插同样会更平滑,会得到更准确的旋转,而不会受到gimble lock的影响。

    四元组复习
           四元组可以用一个实数和4个虚数表示。四元组的w分量是实部,其他的是虚部。描述四元组的方程看上去如下所示:
    q = w + xi + yj + zk

           四元组乘法是不可互换的,所以不能仅将该四元组的每个分量乘上另一个四元组的对应分量。两个四元组相乘用下面的公式计算:
    q1 * q2 = (w1*w2 - x1*x2 - y1*y2 - z1*z2)   +
        (w1*x2 + x1*w2 + y1*z2 - z1*y2)i +
        (w1*y2 + x1*z2 + y1*w2 - z1*x2)j +
        (w1*z2 + x1*y2 + y1*x2 - z1*w2)k

           四元组可用一个类表示,这个类的成员变量分别是w、x、y、z。可以对四元组实现多种操作,如乘法、加法、减法、计算四元组量值(长度)、点积、叉积、球形内插和旋转。

     1 class CQuaternion 
    2 {
    3 public:
    4 CQuaternion();
    5 CQuaternion(float xAxis, float yAxis,
    6 float zAxis, float wAxis);
    7
    8 void operator=(const CQuaternion &q);
    9 CQuaternion operator*(const CQuaternion &q);
    10 CQuaternion operator+(const CQuaternion &q);
    11
    12 void CreateQuatFromAxis(CVector3 &a, float radians);
    13
    14 float Length();
    15 void Normal();
    16
    17 CQuaternion Conjugate();
    18 CQuaternion CrossProduct(const CQuaternion &q);
    19
    20 void Rotatef(float amount, float xAxis,
    21 float yAxis, float zAxis);
    22 void RotationRadiansf(double X, double Y, double Z);
    23
    24 void CreateMatrix(float *pMatrix);
    25
    26 void Slerp(const CQuaternion &q1, const CQuaternion &q2, float t);
    27
    28 float w, x, y, z;
    29 };

      使用一个名为D3DXQUATERNION的结构就可以创建Direct3D四元组结构。Direct3D同样还包含了一系列用于操纵四元组的函数,如D3DXQuaternionDot()函数将返回两个四元组的点积,D3DXQuaternionLength()函数计算得到四元组的长度,而D3DXQuaternionMultiply()函数则将两个四元组相乘。查看DirectX SDK文档可以得到完整的函数列表文档。Direct3D的四元组结构如程序清单8.18所示。

    typedef struct D3DXQUATERNION {

        FLOAT x;

        FLOAT y;

        FLOAT z;

        FLOAT w;

    } D3DXQUATERNION;

        射线是由两个矢量组成的简单结构。一个矢量代表原点,而另一个矢量代表射线朝向的方向。射线方向是单位长度法线,它代表射线照向无限远的方向。这意味着如果射线朝向一个物体,那么无论它们两个相距多远,都会和物体交汇。指定射线最小长度的方法有很多,但从实用角度而言,射线应该是无限长的。
    struct stRay
    {
        Vector3D m_origin;       // 射线原点
        Vector3D m_direction;    // 射线照射的方向
    }
       射线主要用于碰撞检测。在第9章介绍碰撞检测这个主题时会讨论射线的使用方法。目前要注意的是,所有的射线都有长度和单位长度归一化后的方向。

        

  • 相关阅读:
    HTML知识点链接
    Apache和PHP的安装
    MySql的安装
    MY_FIRSH_MODULE
    【PAT甲级】1053 Path of Equal Weight (30 分)(DFS)
    Atcoder Grand Contest 039B(思维,BFS)
    Codeforces Round #589 (Div. 2)E(组合数,容斥原理,更高复杂度做法为DP)
    Codeforces Round #589 (Div. 2)D(思维,构造)
    【PAT甲级】1052 Linked List Sorting (25 分)
    【PAT甲级】1051 Pop Sequence (25 分)(栈的模拟)
  • 原文地址:https://www.cnblogs.com/kex1n/p/2173474.html
Copyright © 2011-2022 走看看