利用矩阵进行坐标系转换
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用矩阵进行坐标系转换相关的知识,希望对你有一定的参考价值。
参考技术A 原文链接m×n 矩阵是排列在 m 行和 n 列中的一系列数。下图显示几个矩阵。
可以通过将单个元素相加来加合两个尺寸相同的矩阵。下图显示了两个矩阵相加的示例。
m×n 矩阵可与一个 n×p 矩阵相乘,结果为一个 m×p 矩阵。第一个矩阵的列数必须与第二个矩阵的行数相同。例如,一个 4×2 矩阵与一个 2×3 矩阵相乘,产生一个 4×3 矩阵。
矩阵的行列的平面点可视为矢量。例如,(2, 5) 是具有两个组件的矢量,(3, 7, 1) 是具有三个组件的矢量。两个矢量的点积定义如下:
例如,(2, 3) 和 (5, 4) 的点积是 (2)(5) + (3)(4) = 22。(2, 5, 1) 和 (4, 3, 1) 的点积是 (2)(4) + (5)(3) + (1)(1) = 24。请注意,两个矢量的点积是数字,而不是另一个矢量。另外请注意,只有当两个矢量的组件数相同时,才能计算点积。
将 A(i, j) 作为矩阵 A 中第 i 行、第 j 列的项。例如,A(3, 2)是矩阵 A 中第 3 行、第 2 列的项。假定 A、B 和 C 是矩阵,且 AB = C,则 C 的项计算如下:
以第二个等式为例,假设等式两边的矩阵分别是a、b、c,1 * 3的矩阵和3 * 2的矩阵相乘,得到的结果为1 * 2的矩阵。
其中c[0][0] = a[0][0] * b[0][0]+a[0][1] * b[1][0]+a[0][2] * b[2][0],c[0][1]=a[0][0] * b[0][1]+a[0][1] * b[1][1]+a[0][2] * b[2][1]。
矩阵的加法、乘法,可以用来做坐标转换。我们通常使用3 * 3(如果不需要旋转,则2*2的矩阵即可)的矩阵来做平面上的各种坐标转换,包括x/y轴的平移、旋转。现在来看一个简单的坐标系转换的例子:假设我们的客户区分辨率是100 * 100,要在客户区中心点画一个点,这个点的坐标是(x, y)。现在如果我们调整了客户区分辨率为400 * 300,此时如果还需要保持这个点的相对位置不变,计算他的坐标应该是(x * 400 / 100, y * 300 / 100)。这个计算过程很简单,那么用矩阵操作应该如何来实现呢?
我们将这个点视为一个1 * 2的矩阵,将其乘以一个2 * 2的矩阵,得出的仍然是一个1 * 2的矩阵,就是新的坐标了。由于屏幕分辨率在x、y轴分别扩大为原来的4倍和3倍,那么我们只要将点的x、y轴坐标都扩大到原来的4、3倍即可。公式如下:
等式左边的第二个矩阵,就是用来实现坐标转换的矩阵。其中b[0][0]就是x轴的扩大倍数,b[1][1]就是在y轴上的扩大倍数。这里面b[0][1]和b[1][0]永远是0。坐标系的这种转换,叫做线性变换。
OK。看完这个例子,是不是觉得用矩阵比直接计算还麻烦?嗯,对于这种简单的情况是这样的。不过别急,继续看坐标系旋转的情况,如果现在要求这个客户区逆时针旋转30度,要保持这个点的相对位置不变,他的新坐标应该是多少呢?
普通的计算的公式就不陈述了,这就是个初中几何题目。我们直接来看怎样通过矩阵操作实现。首先看公式:在二维空间中,旋转可以用一个单一的角 θ 定义。作为约定,正角表示逆时针旋转。关于原点逆时针旋转 θ 的矩阵是:
也就是说,逆时针旋转30度的新坐标就是:
当然,除此之外,坐标系还有平移,但是这个就简单了,只是一个简单的矩阵加法。比如(x, y)向右平移一个单位,用矩阵就是[x, y] + [1, 0]就是是(x + 1, y)。
下图显示了应用于点 (2, 1) 的几个变换:
前图中显示的所有变换都是线性变换。某些其他变换(如平移)不是线性的,不能表示为与 2×2 矩阵相乘的形式。假定您要从点 (2, 1) 开始,将其旋转 90 度,在 x 方向将其平移 3 个单位,在 y 方向将其平移 4 个单位。可通过先使用矩阵乘法再使用矩阵加法来完成此操作。
后面跟一平移(与 1×2 矩阵相加)的线性变换(与 2×2 矩阵相乘)称为仿射变换,如上图所示。放射变换(先乘后加)可以通过乘以一个3*3的矩阵来实现,若要使其起作用,平面上的点必须存储于具有虚拟第三坐标的 1×3 矩阵中。通常的方法是使所有的第三坐标等于 1。例如,矩阵 [2 1 1] 代表点 (2, 1)。下图演示了表示为与单个 3×3 矩阵相乘的仿射变换(旋转 90 度;在 x 方向上平移 3 个单位,在 y 方向上平移 4 个单位):
在前面的示例中,点 (2, 1) 映射到了点 (2, 6)。请注意,3×3 矩阵的第三列包含数字 0,0,1。对于仿射变换的 3×3 矩阵而言,情况将总是如此。重要的数字是列 1 和列 2 中的 6 个数字。矩阵左上角的 2×2 部分表示变换的线性部分,第 3 行中的前两项表示平移。
在使用3 * 3的矩阵做仿射变换时候,表示点的矩阵变成了一个1 * 3矩阵,这个矩阵中的最后一个值(a[0][2])必须设置成1。对于3 * 3矩阵b,其最后一列的值是多少是没有关系的,因为他们不会影响结果中的前两列。不过如上,经常将他们设置为0,0,1。这一列对于坐标转换的结果并没有任何影响,但是他们是必须的,因为矩阵相乘必须满足开篇所讲的“相乘的两个矩阵第一个矩阵的列数必须与第二个矩阵的行数相同”。
在.Net Framework中,又一个矩阵类“Matrix”。其内置了点坐标转换(TransformPoints)、平移(Translate)、缩放(Scale)、旋转(Rotate)方法。下面的示例创建了复合变换(先旋转 30 度,再在 y 方向上缩放 2 倍,然后在 x 方向平移 5 个单位)的矩阵:
除了Matrix类以外,.Net Framework中也有其他用于坐标系转换的类,比如System.Drawing.Graphics。具体用法请查阅相关文档。
以上只是利用矩阵进行平面坐标系转换的方法。如果是三位坐标系,也是可以利用矩阵来操作的,但Matrix类不行,因为其本身的定位就是“封装表示几何变换的 3 x 3 仿射矩阵”。
上面是分别绕单个轴旋转的公式。复杂的旋转可以通过这三个公式组合而成,任何 3 维旋转矩阵都可以用这三个角 θ x , θ y , 和 θ z 来刻画,并且可以表示为 roll, pitch 和 yaw 矩阵的乘积。
openGL 坐标系的互相转换
openGL坐标系包括旋转,平移,缩放被塞在一个矩阵里面。
坐标系之间的转换基础是矩阵的运算。
每个矩阵代表的坐标系,就是是原点坐标系通过旋转。平移,缩放得到的坐标系。
当一个矩阵右乘一个向量或是还有一个矩阵,意味着把右边的变换。变成相对于左边的矩阵坐标系之上。
假设把一个世界坐标的X转换到一个矩阵上,我们能够矩阵右乘这个坐标:
static float multiplyMX(Matrix4* matrix, float x) { return matrix->m[0] * x + matrix->m[4] + matrix->m[8] + matrix->m[12]; }
假设把一个世界坐标Y转换到一个矩阵上。我们能够矩阵右乘这个坐标:
static float multiplyMY(Matrix4* matrix, float y) { return matrix->m[1] + matrix->m[5] * y + matrix->m[9] + matrix->m[13]; }
假设把一个世界坐标点转换到一个矩阵上,我们能够矩阵右乘这个点:
static void multiplyMV4(Matrix4* matrix, float x, float y, float z, float w, Out(Vector4* result)) { result->v[0] = matrix->m[0] * x + matrix->m[4] * y + matrix->m[8] * z + matrix->m[12] * w; result->v[1] = matrix->m[1] * x + matrix->m[5] * y + matrix->m[9] * z + matrix->m[13] * w; result->v[2] = matrix->m[2] * x + matrix->m[6] * y + matrix->m[10] * z + matrix->m[14] * w; result->v[3] = matrix->m[3] * x + matrix->m[7] * y + matrix->m[11] * z + matrix->m[15] * w; } static void multiplyMV3(Matrix4* matrix, float x, float y, float z, Out(Vector3* result)) { result->v[0] = matrix->m[0] * x + matrix->m[4] * y + matrix->m[8] * z + matrix->m[12]; result->v[1] = matrix->m[1] * x + matrix->m[5] * y + matrix->m[9] * z + matrix->m[13]; result->v[2] = matrix->m[2] * x + matrix->m[6] * y + matrix->m[10] * z + matrix->m[14]; } static void multiplyMV2(Matrix4* matrix, float x, float y, Out(Vector2* result)) { result->v[0] = matrix->m[0] * x + matrix->m[4] * y + matrix->m[8] + matrix->m[12]; result->v[1] = matrix->m[1] * x + matrix->m[5] * y + matrix->m[9] + matrix->m[13]; }
假设把一个世界坐标系转换到一个矩阵上,我们矩阵右乘这个矩阵:
static void multiplyMM(Matrix4* left, Matrix4* right, Out(Matrix4* result)) { result->m[0] = left->m[0] * right->m[0] + left->m[4] * right->m[1] + left->m[8] * right->m[2] + left->m[12] * right->m[3]; result->m[1] = left->m[1] * right->m[0] + left->m[5] * right->m[1] + left->m[9] * right->m[2] + left->m[13] * right->m[3]; result->m[2] = left->m[2] * right->m[0] + left->m[6] * right->m[1] + left->m[10] * right->m[2] + left->m[14] * right->m[3]; result->m[3] = left->m[3] * right->m[0] + left->m[7] * right->m[1] + left->m[11] * right->m[2] + left->m[15] * right->m[3]; result->m[4] = left->m[0] * right->m[4] + left->m[4] * right->m[5] + left->m[8] * right->m[6] + left->m[12] * right->m[7]; result->m[5] = left->m[1] * right->m[4] + left->m[5] * right->m[5] + left->m[9] * right->m[6] + left->m[13] * right->m[7]; result->m[6] = left->m[2] * right->m[4] + left->m[6] * right->m[5] + left->m[10] * right->m[6] + left->m[14] * right->m[7]; result->m[7] = left->m[3] * right->m[4] + left->m[7] * right->m[5] + left->m[11] * right->m[6] + left->m[15] * right->m[7]; result->m[8] = left->m[0] * right->m[8] + left->m[4] * right->m[9] + left->m[8] * right->m[10] + left->m[12] * right->m[11]; result->m[9] = left->m[1] * right->m[8] + left->m[5] * right->m[9] + left->m[9] * right->m[10] + left->m[13] * right->m[11]; result->m[10] = left->m[2] * right->m[8] + left->m[6] * right->m[9] + left->m[10] * right->m[10] + left->m[14] * right->m[11]; result->m[11] = left->m[3] * right->m[8] + left->m[7] * right->m[9] + left->m[11] * right->m[10] + left->m[15] * right->m[11]; result->m[12] = left->m[0] * right->m[12] + left->m[4] * right->m[13] + left->m[8] * right->m[14] + left->m[12] * right->m[15]; result->m[13] = left->m[1] * right->m[12] + left->m[5] * right->m[13] + left->m[9] * right->m[14] + left->m[13] * right->m[15]; result->m[14] = left->m[2] * right->m[12] + left->m[6] * right->m[13] + left->m[10] * right->m[14] + left->m[14] * right->m[15]; result->m[15] = left->m[3] * right->m[12] + left->m[7] * right->m[13] + left->m[11] * right->m[14] + left->m[15] * right->m[15]; }
这就是利用矩阵, 把一个世界坐标系的坐标,转换到局部坐标系的方法。
那么。怎样把一个局部坐标系转换到世界坐标系呢?
这里须要得到局部坐标系相应矩阵的逆矩阵,这个矩阵包括了还原矩阵操作的变换。
然后,把逆矩阵当做左边的矩阵,去右乘局部坐标点, 我们就能够得到局部坐标变成世界坐标后的坐标。
static bool tryInvert(Matrix4* matrix, Out(Matrix4* result)) { float a0 = matrix->m[0] * matrix->m[5] - matrix->m[1] * matrix->m[4]; float a1 = matrix->m[0] * matrix->m[6] - matrix->m[2] * matrix->m[4]; float a2 = matrix->m[0] * matrix->m[7] - matrix->m[3] * matrix->m[4]; float a3 = matrix->m[1] * matrix->m[6] - matrix->m[2] * matrix->m[5]; float a4 = matrix->m[1] * matrix->m[7] - matrix->m[3] * matrix->m[5]; float a5 = matrix->m[2] * matrix->m[7] - matrix->m[3] * matrix->m[6]; float b0 = matrix->m[8] * matrix->m[13] - matrix->m[9] * matrix->m[12]; float b1 = matrix->m[8] * matrix->m[14] - matrix->m[10] * matrix->m[12]; float b2 = matrix->m[8] * matrix->m[15] - matrix->m[11] * matrix->m[12]; float b3 = matrix->m[9] * matrix->m[14] - matrix->m[10] * matrix->m[13]; float b4 = matrix->m[9] * matrix->m[15] - matrix->m[11] * matrix->m[13]; float b5 = matrix->m[10] * matrix->m[15] - matrix->m[11] * matrix->m[14]; // Calculate the determinant. float det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; // Close to zero, can‘t invert. if (fabs(det) < FLT_EPSILON) { return false; } float scalar = 1.0f / det; // Support the case where matrix == result result->m[0] = ( matrix->m[5] * b5 - matrix->m[6] * b4 + matrix->m[7] * b3) * scalar; result->m[1] = (-matrix->m[1] * b5 + matrix->m[2] * b4 - matrix->m[3] * b3) * scalar; result->m[2] = ( matrix->m[13] * a5 - matrix->m[14] * a4 + matrix->m[15] * a3) * scalar; result->m[3] = (-matrix->m[9] * a5 + matrix->m[10] * a4 - matrix->m[11] * a3) * scalar; result->m[4] = (-matrix->m[4] * b5 + matrix->m[6] * b2 - matrix->m[7] * b1) * scalar; result->m[5] = ( matrix->m[0] * b5 - matrix->m[2] * b2 + matrix->m[3] * b1) * scalar; result->m[6] = (-matrix->m[12] * a5 + matrix->m[14] * a2 - matrix->m[15] * a1) * scalar; result->m[7] = ( matrix->m[8] * a5 - matrix->m[10] * a2 + matrix->m[11] * a1) * scalar; result->m[8] = ( matrix->m[4] * b4 - matrix->m[5] * b2 + matrix->m[7] * b0) * scalar; result->m[9] = (-matrix->m[0] * b4 + matrix->m[1] * b2 - matrix->m[3] * b0) * scalar; result->m[10] = ( matrix->m[12] * a4 - matrix->m[13] * a2 + matrix->m[15] * a0) * scalar; result->m[11] = (-matrix->m[8] * a4 + matrix->m[9] * a2 - matrix->m[11] * a0) * scalar; result->m[12] = (-matrix->m[4] * b3 + matrix->m[5] * b1 - matrix->m[6] * b0) * scalar; result->m[13] = ( matrix->m[0] * b3 - matrix->m[1] * b1 + matrix->m[2] * b0) * scalar; result->m[14] = (-matrix->m[12] * a3 + matrix->m[13] * a1 - matrix->m[14] * a0) * scalar; result->m[15] = ( matrix->m[8] * a3 - matrix->m[9] * a1 + matrix->m[10] * a0) * scalar; return true; }是的有些矩阵是没有逆矩阵的,所以求逆矩阵的操作会失败。
世界坐标系的意义,就是坐标是相对于原点坐标系的。
局部坐标系的意义。就是坐标不是相对于原点坐标系。而是相对于某个详细的坐标系。
局部坐标系是能够通过上面的方法互相转换的。
那么怎样在局部坐标系之间互相转换呢?
我们无法把一个局部坐标系的坐标,一次就变化成还有一个局部坐标系上。
由于两个不同的局部坐标的坐标。都是相对于各自的坐标系。也就是參考系不同。
但。我们能够,把一个局部坐标系,转换到世界坐标系。以后再从世界坐标系转换到还有一个局部坐标系上。
坐标系转换的意义是什么?
假设我们可以恰当的选取坐标系。在进行坐标计算的时候,会简化非常多运算和思考的模型。
由于一个物体坐标的变化总是在父类坐标之内的,也就是相对于父类坐标系去变化。
这个父类坐标系。要么世界坐标系,要么就是某个详细的坐标系。
而我们这里讨论的坐标转换的模型是这种。
一个坐标终于呈如今屏幕上,我们假设改动了坐标的父坐标系,通过坐标系的转化。而保持这个坐标终于呈现的位置不变。
打一个例如
假设一个坐标(0, 0)在世界坐标系上,终于呈现出来的就是在(0, 0)点处。
我们如今把这个坐标,放到一个在(5, 5)处的坐标系内。这样这个坐标全部的数值都像相对于(5, 5)这个坐标系的。
那么,(0, 0)终于呈现的就是在(5, 5)处了,而不再原来的位置。
我们通过把这个坐标(0, 0)转换到(5, 5)的坐标系里,会得到新的坐标(-5, -5)这是相对于新坐标系的数值。
终于(-5, -5) 会呈如今(0, 0)的位置。
其实,在openGL绘制的时候。我们常常须要在各种不同的坐标系之间互相转换,可能是为了计算动画。可能是为了计算物理碰撞。
以上是关于利用矩阵进行坐标系转换的主要内容,如果未能解决你的问题,请参考以下文章