# 二维矩阵

如何对二维物体进行平移, 旋转,和 缩放。 每种变换都改变了着色器并且这些变换还受先后顺序影响。在 前例中我们先缩放,再旋转,最后平移,如果执行顺序不同 结果也不同。

例如这是缩放 2, 1 ,旋转30度,然后平移 100, 0 的结果。

f-scale-rotation-translation

这是平移 100, 0 ,旋转30度,然后缩放 2, 1 的结果。

f-translation-rotation-scale

结果截然不同,更糟的是,针对第二种情况中的转换顺序,需要写一个新的着色器。

有些比我聪明的人可能已经想到了矩阵,对于二维我们使用 3x3 的矩阵, 3x3 的矩阵就像是有9个格子的格网。

const matrix = [
 [0.0, 0.1, 0.2],
 [1.0,.1.1, 1.2],
 [2.0, 2.1, 2.2],
]

而坐标则看作一个列向量

const v = [
  x,
  y,
  1.0
]

在计算的时候我们将位置坐标沿着矩阵列的方向依次相乘再将结果加起来。 我们的位置信息只有两个值, x 和 y 。但是要进行运算需要三个值, 所以我们将第三个值赋值为 1 。

在这个例子中结果将是

newX = x * matrix[0][0] + y * matrix[1][0] + 1.0 * matrix[2][0]
newY = x * matrix[0][1] + y * matrix[1][1] + 1.0 * matrix[2][1]
extra = x * matrix[0][2] + y * matrix[1][2] + 1.0 * matrix[2][2]

你可能会想“这样做有什么意义?”,好吧,假设我们要进行平移, 平移的量为 tx 和 ty ,然后定义一个这样的矩阵

const matrix = [
 [1.0, 0.0, 0.0],
 [0.0, 1.0, 0.0],
 [tx ,  ty, 1.0],
]

注意到 matrix[0][0] = 1 matrix[1][0] = 0 matrix[2][0] = tx,可以得出

newX = x + tx

同理matrix[0][1] = 0 matrix[1][1] = 1 matrix[2][1] = ty,可以得出

newY = y + ty

其他的就不用关心了。这个看起来和平移例子中的代码有些相似。

同样的来实现旋转,在旋转章节提到过,旋转只需要和旋转角对应的正弦和余弦值,可以得到旋转矩阵。

const cos = Math.cos(angle)
const sin = Math.sin(angle)
const matrix = [
 [cos,-sin, 0.0],
 [sin, cos, 0.0],
 [0.0, 0.0, 1.0],
]

使用矩阵后得到 (记得把 y 看作 cosx 看作 sin )

newX = x * cos + y * sin // sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b)
newY = x * -sin + y * cos // cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b)
extra = 1

最后是缩放,我们将两个缩放因子叫做 sx 和 sy 。

然后创建一个这样的矩阵

const matrix = [
 [ sx, 0.0, 0.0],
 [0.0,  sy, 0.0],
 [0.0, 0.0, 1.0]
]

使用矩阵后得到

newX = x * sx
newY = y * sy

现在你可能还会想“那又怎样,有什么意义?”, 好像花了更多精力做之前做过的事情。

现在开始有趣的部分了,假定有一个方法 m3.multiply 可以将两个矩阵相乘并返回结果,矩阵相乘后他们可以用一个矩阵代表三个变换。

为了方便讲解,我们先创建平移,旋转和缩放矩阵。

const m3 = {
  translation: function(tx, ty) {
    return [
      1, 0, 0,
      0, 1, 0,
      tx, ty, 1,
    ];
  },
 
  rotation: function(angleInRadians) {
    const c = Math.cos(angleInRadians);
    const s = Math.sin(angleInRadians);
    return [
      c,-s, 0,
      s, c, 0,
      0, 0, 1,
    ];
  },
 
  scaling: function(sx, sy) {
    return [
      sx, 0, 0,
      0, sy, 0,
      0, 0, 1,
    ];
  },
};

现在该修改着色器了,新的着色器就简单多了。




 



 









attribute vec2 a_position;

uniform vec2 u_resolution;
uniform mat3 u_matrix;

void main() {
  // 将位置乘以矩阵
  vec2 position = (u_matrix * vec3(a_position, 1)).xy;
  // convert the position from pixels to 0.0 to 1.0
  vec2 zeroToOne = position / u_resolution;
  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;
  // convert from 0->2 to -1->+1 (clipspace)
  vec2 clipSpace = zeroToTwo - 1.0;
  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}