转 第十八章 矩阵数学 (as3.0)

本章我们不去介绍一些新的运动、物理学或渲染图形的方法。我要给大家介绍的是矩阵
(Matrix),它给我们提供了一个新的可选方案。
     矩阵在 3D 系统中 3D 点的旋转,缩放以及平移(运动)中使用得非常频繁。在各种 2D
图形的变换上也很常用。您也许可以回想到 beginGradientFill 方法就是使用矩阵来设置位
置,大小以及旋转比例的。
     本章大家将看到如何创建一个 3D 矩阵系统,用以操作 3D 的影片并且可以看到一些
Flash 中内置的矩阵。我很庆幸现在为止还没有一处提到 Keanu Reeves [译注:基努-里维斯,
尤指电影《黑客帝国》-- The Matrix] 的电影。看看我还能坚持多久。
转 <wbr>第十八章 <wbr>矩阵数学 <wbr>(as3.0)


图 18-1 一个 3×3 矩阵,一个 1×3 矩阵,一个 3×1 矩阵
     矩阵通常都是由一些变量来描述的,如 M。在矩阵中为表示一个特殊的单元,我们使
用的变量里面通常要用行列的值作为脚标。例如,如果图 18-1 中的 3×3 矩阵叫做 M,
那么 M2,3 就等于 6,因为它指向第二行,第三列。
     一个矩阵的单元不仅可以包含简单的数字,也可以是公式和变量。其实电子表格就是一
个大的矩阵。 我们可以用一个单元保存某一列的和,用另一个单元格将这个总和乘以一个分
数,等等。我们看到这样的矩阵应该非常有用。
矩阵运算
    一个电子表格就像一个自由组合的矩阵,我们要处理的矩阵更加有结构, 至于能用它们
做什么以及如何生成都有各自的规则。
    我所见过的大多数矩阵数学的教材都只介绍两种方法的一种。首先学校讲的是矩阵运算
的细节,使用的整个矩阵的几乎都是一些随机的数字。我们学习这些规则,但是不知道为什
么要做这些事情或所得的结果代表什么。就像在玩把数字排列成漂亮形状的游戏。
    第二个方法是详细地描述矩阵的内容但是略过手工操作, “将两个矩阵相乘得到这个
 如结果… …”让读者不知道乘法到底是怎么算的。
    为了保证大家都能了解矩阵是如何工作的,我选择一个两者兼具的方法(折衷) ,从介
绍一些数值矩阵开始,然后描述如何做矩阵乘法。
矩阵加法
    矩阵更为通常的作用是操作 3D 点。一个 3D 点包涵了 x, y, z 坐标。我们可以简单地
将它视为一个 1×3 的矩阵:
    xyz
现在假设要将这个点在空间中移动,或叫做点的平移。我们需要知道每个轴上移动多远。这
时可以将它放入一个转换矩阵(translation matrix)中。它又是一个 1×3 的矩阵:
    dx dy dz
这里 dx, dy, dz 是每个轴移动的距离。现在我们要想办法将转换矩阵加到点矩阵上面。这就
是矩阵加法,  非常简单。我们只需要将相应的单元进行相加形成一个新的包含了每个单元之
和的矩阵。很明显,要让两个矩阵相加,它们的大小都应该是相同的。转换方法如下:
    x y z + dx dy dz = (x + dx) (y + dy) (z + dz)
获得的矩阵可以叫做 x1, y1, z1,转换之后包含了该点的新坐标。让我们用实数来试一下。
假设点在 x, y, z 轴上的位置分别为 100, 50, 75,要让它们分别移动 -10, 20, -35。则应该是
这样的:
    100 50 75 + -10 20 -35 = (100 - 10) (50 + 20) (75 - 35)
因此,当进行加法运算时,所得该点的新坐标就是 90, 70, 40。非常简单,不是吗?大家也
许已经注意到了速度间的相互关系,   每个轴上的速度都加到了另一个矩阵的相应位置上。   公
平交易嘛。
    如果我们有一个较大的矩阵,那么继续使用同样的方法,匹配每个单元。我们不会去处
理大于 3×1 的矩阵加法,但是我会给大家这样一个抽象的例子:
    abc               jkl         (a + j) (b + k) (c + l)
    def         +     mno       = (d + m) (e + n) (f + o)
    ghi               pqr         (g + p) (h + q) (i + r)
  以上就是我们需要知道矩阵加法的一切。 在介绍了矩阵乘法之后,我将展示如何将现有的函
数使用在矩阵 3D 引擎中。
矩阵乘法
      在 3D 转换中应用更为广泛的是矩阵乘法(matrix multiplication),常用于缩放与旋转。
在本书中我们实际上不会用到 3D 缩放,因为例子中的点缩放,影片也没有 3D 的“厚度”,
因此只有二维的缩放。当然,大家可以建立一个可缩放整个 3D 立体模型的更为复杂的引
擎。这就需要写一些根据新的影片大小改变 3D 点的函数。这些已经超出了我们讨论的范
围,但是由于缩放是非常简单的,并且使用矩阵乘法很容易实现,因此我将带大家看一下这
个例子。
使用矩阵进行缩放
    首先,需要知道一个物体现有的宽度,高度和深度 —— 换句话讲,它是三个轴上每个
轴分量的大小。当然可以建立一个 3×1 的矩阵:
      whd
我们知道 w, h, d 代表宽度(width),高度(height)和深度(depth)。下面需要缩放这个矩
阵:
      sx  0  0
      0  sy  0
      0   0 sz
这里 sx, sy, sz 是对应轴上的缩放比例。它们都将是分数或小数,1.0 为 100%,2.0 为
200%,0.5 为 50%,等等。稍后大家会看到为什么矩阵是用这种形式分布的。
    要知道,矩阵乘法是为了让两个矩阵相乘,第一个矩阵的列数必需与另一个矩阵的行数
相同。只要符合这个标准,第一个矩阵可以有任意多个行,第二个矩阵可以有任意多个列。
本例中,由于第一个矩阵有三列(w, h, d),因此缩放矩阵就有三行。那么它们如何进行乘
法运算呢?让我们来看一下这个模式:
            sx 0 0
    w h d * 0 sy 0
            0 0 sz
矩阵的计算结果如下:
     (w*sx + h*0 + d*0) (w*0 + h*sy + d*0) (w*0 + h*0 + d*sz)
删除所有等于 0 的数:
     (w*sx) (h*sy) (d*sz)
非常有合乎逻辑,因为我们是用宽度 (x 轴分量) 乘以 x 缩放系数,高度乘以 y 缩放系数,
深度乘以 z 缩放系数。但是,我们究竟在做什么呢?那些所有等于 0 的数都像被遮盖上了,
因此让我们将这个模式抽象得更清晰一点。
        a  b  c
  uvw * d  e  f
        g  h  i

现在可以看到该模式的结果为:
    (u*a + v*d + w*g) (u*b + v*e + w*h) (u*c + v*f + w*i)
我们将第一个矩阵的第一行(u, v, w)与第二个矩阵每行的第一个元素相乘。将它们加起来
就得到了结果的第一行的第一个元素。在第二个矩阵的第二列(b, e, h)中使用相同的方法
就得到了第二列的结果。
    就要在第二行中重复上述动作,就会得到第二行的结果:
    如果第一个矩阵的行数大于 1,
  u v w   a b c
  x y z * d e f
          g h i
就得到了这个 3×2 的矩阵:
    (u*a + v*d + w*g) (u*b + v*e + w*h) (u*c + v*f + w*i)
    (x*a + y*d + z*g) (x*b + y*e + z*h) (x*c + y*f + z*i)
现在让我们看一些实际中用到的矩阵乘法 —— 坐标旋转。希望通过这个缩放的例子会让它
看起来更加清晰。
使用矩阵进行坐标旋转
首先,要挖出我们的 3D 点矩阵:
    x y z
它保存了该点所有的坐标。当然,还要有一个旋转矩阵。我们可以在三个轴的任意一轴上进
行旋转。我们将分别创建每种旋转的矩阵。先从 x 轴旋转矩阵开始:
    1    0      0
    0   cos   sin
    0  -sin   cos
这里有一些正余弦值, “sin 和 cos 是什么?”很明显,这就是我们要旋转的角度的正余弦
值。如果让这个点旋转 45 度,则这两个值就是 45 的正弦和余弦值。 (当然,在代码中要
使用弧度制)现在,我们让该矩阵与一个 3D 点的矩阵相乘,看一下结果。
          1      0     0
  x y z * 0     cos   sin
          0    -sin   cos
由此得到:
    (x*1 + y*0 + z*0) (x*0 + y*cos - z*sin) (x*0 + y*sin + z*cos)
整理后结果如下:
    (x) (y*cos - z*sin) (z*cos + y*sin)
这句话用 ActionScript 大略可以翻译成:
    x = x;
    y = Math.cos(angle) * y - Math.sin(angle) * z;
    z = Math.cos(angle) * z + Math.sin(angle) * y;
回忆一下第十章,在讨论坐标旋转时,我们会看到这实际上就是 x 轴的坐标旋转。不要惊
讶,矩阵数学只是观察和组织各种公式和方程的不同方法。至此,要创建一个 y 轴旋转的
矩阵就非常容易了:
      cos  0  sin
       0   1   0
     -sin  0  cos
最后,z 轴的旋转为:
      cos sin  0
     -sin cos  0
       0   0   1
这是一个很好的尝试,用 x, y, z 的矩阵乘以每个旋转矩阵的单位,证明所得到的结果与第
十章的坐标旋转公式完全相同。
编写矩阵
      OK,现在大家已经有了足够的基础将这些知识转换为代码了。下面,我们对第十五章
的 RotateXY.as 进行重新转换。这个类中有 rotateX 和 rotateY 两个方法, 用以实现 3D 坐
标旋转。我们要让它们以矩阵的方式工作。
      从 rotateX 函数开始。它会用到小球的 x, y, z 坐标,将它们放入 1×3 矩阵,然后创
建一个给定角度的 x 旋转矩阵。这个矩阵将使用数组的形式表示。最后使用 matrixMultiply
函数让两个矩阵相乘,当然还需要创建这个函数!相乘后的矩阵还要用另一个数组进行保存,
因为我们需要将这些数值再存回小球的 x, y, z 坐标中。下面是新版的方法:
private function rotateX(ball:Ball3D, angleX:Number):void {
  var position:Array = [ball.xpos, ball.ypos, ball.zpos];
  var sin:Number = Math.sin(angleX);
  var cos:Number = Math.cos(angleX);
  var xRotMatrix:Array = new Array();
  xRotMatrix[0] = [1, 0, 0];
  xRotMatrix[1] = [0, cos, sin];
  xRotMatrix[2] = [0, -sin, cos];
  var result:Array = matrixMultiply(position, xRotMatrix);
  ball.xpos = result[0];
  ball.ypos = result[1];
  ball.zpos = result[2];
}
下面是矩阵乘法的函数:
private function matrixMultiply(matrixA:Array, matrixB:Array):Array {
  var result:Array = new Array();
  result[0] = matrixA[0] * matrixB[0][0] +
  matrixA[1] * matrixB[1][0] +
  matrixA[2] * matrixB[2][0];
  result[1] = matrixA[0] * matrixB[0][1] +
  matrixA[1] * matrixB[1][1] +
  matrixA[2] * matrixB[2][1];
  result[2] = matrixA[0] * matrixB[0][2] +
  matrixA[1] * matrixB[1][2] +
  matrixA[2] * matrixB[2][2];
  return result;
}
现在,  这个矩阵乘法的函数是手工写出的一个 1×3 和 3×3 矩阵的乘法,这就是我们后面
用在每个例子中的函数。大家也可以使用 for 循环创建出更为动态的可处理任何大小的矩
阵函数,但是现在我要让代码保持简洁。
      最后创建 rotateY 函数。如果你了解 rotateX 函数, 那么这个函数应该非常显而易见了。
只需要创建一个 y 旋转矩阵来代替 x 旋转矩阵即可。
private function rotateY(ball:Ball3D, angleY:Number):void {
  var position:Array = [ball.xpos, ball.ypos, ball.zpos];
  var sin:Number = Math.sin(angleY);
  var cos:Number = Math.cos(angleY);
  var yRotMatrix:Array = new Array();
  yRotMatrix[0] = [ cos, 0, sin];
  yRotMatrix[1] = [ 0, 1, 0];
  yRotMatrix[2] = [-sin, 0, cos];
  var result:Array = matrixMultiply(position, yRotMatrix);
  ball.xpos = result[0];
  ball.ypos = result[1];
  ball.zpos = result[2];
}
就是这样。大家也可以创建一个 rotateZ 函数,由于我们的例子中实际上不需要用到它,所
以我将它作为练习留给大家完成。
      现在,运行一下 RotateXY.as,与第十五章的版本相比,它们看上去实际是一样的。 AS
                                           

阅读更多
个人分类: Flash Flex
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭