常规。一些警示:
你可能发现我们这里给的知识点和其它的网上或者书本上的有点不一样。这些知识当然是一样的,知识矩阵的参数的顺序或者是符号有点不同。这是因为不同的作者或者程序,使用不同的习惯表达。为了能过跟上这个逻辑而不用关注其它的档案里面说的。这篇文章和下篇文章我们会准确地介绍一些书本上或者是代码实现上的一些习惯使用。
(1)点和矩阵相乘
这片教程中,我们会把之前我们学习的点,向量,矩阵,坐标系统全部结合起来。最后,你会学习到矩阵是如何工作的。在之前的篇章中,我们提到了两个矩阵相乘需要有相关的行列限制。比如,一个矩阵的大小是m × p,另一个矩阵的大小是p × n那么这两个矩阵就可以相乘。我们同时也在之前的文章中提到了计算机图形,我们会主要处理4 × 4矩阵。
一个点或者是向量是一个三个数字的序列,出于这一点它们可以写成一个1 × 3的矩阵,
点可以写成矩阵的这种形式 P = [xyz].
这里的技巧就是,如果我们把点或者向量写成[1 × 3]的矩阵,我们可以将它们和其它的矩阵相乘。记住,m x p可以和p x n相乘得到的矩阵是m x n。如果第一个矩阵我们可以写成 m = 1 p = 3,那么也就意味这p x n的第二个矩阵就是 3 x n,这里的n可以是大于1的任何数字。在理论上,[1 × 3]的矩阵的后面相乘的矩阵可以是[3 x 1][3 × 2][3 ×3][3 x 4]等等,这里举了一个[1 x3][3x4]的例子:
有两件事情是需要记住的,也是下面我们要解释的。第一就是一点和矩阵相乘会将这个点移动到一个新的位置。点和矩阵相乘得到的还是点。如果它不是,我们就不会使用矩阵来变换点。第二点需要我们记住的是m x p和 p x n相乘得到一个m x n的矩阵。如果我们把我们的点看做是1 x 3的矩阵,那么我们需要得到一个 1 x 3的点的矩阵,那也就是说我们需要一个3 x 3的矩阵和点相乘。1 x 3的点和3x3的矩阵相乘得到一个点,像下面的样子。
在CG中,我们通常使用的是4x4的矩阵而不是3x3的矩阵,我们不久后就会解释原因。但是现在,我们坚持使用3x3的矩阵一段时间。为了完成这个章节,我们会写一些伪代码来表示一个点P和一个3x3的矩阵相乘得到另一个点Pt.如果你需要重新复习矩阵的乘法,请看之前的章节。记住对于新矩阵的每个参数,记着你需要左边的矩阵每排中的数和右边的矩阵每列的数相乘得到的结果再相加。在伪代码中像下面这个形式(不久之后我们会给出4×4的样式):
// multiply coeffs from row 1 with coeffs from column 1
Ptransformed.x = P.x * c00 + P.y * c10 + P.z * c20
// multiply coeffs from row 1 with coeffs from column 2
Ptransformed.y = P.x * c01 + P.y * c11 + P.z * c21
// multiply coeffs from row 1 with coeffs from column 3
Ptransformed.z = P.x * c02 + P.y * c12 + P.z * c22
(2)相似矩阵
相似矩阵或者说单位矩阵是一个方形矩阵,除了它斜对角的参数是1之外其它的都是0.
矩阵P和相似矩阵相乘的结果还是P.如果我们用相似矩阵的参数去代替之前的矩阵计算的公式,我们可以很明显地看到为什么是这样的:
// multiplying P by the identity matrix gives P
Ptransformed.x = P.x * 1 + P.y * 0 + P.z * 0 = P.x
Ptransformed.y = P.x * 0 + P.y * 1 + P.z * 0 = P.y
Ptransformed.z = P.x * 0 + P.y * 0 + P.z * 1 = P.z
(3)缩放矩阵
如果你看到了前面点和矩阵相乘的代码,你会发现P点的坐标值是R00 × x, R11 × y, 和R22 × z。当矩阵的参数设置为1的时候(其余的参数设置为0),我们会得到相似矩阵。但是,当这些参数不为1的时候(大于1或者小于1),它们可以看做是一个乘数和一个点的相乘(换句话说,点被放大或者缩小了一定的量)。如果你记得之前的章节中我们说过的坐标系统,点的坐标和一个实数相乘会缩放这个点的坐标。因此,缩放矩阵可以写成下面这种形式:
这里的 Sx, Sy, Sz可以看做是缩放因子。
// multiplying P by the scaling matrix
Ptransformed.x = P.x * Sx + P.y * 0 + P.z * 0 = P.x * Sx
Ptransformed.y = P.x * 0 + P.y * Sy + P.z * 0 = P.y * Sy
Ptransformed.z = P.x * 0 + P.y * 0 + P.z * Sz = P.z * Sz
作为一个例子,想象一下点P,它的坐标是(1,2,3),如果我们设置它的缩放因子为Sx = 1, Sy = 2, Sz = 3,那么P点和它们相乘之后得到另一个点的坐标值是(1, 4, 9).
记住如果缩放因子中有一个为负数,那么点的坐标也会被翻转过来(镜像到另外一面)。
(4)旋转矩阵
这个小节我们将要提到的是旋转矩阵,它会让一个点或者向量绕着坐标系统的某一个轴旋转。这样做的话,我们需要三角函数。
让我们在三维坐标系中定义点P,它的坐标值为(1, 0, 0)。我们现在忽略掉z轴,假设这个点在zy平面上。如果我们想要将P掉移动到Pt点,通过旋转的方式(我们选择旋转因为旋转看起来更简单)。Pt的坐标值为(0, 1, 0)。正如你在图1中看到的那样,这个可以通过点P绕Z轴逆时针(counterclockwise)旋转90度得到。
现在假设我们有一个矩阵R,当P乘以R的时候我们会将P旋转到Pt点。想象我们之前学到的矩阵乘法,我们可以重新写点和矩阵的乘法,分离每个点坐标的计算可以得到:
正如我们所说的,现在我们不关系Pt.z的坐标,它代替的是Pt的z轴坐标值。我们只考虑Pt.x和Pt.y,它们代表的是Pt x轴和y轴的坐标值。从P到Pt我们可以看到x轴的坐标从1走到0,。如果我么看计算Pt的第一行公式,它意味这R00应该为0.因为Py和Pz都为0,所以我们不在乎R10和R20的值。从P到Pt点我们可以看到y的坐标值从0到1.让我们看看上面的第二个计算公式。我们知道P点的什么呢?我们知道P.x是1,然后其它的点为0.它意味这R01不得不为1.我们重新来看,现在的R00为0,R01为1.让我们重新写下R看起来怎么样(把这个矩阵和相似矩阵对比):
如果你不理解为什么这些参数会有它们的值,这个不用担心。它们会在稍后被解释的。现在你需要知道是你能否用这个矩阵将点P(1, 0, 0)移动到Pt(0,1,0)的位置。
我们的三角知识在这里就要用到了。如果你看到下图中的单位元你可以知道x轴和y轴的的坐标可以通过三角函数的正,余弦
当Q为0的时候,x = 1 和y = 0.当Q为90度的时候, x = 0 和 y = 1。那很有趣,因为你可以注意到x = 0, y = 1,是R00/R11和R01/R10的值,因此我们可以重新写这个矩阵:
如果你只想旋转45度,你会得到一个(0.7071, 0.7071)的坐标值,就如下图所示:
因此我们可以把菊展写成下面的样式
我们知道现在使用R矩阵将P点旋转到Pt点是有效的,但是有时也会出现P(0,1,0和Pt(1, 0, 0)的情况,顺时针旋转90度。这个矩阵R还会有效吗?
让我们来检查一下:
这看起来不是很正确的,因为我们开始的坐标是(0, 1, 0)现在通过变换之后获取的坐标值是(-1, 0, 0)而不是(1, 0, 0)。如果我们想要坐标是(1, 0, 0),R01应该是1而不是-1.那样我们应该新生成一个R:
我们知道如果我们将在xy平面上的点绕z轴旋转的话它还在xy平面上(因此我么的旋转矩阵Rz不会影响到z轴的坐标Pt)。当我们看到从P移动到Pt的代码时,很明显可以发现我们的第三排和第三列的计算不会影响到Pt.我们可以看到R02和R12设置为0,第三个参数设置为1,它和Pz相乘不会影响到Pz的值。我们可以总结出绕z轴旋转的点或者向量的生成矩阵有以下的形式:
为了找到点绕x轴和y轴的矩阵(或者是yz和zx平面),我们可以简单地用之前点或者向量绕Z轴旋转寻找矩阵的方法来找到旋转矩阵。我们会把这个留作一个练习给你,考虑到之前绕z轴旋转的知识的话,这个会相对简单很多。如果Rx是绕x轴旋转的矩阵,Ry是绕y轴旋转的矩阵,那么这些矩阵看起来是下面的样子:
记住你你是通过点的坐标和矩阵中每一列的坐标相乘去计算变换后点x, y, z的坐标值。
你可以使用之前坐标系统章节讲到的方式找到如果旋转的角度是正数时该向那边旋转。
对于右手坐标系统,你的大拇指指向旋转的轴,如下图所示,你可以通过其它的手指的转向得到正方向。
对于左手坐标系,使用左手重复上述的过程。
(5)结合旋转矩阵
我们在之前的章节中已经学过,矩阵之间的相乘会将它们的变换结合起来。现在我们知道了矩阵怎么绕单个的轴进行旋转,将Rx, Ry, Rz相乘是去创造一个更复杂的旋转是有可能的。比如你像一个点绕x轴旋转之后,然后绕y轴旋转,你可以创建两个矩阵Rx和Ry,然后用矩阵的乘法再将它们结果起来创建一个Rxy矩阵来表示两个单独的旋转:
Rxy = Rx * Ry;
记住旋转的顺序非常重要而且会对结果造成不同。如果你将一个点绕x轴旋转之后然后绕y轴旋转,你会得到一个结果就是先绕y轴旋,然后绕x轴旋转。在大多数的3D包中,比如Maya,3DSMax, Softimage, Houdini, 等。它是可以指定旋转发生的先后顺序。比如旋转的顺序是xyz(查看Maya的列举的可能选项)。
(6)移动矩阵
为了移动一个点我们使用点和矩阵的乘法,为此我们需要一个[4X4]的矩阵。这个章节将会限制在[3X3]矩阵中,我们会在点和向量的平移章节解释平移和矩阵的关系。
(7)任意轴旋转
一个点或者向量绕着任意的轴旋转是有可能的。但是,这个对于写基本的光线追踪没有什么作用,我们会在其它的基础章节讲完之后会在未来的版本中涉及到这些知识。