背景
在图形图像领域,矩阵是一个应用广泛,且极其重要的工具。简单的,我们在OpenGL的Shader中,可以利用矩阵进行视图变换,比如透视、投影等。但本文不打算讨论这些内容,而是聚焦在如何利用矩阵把坐标从一个坐标系变换到另一个坐标系,并且保证坐标的相对位置不变,即计算一个坐标系上的点在另一个坐标系的投影。本文只探讨平面坐标系的问题,并且假设读者对矩阵知识有一定的了解,如果对矩阵比较陌生,建议先复习一下这部分知识。
通常,一个成熟的图像处理软件会(比如大名鼎鼎的Photoshop)引入这些概念,图层、画布和窗口。图层是软件的直接处理对象,简单的一张图片就可以作为一个图层,图层通常不止一个,并且会有各种各样的操作,比如缩放、旋转和位移;画布则是所有图层的载体,对图层的各种处理结果会直接表现在画布上,简单说画布就是图像的最终处理结果;而窗口则是画布的载体,只是简单的显示画布,不会直接和图层进行耦合,但是操作图层的坐标通常源于窗口。
这种分层结构使得图像的处理逻辑变得清晰,但同时也变得更为复杂。一个典型的问题,点击窗口上的点P,然后在图层上绘制一个点P`,使得点P与点P`在窗口上重合(点P在图层上的投影)。注意,由于图层会缩放、旋转和位移,所以点P与点P`的坐标通常不会相等,点P需要通过一系列计算才能得出点P`的坐标,而矩阵就是可以进行这种关系计算的数学工具。
定义问题
上面提到了图层、画布、窗口三种结构,实际上对应着一个三层嵌套坐标系统,它们有这样的关系,窗口坐标系作为整个坐标系统的根,是画布坐标系的直接父级坐标系,同样的画布坐标系是图层坐标系的直接父级坐标系。三个坐标系的转换计算看起来很复杂,但实际上我们只要找出其中两个坐标系的矩阵计算方法,即可推广到所有坐标系,不管坐标系统有多少层级。
如下图,坐标系统由xOy、x`O`y`两个坐标系组成,分别对应画布坐标系、图层坐标系,那么容易得出坐标系转换问题的数学定义。
已知O(0, 0)、O`(0, 0)分别是两个坐标系的原点,点O`在坐标系xOy中的坐标为(1, 3),P(3, 4)是xOy上的一点,∠α=30°,求点P在x`O`y`上的投影P`的坐标值。这是一个典型的矩阵运算问题。
我们知道,对坐标系上的点进行缩放、旋转和位移,使用4x4矩阵表示如下。由于本文只探讨平面坐标系中的问题,但是为了表示第三维的存在,所以在单位矩阵中z轴的值为1。矩阵的第四维是为了解决位移无法使用3x3矩阵表示的问题而引入的齐次坐标。
假设缩放、旋转和位移矩阵分别为Ms、Mr、Mt,那么总的变换矩阵M可以表示如下。交换律不适用于矩阵乘法,所以顺序很重要。注意,矩阵表示的顺序和实际的变换顺序是相反的。
解决问题
了解变换矩阵后,我们重新回到坐标变换的问题,这里为了简化问题,暂且忽略坐标系的缩放,那么解决问题可以分为以下两步:
- 第一步,只考虑位移不考虑旋转,此时两个坐标系的状态如图一,设旋转前的x`O`y`上有一点P`,坐标值与点P(3, 4)相等,已知点O`在xOy上点坐标值为(1, 3),若P`向x`轴位移-1,向y`轴位移-3,即位移向量为(-1, -3),那么x`O`y`上的P`将与xOy上的点P重合 。
- 第二步,在第一步的基础上接着考虑旋转问题,让x`O`y`绕点O`旋转∠α=30°。如图二,此时点P`与点P不再重合。如果让P`反向旋转∠α,P`与点P将再次重合,最终算得P在x`O`y`上的投影,如图3。
至此,我们容易得出下面的坐标变换矩阵计算式,点P(3, 4)位移(-1, -3) 后,反向旋转∠α,最终点P`坐标约为(1.232, 1.866)。
接下来考虑坐标系的缩放的问题,设x`O`y`的x`和y`缩放系数分别为sx、sy,同理可以得出缩放矩阵。同旋转位移一样,所求坐标的缩放系数与x`O`y`的相反。代入公式容易得出以下计算式。
到这里我们就可以在保持相对位置不变的前提下,把坐标从一个坐标系变换到另一个坐标系了。这类应用还有很多,如已知窗口上一个裁剪框的坐标,要求对画布上的图层进行裁剪,再比如画笔等。需要注意的是,OpenGL坐标系都是归一化的,需要利用投影矩阵再计算一次才能实际应用到Shader中,当然也可以把矩阵放到Shader计算,只是和CPU同步比较困难。最终源码参考Al2DCoordinate.cpp实现。
hwvc项目:
https://github.com/imalimin/hwvc/tree/develop
Al2DCoordinate源码:https://github.com/imalimin/hwvc/blob/develop/src/al_common/math/Al2DCoordinate.cpp