• 沒有找到結果。

三维游戏引擎中的几何基础

4.2 矩阵及矩阵操作

函数形式很简单,输入两个向量指针,函数返回它们的点积值,返回值类型为 FLOAT。

3.向量的叉积

另一种向量乘法是叉积,这个概念也许是最难理解的。两个向量的叉积得到第三个向量,这 个向量与原始两个向量相垂直,其大小定义如下(叉积符号定义为×):

|

u v

 | | | | | sin

u

*

v

*

(4-8)

其中 sin

为两个向量夹角的正弦值。要计算两个向量叉积的大小,可以建立以下矩阵:

. . . . . .

i j k u v u x u y u z

v x v y v z

  (4-9)

假设 n u v  ,那么:

. . * . . * . . ( . * . . * . ) . . * . . * .

n x u y v z u z v y n y u x v z u z v x n z u x v y u y v x

 

  

 

(4-10)

前面已经讲过,两个向量的叉积得到的向量与原来的向量都垂直,这在计算表面的法线向量 时非常有用。

注意,叉积不满足交换律,但交换顺序的结果仅仅是改变了叉积结果的符号,即:

u v

    (4-11)

v u

D3DX 库为向量叉积运算提供了一个函数,其原型为:

D3DXVECTOR3 * D3DXVec3Cross(

D3DXVECTOR3 * pOut, CONST D3DXVECTOR3 * pV1, CONST D3DXVECTOR3 * pV2 );

pV1 和 pV2 两个指针为输入参数,它们保存了两个原始向量的值,叉积所求得的向量保存在 pOut 中。函数还有一个返回值,其值和 pOut 相等,作用是使得 D3DXVec3Cross 函数返回值可以 作为一个实参传递给其他函数。

4.2 矩阵及矩阵操作

正如以前提到过的,使用矩阵,可以旋转、缩放或移动顶点(进而对整个对象也能做这些变 换)。本章会看到怎样按照不同的方法来旋转 5 个立方体:绕 X 轴、绕 Y 轴、绕 Z 轴、绕自定义 轴和绕所有其他轴。

在讲述几何变换的具体含义之前,先介绍几何变换的形象描述与它在图形渲染中的作用。当 游戏角色在地图上走动时,可以理解为平移(Translation);当怪物的手臂转动时,可以理解为旋 转(Rotation);假如你的武器像孙悟空的金箍棒一样无限放大,那么你需要缩放(Scaling)。

4.2.1 矩阵的概念及其工作原理

矩阵其实是一个高级的数学主题(参看《线性代数》),所以在此只能尽力地、简要地概括一 下。矩阵就像是一个表格,有一定数目的行与列;每个格子中都一个数字或表达式。通过特定的 矩阵,可以对 3D 对象中的顶点坐标进行运算,来实现类似移动、旋转、缩放这样的操作。在 DirectX

76

三维游戏

引 擎 设 计 技术 及 其 应用

中,矩阵就是 4×4 的二维数组。图 4-2 所示是一个矩阵的例子,这个矩阵能使一个对象(由它的 所有顶点)缩放到原来的 5 倍。

5 0 0 0 0 5 0 0 0 0 5 0 0 0 0 1

 

 

 

 

 

 

图 4-2 矩阵示例

在使用 Direct3D 编程的时候,使用 4×4 的矩阵和 1×4 的行向量来进行各种转换,这么做的 想法是这样的:使用一个 4×4 的矩阵 X 来表示一个特定的转换。把一个点的信息放到一个 1×4 的行向量 v 里面,那么这两个的积即 v×X 的结果就是新的转换出来的向量 v'。比如,如果 X 表 示一个单位为 10 的平移转换矩阵,v=[2,6,-3,1],那么转换出来 vX=[12,6,-3,1]。

也许,你会觉得 3×3 的矩阵更加适合转换,但是你要清楚,只有 4×4 矩阵才能更加适合我 们来描述所有的转换。有很多的转换使用 3×3 矩阵是不合适的,如透视投影、平移转换等。注 意,使用的是向量矩阵积的形式来进行转换,所以只有把向量增大成 1×4 来和矩阵达成一致,

因为 1×3 的向量和 4×4 的矩阵相乘是不合法的。

既然多了一个元素(一般标记它为 w),那么使用第四个元素干什么呢?在表示一个点的时 候,把这个 w 设为 1,这样就可以很好地进行转换了。因为向量没有位置的概念,那么向量也就 没有平移的概念,任何对向量进行的平移操作就没有意义。为了防止对向量进行平移操作,在进 行平移操作的时候,把 1×4 的行向量的 w 设置为 0。比如,在表示一个点的时候,P(p1,p2,p3) 对应的向量就应该是 P(p1,p2,p3,1),而在表示一个向量的时候(注意这个向量不表示点),那么就 把 w 设置为 0,像 V(v1,v2,v3)变为 1×4 的向量就是 V(v1,v2,v3,0)。

明白了吗?(似乎越说越糊涂),那么我再次说清楚一点。只有点才可以进行转换操作,所 以,如果一个向量表示一个点,那么就设 w=1,如果不是点,而是一般的向量,比如表示一个方 向,那么设 w=0,这样,进行转换的时候只要看一下 w 就知道能不能转换了。

注意:一个 1×4 的行向量既可以表示一个点,也可以表示一个向量,那么使用术语“向量”

的时候,指的可能是点,也可能是向量。

那么你一定很奇怪为什么要用 4×4 矩阵而不用 3×3 矩阵进行各种转换呢?主要是 3×3 矩 阵只能表示线性变换,而不能表示类似平移转换。比如一个点 p[0,0,0],对它进行平移一个单位,

怎么使用矩阵乘来做呢?没有办法了吧?使用 4×4 矩阵就可以解决了。

那么,矩阵是怎样改变顶点位置的呢?要改变一个顶点的 x、y 与 z 值,应该把它们与某个矩 阵相乘。如图 4-3 演示了顶点是怎样与矩阵相乘的。

这其实是一种简单的计算,只需要将 x、y 和 z 值分别与每列上的数相乘再相加,每列上得出 的数都是新顶点的一个坐标值,这在图 4-3 中是显而易见的。你可能已经注意到上面的图例中在 当前顶点(Current Vertex)的 z 值后面还有一个“1”,那是为了向矩阵的运算提供可行性和方便 性(最好参看一下相关的数学资料)。

所以,只需要操作矩阵就能完成这些类似旋转、缩放或移动的变换。幸运的是,DirectX 提 供了一些函数,能方便地生成一些通常的矩阵。那么,怎样完成既缩放又旋转(复合变换)的变 换呢?首先需要两个矩阵:一个用来旋转,一个用来缩放;然后,把两个矩阵相乘,得出一个新 的复合矩阵,既缩放又旋转的矩阵;然后利用这个新的矩阵来变换顶点。应该注意的是,矩阵相

77

D3DX 库提供了 D3DXMATRIX 这个类,它是从 D3DMATRIX 派生过来的,这里给出它们的 定义:

typedef struct _D3DMATRIX { union {

} D3DMATRIX;

78

三维游戏

引 擎 设 计 技术 及 其 应用

typedef struct D3DXMATRIX : public D3DMATRIX {

public:

D3DXMATRIX() {};

D3DXMATRIX( CONST FLOAT * );

D3DXMATRIX( CONST D3DMATRIX& );

D3DXMATRIX( CONST D3DXFLOAT16 * );

D3DXMATRIX( FLOAT _11, FLOAT _12, FLOAT _13, FLOAT _14, FLOAT _21, FLOAT _22, FLOAT _23, FLOAT _24, FLOAT _31, FLOAT _32, FLOAT _33, FLOAT _34, FLOAT _41, FLOAT _42, FLOAT _43, FLOAT _44 );

// 入口

FLOAT& operator () ( UINT Row, UINT Col );

FLOAT operator () ( UINT Row, UINT Col ) const;

// 类型转换

operator FLOAT* ();

operator CONST FLOAT* () const;

// 赋值操作符

D3DXMATRIX& operator *= ( CONST D3DXMATRIX& );

D3DXMATRIX& operator += ( CONST D3DXMATRIX& );

D3DXMATRIX& operator -= ( CONST D3DXMATRIX& );

D3DXMATRIX& operator *= ( FLOAT );

D3DXMATRIX& operator /= ( FLOAT );

// 取正、取反操作符

D3DXMATRIX operator + () const;

D3DXMATRIX operator - () const;

// 算术操作符

D3DXMATRIX operator * ( CONST D3DXMATRIX& ) const;

D3DXMATRIX operator + ( CONST D3DXMATRIX& ) const;

D3DXMATRIX operator - ( CONST D3DXMATRIX& ) const;

D3DXMATRIX operator * ( FLOAT ) const;

D3DXMATRIX operator / ( FLOAT ) const;

friend D3DXMATRIX operator * ( FLOAT, CONST D3DXMATRIX& );

BOOL operator == ( CONST D3DXMATRIX& ) const;

BOOL operator != ( CONST D3DXMATRIX& ) const;

} D3DXMATRIX, *LPD3DXMATRIX;

D3DXMATRIX 类提供了许多运算符重载,其中入口操作符使得可以像使用数组一样访问其 中的元素。比如,想把矩阵中的元素_11 的大小设置成 5,那么只需要如下这样写即可:

D3DXMATRIX M;

79 第 章

三维游戏引擎中的几何基础

4 M(0, 0) = 5.0f; // 设置入口 ij 为 11 ~ 5.0f

这个类还提供了 5 个赋值运算符,其中“*=”既允许将一个矩阵和一个矩阵相乘,也允许将 一个矩阵和一个数相乘。

比如要计算两个矩阵相乘,只要这么做:

D3DXMATRIX A(…); // 初始化 A D3DXMATRIX B(…); // 实始化 B D3DXMATRIX C = A * B; // C = AB

还有很多算术运算符和取正、取反操作符,其原理和 D3DXVECTOR3 类都差不多,这里就不详 细叙述了。读者如果有兴趣可以查看 d3dx9math.h 和 d3dx9math.inl 文件是怎样定义和实现它的。

4.2.2 矩阵相乘

为了将多个矩阵变换的效果综合在一起,必须将矩阵连接起来。矩阵的连接是通过相乘来实 现的。矩阵相乘,必须具备一定的条件,并不是任意两个矩阵都可以相乘。比如两个矩阵,一个 是 m×n 矩阵 M,另一个是 n×l 矩阵 N,这两个矩阵才可以相乘,相乘之后得到一个 m×l 矩阵。

一般来说,矩阵相乘需要满足的条件是:被乘矩阵的列数应该等于乘矩阵的行数,就是第一个矩 阵的列数应该等于第二个矩阵的行数。

注意:矩阵相乘的顺序是至关重要的。例如,先平移后缩放与先缩放后平移绝对是不一样的,

平移的因子应用到缩放中去了,而后者平移因子并没有应用到缩放中去。一般来说,先将物体搬 到坐标系的原点进行缩放,然后才进行平移;否则,缩放之后的物体形状将和原始物体不一样。

如果先对物体用矩阵 S 来放缩,然后用矩阵 T 对之平移,那么使用下面的矩阵:

M

T S

 (4-12)

在这里 M 只能作为左乘因子对列向量进行变换,如果对行向量进行处理,结果刚好相反(为 什么?仔细想一下线性代数中的矩阵知识吧)。

你只需要记住,如果对 3D 物体进行变换但是变换结果并没有按期望的方式表现出来,那么 很可能是将矩阵乘法的顺序搞错了,只要试着将顺序颠倒过来,再检验结果是否正确即可。

D3DX 库中将两个矩阵相乘得到另一个矩阵的函数是:

D3DXMATRIX * D3DXMATRIXMultiply(

D3DXMATRIX * pOut, CONST D3DXMATRIX * pM1, CONST D3DXMATRIX * pM2 );

在这里,

pOut

pM

1

pM

2。 4.2.3 矩阵的求逆

矩阵求逆相当于矩阵变换的逆操作。比如将物体从 A 处平移到 B 处称为变换 T1,从 B 处移 到 A 处称为变换 T2,那么 T1和 T2这两个变换互逆,变换表示的矩阵互为逆矩阵。从数学上说,

如果

1 2

T

T

I

(4-13)

I 为单位矩阵,则称 T

1和 T2互逆,一般 T1的逆矩阵用

T

11表示。现在来讨论平移矩阵、旋转 矩阵和缩放矩阵的逆矩阵的求法。

80

81 第 章

三维游戏引擎中的几何基础

相關文件