Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

2D变换矩阵的分解

  2D变换矩阵通常由一个3*2的矩阵来构成,因为2*2的矩阵只能实现旋转、缩放、变形,这三种行为。想实现平移就需要多引入一个维度,在计算时向量也要加上一个齐次项。那么,给定一个这样的3*2的矩阵,如何将其中的旋转、缩放、变形、平移,逐个分离出来呢?
  假如现在我们有个这样的矩阵
    origin=
ab
cd
ef

  而我们要从这个现有的矩阵中分析出下列参数:
    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)
  可见此处ef是系数是1,它们就是用来平移的。于是有:
    translateX=e
    translateY=f
  这一步是毫无悬念的。而且既然我们把ef独立作为平移来处理了,后面的计算就不用再考虑它们了,直接把原矩阵当做一个2*2的矩阵来用即可。下一步,我们要从这个矩阵中抽取出缩放参数。如果一个变换没有缩放的话,那么这个二阶矩阵在平面坐标系中对应的两个向量就是已经归一化的,并且其行列式的值不为负。那么首先我们应该计算出这两个向量的模长,以便后续的归一化:
    lengthX=length(vec2(a,b))
    lengthY=length(vec2(c,d))
  但这只是模长而已,如果直接用这个值来作为归一化系数,结果只能得到两个归一化后的向量,而无法确保整个矩阵的行列式值不为负。所以此处还应该判断行列式的值是否为负,如果其值为负就需要对lengthXlengthY的其中之一取负值。其实如果没有特殊需求的话无论对谁取负值都可以。
    scaleX=determinant(origin)<0?-1:1*lengthX
    scaleY=lengthY
  在取出缩放参数后,我们的原矩阵变成了这样:
    origin=
a/scaleXb/scaleX
c/scaleYd/scaleY

  接着是对旋转的处理。如何判断旋转了多少呢?按照之前的思路,也就是将这个矩阵中的两行视为两个向量的话,这些向量的旋转角度就是整个矩阵提供的旋转角。而我们的矩阵有两行,也就是有两个向量,这就有两种选择。其实用哪个向量来判断旋转角都可以,选一条向量作为计算旋转的依据,然后通过atan2来计算
    rotation=atan2(c/scaleY,d/scaleY)
  在计算完rotation之后,我们还应该让矩阵倒着旋转回去,以恢复到旋转前的状态,因为我们已经把这个旋转行为抽取出来了。其实由于先前已经执行过归一化,这里的sin(rotation)cos(rotation)的结果实际上就是c/scaleYd/scaleY,因此可以构造逆旋转矩阵
    invRot=
d/scaleYc/scaleY
-c/scaleYd/scaleY

  然后把先前剩下的矩阵乘以这个逆旋转矩阵,剩余的东西就是
    remainder=origin*invRot
  最后这个残余的矩阵提供了变形变换,如果没有变形的话这个残余矩阵应该会得到一个单位矩阵。至此,2D变换矩阵的所有参数都已分离出来了,当然,从以上过程可以看出,这个分离行为并没有唯一套路,其中有一些可供选择的做法。当然,对于一些特殊的应用场景,某些算法可能会强制约束这些选项,以保证结果的唯一性。
网名:
3.84.186.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^