人人的懂的齐次坐标

83 阅读4分钟

想要了解齐次坐标,先要了解矩阵中的平移,缩放和旋转。

1. 学习知识点

  1. 什么是仿射变换,什么叫线性变换?
  2. 平移,旋转与缩放。
  3. 齐次坐标
  4. 案例:transform matrix 理解

2. 什么是仿射变换,什么叫线性变换?

仿射变换简单来说就是“线性变换 + 平移”。

实际上在平常的 Web 开发中,我们也经常会用到仿射变换,比如,对元素设置 CSS 的 transform 属性就是对元素应用仿射变换。 线性变换主要是指,旋转和缩放

2.1 仿射变换的2个性质:

  1. 仿射变换前是直线段的,仿射变换后依然是直线段。
  2. 对两条直线段 a 和 b 应用同样的仿射变换,变换前后线段长度比例保持不变。

由于仿射变换具有这两个性质,因此对线性空间中的几何图形进行仿射变换,就相当于对它的每个顶点向量进行仿射变换。

2.2 线性变换的两个性质:

  1. 线性变换不改变坐标原点(因为如果 x0、y0等于零,那么 x、y 肯定等于 0)。
  2. 线性变换可以叠加,多个线性变换的叠加结果就是将线性变换的矩阵依次相乘,再与原始向量相乘。

3. 平移,旋转与缩放

常见的仿射变换主要包括,平移,旋转,缩放以及他们的组合。

3.1 平移的几何意义

如果我们想让向量 P(x, y) 沿着向量 Q(x1, y1) 平移,只要将 P 和 Q 相加就可以了。

3.2 旋转的几何意义

image.png

如果我们想让向量 P(x, y) 沿着向量 Q(x1, y1) 平移,只要将 P 和 Q 相加就可以了。

image.png

因为 rcos(a),rsin(a)是向量p原始的坐标x0,y0,所以,带入坐标公式中: image.png

最终我们代数矩阵形式中就得到了下面的公式: image.png

3.2 缩放的几个意义

缩放变换也很简单,我们可以直接让向量与标量(标量只有大小、没有方向)相乘。

image.png 矩阵的表达形式: image.png

3.3 推导线性变换公式

根据线性变换性质2:线性变换可以叠加,多个线性变换的叠加结果就是将线性变换的矩阵依次相乘,再与原始向量相乘。 可得出:

3.4 仿射变换的终极公式

仿射变换的终极公式是:

4. 齐次坐标

由于仿射变换中的即存在矩阵的乘法,又存在矩阵的加法,导致在矩阵的运算中十分不方便,因此,我们需要把它升高维度,让它变成线性变换。

而这种将原本 n 维的坐标转换为了 n+1 维的坐标。这种 n+1 维坐标被称为齐次坐标,对应的矩阵就被称为齐次矩阵。

齐次坐标和齐次矩阵是可视化中非常常用的数学工具,它能让我们用线性变换来表示仿射变换。 这样一来,我们就能利用线性变换的叠加性质,来非常方便地进行各种复杂的仿射变换了。

4.1 矩阵叉乘的运算方式

image.png

5. 案例:transform matrix 理解

4.1 实现案例:

对于div实现先旋转 30 度,然后平移 100px、50px,最后再放大 0.5 倍。实际上相当于我们做了如下变换;

4.2 普通api的写法

div.block{
   rotate(30deg) translate(100px,50px) scale(1.5)
}

4.2 矩阵写法

首先根据矩阵乘法,我们要实现一个矩阵乘法的函数

function multiply(out, a, b) {
  let a00 = a[0],
      a01 = a[1],
      a02 = a[2];
  let a10 = a[3],
      a11 = a[4],
      a12 = a[5];
  let a20 = a[6],
      a21 = a[7],
      a22 = a[8];
  
  let b00 = b[0],
      b01 = b[1],
      b02 = b[2];
  let b10 = b[3],
      b11 = b[4],
      b12 = b[5];
  let b20 = b[6],
      b21 = b[7],
      b22 = b[8];
  
  out[0] = b00 * a00 + b01 * a10 + b02 * a20;
  out[1] = b00 * a01 + b01 * a11 + b02 * a21;
  out[2] = b00 * a02 + b01 * a12 + b02 * a22;
  
  out[3] = b10 * a00 + b11 * a10 + b12 * a20;
  out[4] = b10 * a01 + b11 * a11 + b12 * a21;
  out[5] = b10 * a02 + b11 * a12 + b12 * a22;
  
  out[6] = b20 * a00 + b21 * a10 + b22 * a20;
  out[7] = b20 * a01 + b21 * a11 + b22 * a21;
  out[8] = b20 * a02 + b21 * a12 + b22 * a22;
  return out;
}

矩阵的乘法本质上,就是每一行乘以每一列;

构建各种的矩阵

const rotate = [
  Math.cos(rad), -Math.sin(rad), 0,
  Math.sin(rad), Math.cos(rad), 0,
  0, 0, 1,
];

const translate = [
  1, 0, 100, 
  0, 1, 50,
  0, 0, 1
];

const scale = [
  0.5, 0, 0,
  0, 0.5, 0,
  0, 0, 1
];

各种类型的矩阵相乘

var out = [rotate, translate, scale].reduce((a, b) => multiply([], b, a));

// 结果:
[
  0.43301270189221935, -0.24999999999999997, 61.60254037844388, 
  0.24999999999999997, 0.43301270189221935, 93.30127018922192,
  0, 0, 1
]

根据矩阵api填充参数

transform: matrix(a,b,c,d,e,f);

其对应关系是: image.png 因此最终使用的结果:

dom.style.transform = `matrix(${[
    out[0],
    out[3],
    out[1],
    out[4],
    out[2],
    out[5],
  ].join(",")})`;

// 这个效果与上面的效果是一样的

rotate(30deg) translate(100px,50px) scale(1.5)

致谢

如果感觉我的文章有用,请关注下我的公众号: 前端小黄。 我将不定期更新我的原创文章。 本文章源码地址:github.com/hpstream/We…