Matrix是一个矩阵,主要功能是坐标映射,数值转换。
它看起来大概是下面这样:
Matrix作用就是坐标映射,那么为什么需要Matrix呢? 举一个简单的例子:
我的的手机屏幕作为物理设备,其物理坐标系是从左上角开始的,但我们在开发的时候通常不会使用这一坐标系,而是使用内容区的坐标系。
以下图为例,我们的内容区和屏幕坐标系还相差一个通知栏加一个标题栏的距离,所以两者是不重合的,我们在内容区的坐标系中的内容最终绘制的时候肯定要转换为实际的物理坐标系来绘制,Matrix在此处的作用就是转换这些数值。
假设通知栏高度为20像素,导航栏高度为40像素,那么我们在内容区的(0,0)位置绘制一个点,最终就要转化为在实际坐标系中的(0,60)位置绘制一个点。
以上是仅作为一个简单的示例,实际上不论2D还是3D,我们要将图形显示在屏幕上,都离不开Matrix,所以说Matrix是一个在背后辛勤工作的劳模。
Matrix特点
- 作用范围更广,Matrix在View,图片,动画效果等各个方面均有运用,相比与之前讲解等画布操作应用范围更广。
- 更加灵活,画布操作是对Matrix的封装,Matrix作为更接近底层的东西,必然要比画布操作更加灵活。
- 封装很好,Matrix本身对各个方法就做了很好的封装,让开发者可以很方便的操作Matrix。
- 难以深入理解,很难理解中各个数值的意义,以及操作规律,如果不了解矩阵,也很难理解前乘,后乘。
常见误解
1.认为Matrix最下面的一行的三个参数(MPERSP_0、MPERSP_1、MPERSP_2)没有什么太大的作用,在这里只是为了凑数。
实际上最后一行参数在3D变换中有着至关重要的作用,这一点会在后面中Camera一文中详细介绍。
2.最后一个参数MPERSP_2被解释为scale
的确,更改MPERSP_2的值能够达到类似缩放的效果,但这是因为齐次坐标的缘故,并非这个参数的实际功能。
Matrix基本原理
Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就看看几种常见变换的原理:
我们所用到的变换均属于仿射变换,仿射变换是 线性变换(缩放,旋转,错切) 和 平移变换(平移) 的复合,由于这些概念对于我们作用并不大,此处不过多介绍,有兴趣可自行了解。
基本变换有4种: 平移(translate)、缩放(scale)、旋转(rotate) 和 错切(skew)。
下面我们看一下四种变换都是由哪些参数控制的。
从上图可以看到最后三个参数是控制透视的,这三个参数主要在3D效果中运用,通常为(0, 0, 1),不在本篇讨论范围内,暂不过多叙述,会在之后对文章中详述其作用。
由于我们以下大部分的计算都是基于矩阵乘法规则,如果你已经把线性代数还给了老师,请参考一下这里: 维基百科-矩阵乘法
1.缩放(Scale)
你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
(x, y, 1) - 点
(x, y, 0) - 向量
另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)…(2N,3N,N)表示的均是(2,3)这一个点。(将MPERSP_2解释为scale这一误解就源于此)。
图例:
2.错切(Skew)
错切存在两种特殊错切,水平错切(平行X轴)和垂直错切(平行Y轴)。
水平错切
用矩阵表示:
图例:
垂直错切
用矩阵表示:
图例:
复合错切
水平错切和垂直错切的复合。
用矩阵表示:
图例:
3.旋转(Rotate)
假定一个点 A(x0, y0) ,距离原点距离为 r, 与水平轴夹角为 α 度, 绕原点旋转 θ 度, 旋转后为点 B(x, y) 如下:
用矩阵表示:
图例:
4.平移(Translate)
此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。
Matrix复合原理
其实Matrix的多种复合操作都是使用矩阵乘法实现的,从原理上理解很简单,但是,使用矩阵乘法也有其弱点,后面的操作可能会影响到前面到操作,所以在构造Matrix时顺序很重要。
我们常用的四大变换操作,每一种操作在Matrix均有三类,前乘(pre),后乘(post)和设置(set),可以参见文末对Matrix方法表,由于矩阵乘法不满足交换律,所以前乘(pre),后乘(post)和设置(set)的区别还是很大的。
前乘(pre)
前乘相当于矩阵的右乘
这表示一个矩阵与一个特殊矩阵前乘后构造出结果矩阵。
后乘(post)
后乘相当于矩阵的左乘
这表示一个矩阵与一个特殊矩阵后乘后构造出结果矩阵。
设置(set)
设置使用的不是矩阵乘法,而是直接覆盖掉原来的数值,所以,使用设置可能会导致之前的操作失效。
组合
关于 Matrix 的文章终有一个问题,就是 pre 和 post 这一部分的理论非常别扭,国内大多数文章都是这样的,看起来貌似是对的但很难理解,部分内容违背直觉。
经过良久的思考之后,决定抛弃国内大部分文章的那套理论和结论,只用严谨的数学逻辑和程序逻辑来阐述这一部分的理论,也许仍有疏漏,如有发现请指正。
首先澄清两个错误结论,记住,是错误结论,错误结论,错误结论。
错误结论一:pre 是顺序执行,post 是逆序执行。
这个结论很具有迷惑性,因为这个结论并非是完全错误的,你很容易就能证明这个结论,例如下面这样:
这两段代码最终结果是等价的,于是轻松证得这个结论的正确性,但事实真是这样么?
首先,从数学角度分析,pre 和 post 就是右乘或者左乘的区别,其次,它们不可能实际影响运算顺序(程序执行顺序)。以上这两段代码等价也仅仅是因为最终化简公式一样而已。
设原始矩阵为 M,平移为 T ,旋转为 R ,单位矩阵为 I ,最终结果为 M’
- 矩阵乘法不满足交换律,即 A*B ≠ B*A
- 矩阵乘法满足结合律,即 (A*B)*C = A*(B*C)
- 矩阵与单位矩阵相乘结果不变,即 A * I = A
由于两者最终的化简公式是相同的,所以两者是等价的,但是,这结论不具备普适性。
即原始矩阵不为单位矩阵的时候,两者无法化简为相同的公式,结果自然也会不同。另外,执行顺序就是程序书写顺序,不存在所谓的正序逆序。
错误结论二:pre 是先执行,而 post 是后执行。
所以就得出了 pre 先执行,而 post 后执行这一说法,但从严谨的数学和程序角度来分析,完全是不可能的,还是上面所说的,pre 和 post 不能影响程序执行顺序,而程序每执行一条语句都会得出一个确定的结果,所以,它根本不能控制先后执行,属于完全扯淡型。
如果非要用这套理论强行解释的话,反而看起来像是 post 先执行,例如:
同样化简公式:
从实际上来说,由于矩阵乘法满足结合律,所以不论你说是靠右先执行还是靠左先执行,从结果上来说都没有错。
如何理解和使用 pre 和 post ?
不要去管什么先后论,顺序论,就按照最基本的矩阵乘法理解。
那么如何使用?
正确使用方式就是先构造正常的 Matrix 乘法顺序,之后根据情况使用 pre 和 post 来把这个顺序实现。
还是用一个最简单的例子理解,假设需要围绕某一点旋转。
可以用这个方法 xxxRotate(angle, pivotX, pivotY) ,由于我们这里需要组合构造一个 Matrix,所以不直接使用这个方法。
首先,有两条基本定理:
- 所有的操作(旋转、平移、缩放、错切)默认都是以坐标原点为基准点的。
- 之前操作的坐标系状态会保留,并且影响到后续状态。
基于这两条基本定理,我们可以推算出要基于某一个点进行旋转需要如下步骤:
具体公式如下:
M 为原始矩阵,是一个单位矩阵, M‘ 为结果矩阵, T 为平移, R为旋转
按照公式写出来的伪代码如下:
围绕某一点操作可以拓展为通用情况,即:
公式为:
但是这种方式,两个调整中心的平移函数就拉的太开了,所以通常采用这种写法:
这样公式为:
可以看到最终化简结果是相同的。
所以说,pre 和 post 就是用来调整乘法顺序的,正常情况下应当正向进行构建出乘法顺序公式,之后根据实际情况调整书写即可。
在构造 Matrix 时,个人建议尽量使用一种乘法,前乘或者后乘,这样操作顺序容易确定,出现问题也比较容易排查。当然,由于矩阵乘法不满足交换律,前乘和后乘的结果是不同的,使用时应结合具体情景分析使用。
下面我们用不同对方式来构造一个相同的矩阵:
注意:
- 1.由于矩阵乘法不满足交换律,请保证使用初始矩阵(Initial Matrix),否则可能导致运算结果不同。
- 2.注意构造顺序,顺序是会影响结果的。
- 3.Initial Matrix是指new出来的新矩阵,或者reset后的矩阵,是一个单位矩阵。
1.仅用pre:
2.仅用post:
3.混合:
或:
注意: 由于矩阵乘法不满足交换律,请保证初始矩阵为单位矩阵,如果初始矩阵不为单位矩阵,则导致运算结果不同。
上面虽然用了很多不同的写法,但最终的化简公式是一样的,这些不同的写法,都是根据同一个公式反向推算出来的。
Matrix方法表