2D变换矩阵通常由一个3*2的矩阵来构成,因为2*2的矩阵只能实现旋转、缩放、变形,这三种行为。想实现平移就需要多引入一个维度,在计算时向量也要加上一个齐次项。那么,给定一个这样的3*2的矩阵,如何将其中的旋转、缩放、变形、平移,逐个分离出来呢? 假如现在我们有个这样的矩阵 origin=
而我们要从这个现有的矩阵中分析出下列参数: x方向平移: translateX y方向平移: translateY x方向缩放: scaleX y方向缩放: scaleY 旋转的角度: rotation 残余矩阵: remainder 在用矩阵去乘向量的时候,由于2D变换矩阵是一个3*2(具有三行的)矩阵,向量也必须是三维的才能被这个矩阵乘,所以在计算时我们会给向量添加一个齐次项。比如 vec2(x,y) 要被这个矩阵乘的话先要变成 vec3(x,y,1) ,然后相乘得到结果: vec2(x*a+y*c+1*e,x*b+y*d+1*f) 可见此处 e 和 f 是系数是 1 ,它们就是用来平移的。于是有: translateX = e translateY = f 这一步是毫无悬念的。而且既然我们把 e 和 f 独立作为平移来处理了,后面的计算就不用再考虑它们了,直接把原矩阵当做一个2*2的矩阵来用即可。下一步,我们要从这个矩阵中抽取出缩放参数。如果一个变换没有缩放的话,那么这个二阶矩阵在平面坐标系中对应的两个向量就是已经归一化的,并且其行列式的值不为负。那么首先我们应该计算出这两个向量的模长,以便后续的归一化: lengthX = length(vec2(a,b)) lengthY = length(vec2(c,d)) 但这只是模长而已,如果直接用这个值来作为归一化系数,结果只能得到两个归一化后的向量,而无法确保整个矩阵的行列式值不为负。所以此处还应该判断行列式的值是否为负,如果其值为负就需要对 lengthX 或 lengthY 的其中之一取负值。其实无论对谁取负值都可以,不过通常会尽量取短的,把更多的操作交给后面的步骤。 scaleX = determinant(origin)<0?-1:1 * lengthX scaleY = lengthY 在取出缩放参数后,我们的原矩阵变成了这样: origin=
接着是对旋转的处理。如何判断旋转了多少呢?按照之前的思路,也就是将这个矩阵中的两行视为两个向量的话,这些向量的旋转角度就是整个矩阵提供的旋转角。而我们的矩阵有两行,也就是有两个向量,这就有两种选择。其实用哪个向量来判断旋转角都可以,选一条向量作为计算旋转的依据,然后通过 atan2 来计算 rotation = atan2(a/scaleY,b/scaleY) 在计算完 rotation 之后,我们还应该让矩阵倒着旋转回去,以恢复到旋转前的状态,因为我们已经把这个旋转行为抽取出来了。其实由于先前已经执行过归一化,这里的 sin(rotation) 和 cos(rotation) 的结果实际上就是 a/scaleY 和 b/scaleY ,因此可以构造逆旋转矩阵 invRot=
然后把先前剩下的矩阵乘以这个逆旋转矩阵,剩余的东西就是 remainder = origin * invRot 最后这个残余的矩阵提供了变形变换,如果没有变形的话这个残余矩阵应该会得到一个单位矩阵。至此,2D变换矩阵的所有参数都已分离出来了,当然,从以上过程可以看出,这个分离行为并没有唯一套路,其中有一些可供选择的做法。当然,对于一些特殊的应用场景,某些算法可以会强制约束这些选项,以保证结果的唯一性。 |
|