第八章:矩阵和线性变换
第一节:旋转
1.2D中的旋转
上一节中讲了矩阵的几何意义,矩阵的每一行可以看做该维度向量的分解坐标轴向量所对应的最终状态。接下来我们以2D单位向量来讨论2D向量的旋转问题。
上图中我们以2D向量[1 1]来演示旋转θ角后的矩阵表示。首先分解2D向量[1 1],然后[1 1]向量的旋转其实就是分解的p和q向量的旋转,旋转后得到的p'和q'向量就是旋转后的向量。我们可以根据三角函数求出旋转后的分解向量p'和q'。然后我们根据矩阵的几何定义,可以推理出2D向量的旋转矩阵。
上述矩阵就是我们的2D向量的旋转矩阵,我们可以代入向量[2 2]来求出旋转90°的向量。
2.3D中旋转的方向
在3D中,我们的旋转首先要确定方向,在左手坐标系中,我们以左手为标准,大拇指向要旋转的轴上,四指弯曲的方向为正方向。在右手坐标系中,以右手为标准。例如,在左手坐标系中我们要将以x轴为旋转轴旋转90°。我们首先伸出左手,大拇指指向x轴,则四指弯曲的方向就是正方向,旋转90°就是沿着四指弯曲的方向旋转90°。
3.3D中绕指定轴的旋转(左手坐标系)
根据上面的2D向量旋转矩阵的推导我们可以同理推导出3D旋转矩阵的实现
绕x轴的旋转矩阵
旋转示意图
我们仍然以标准的3D向量[1 1 1]来推导,其分解后得到的三个向量分别是[1 0 0],[0 1 0],[0 0 1],我们绕x轴旋转θ角度后,这三个向量的最终状态变为了[1 0 0],[0 cosθ sinθ],[0 -sinθ cosθ],所以绕x轴旋转的矩阵如下:
绕y轴的旋转矩阵
旋转示意图
我们仍以标准向量[1 1 1]来推导,将其分解后得到的三个向量分别为[1 0 0],[0 1 0],[0 0 1],我们绕y轴旋转θ角度后,这三个向量的最终状态变为了[cosθ 0 -sinθ],[0 1 0],[sinθ 0 cosθ],所以绕y轴旋转的矩阵如下:
绕z轴的旋转矩阵
旋转示意图
我们仍以标准向量[1 1 1]来推导,将其分解后得到的三个向量分别为[1 0 0],[0 1 0],[0 0 1],我们绕z轴旋转θ角度后,这三个向量的最终状态变为了[cosθ sinθ 0],[-sinθ cosθ 0],[0 0 1],所以绕z轴旋转的矩阵为:
4.3D中绕指定轴旋转的代码实现
头文件(Matrix3x3.h)
#pragma once #include <iostream> using namespace std; class Matrix3x3 { ...... public: /* 设置旋转矩阵 */ void setRotate(int axis, float theta); ...... };
cpp文件(Matrix3x3.cpp)
#include "Matrix3x3.h" ...... /* 旋转矩阵 */ void Matrix3x3::setRotate(int axis, float theta) { switch (axis) { case 1: m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = cos(theta); m23 = sin(theta); m31 = 0.0f; m32 = -sin(theta); m33 = cos(theta); break; case 2: m11 = cos(theta); m12 = 0.0f; m13 = -sin(theta); m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = sin(theta); m32 = 0.0f; m33 = cos(theta); break; case 3: m11 = cos(theta); m12 = sin(theta); m13 = 0.0f; m21 = -sin(theta); m22 = cos(theta); m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; default: cerr << "坐标轴输入错误" << endl; break; } } ......
测试向量的旋转矩阵
#include <iostream> #include "Vector3.h" #include "Matrix3x3.h" using namespace std; const float PI = 3.14159265358979323846264; int main() { // 测试旋转矩阵 Matrix3x3 m1; m1.setRotate(1, -PI/2.0f); Vector3 v1(0, 10, 0),v2; v2 = v1*m1; v2.printV(); return EXIT_SUCCESS; }
第二节:缩放
1.缩放矩阵的原理
我们已经知道矩阵的几何意义,所以说缩放其实就是各个坐标轴上的缩放,我们可以根据单位矩阵进行演变,单位矩阵对角线元素都为1,如果我们尝试其中一个元素为其他数值,则对应该轴的长度就会相应的改变,所以缩放矩阵如下:
2.缩放矩阵的代码实现
头文件(Matrix3x3.h)
#pragma once #include <iostream> using namespace std; class Vector3; class Matrix3x3 { ...... public: /* 设置缩放矩阵 */ void setScale(const Vector3& v); ...... };
cpp文件(Matrix3x3.cpp)
#include "Matrix3x3.h" #include "Vector3.h" ...... /* 缩放矩阵 */ void Matrix3x3::setScale(const Vector3& v) { m11 = v.x; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = v.y; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = v.z; } ......
3.缩放矩阵的测试
#include <iostream> #include "Vector3.h" #include "Matrix3x3.h" using namespace std; int main() { // 测试缩放矩阵 Matrix3x3 m2; m2.setScale(Vector3(0.5f,0.5f,0.5f)); Vector3 v3(10, 0, 0), v4; v4 = v3*m2; v4.printV(); return EXIT_SUCCESS; }
第三节:正交投影
1.正交投影矩阵的原理
投影就是意味着降维操作,二维进行降维就变成了一维,三维进行降维就变成了二维。其实用数据来表示就是对应的坐标轴的数值变为了0,例如下图的茶壶,进行正交投影,假设沿x轴投影,那么投影后该茶壶的所有坐标的x元素都变为了0。
根据单位矩阵,我们同样可以进行修改,来得出对应各个坐标轴的正交投影矩阵。因为只是其中一个轴为0,则只是修改对角线元素中的某个值为0,其他为1即可。
投影到yz平面的矩阵(x为0,其余坐标保持原来的值)
投影到xz平面的矩阵(y为0,其余坐标保持原来的值)
投影到xy平面的矩阵(z为0,其余坐标保持原来的值)
2.正交投影矩阵的代码实现
头文件(Matrix3x3.h)
#pragma once #include <iostream> using namespace std; class Matrix3x3 { ...... public: /* 正交投影矩阵 */ void setProject(int axis); ...... };
cpp文件(Matrix3x3.cpp)
#include "Matrix3x3.h" #include "Vector3.h" ...... /* 正交投影矩阵 */ void Matrix3x3::setProject(int axis) { switch (axis) { case 1: // x轴 m11 = 0.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; case 2: // y轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 0.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; case 3: // z轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 0.0f; break; default: break; } } ......
3.测试正交投影矩阵
#include <iostream> #include "Vector3.h" #include "Matrix3x3.h" using namespace std; int main() { // 测试正交投影矩阵 Matrix3x3 m3; m3.setProject(1); Vector3 v5(3, 4, 0), v6; v6 = v5*m3; v6.printV(); return EXIT_SUCCESS; }
第四节:镜像
1.镜像矩阵的原理
镜像也叫做反射,镜像矩阵也可以通过单位矩阵来进行修改,这里的对角线元素不再为1,如果为-1,则就是镜像矩阵,原理如下图所示,只不过是将坐标进行了取反而已。
2.镜像矩阵的代码实现
头文件(Matrix3x3.h)
#pragma once #include <iostream> using namespace std; class Vector3; class Matrix3x3 { ...... /* 镜像矩阵 */ void setMirror(int axis); ...... };
cpp文件(Matrix3x3.cpp)
#include "Matrix3x3.h" #include "Vector3.h" ...... /* 正交投影矩阵 */ void Matrix3x3::setProject(int axis) { switch (axis) { case 1: // x轴 m11 = 0.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; case 2: // y轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 0.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; case 3: // z轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 0.0f; break; default: break; } } /* 镜像矩阵 */ void Matrix3x3::setMirror(int axis) { switch (axis) { case 1: // x轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = -1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; case 2: // y轴 m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = -1.0f; break; case 3: // z轴 m11 = -1.0f; m12 = 0.0f; m13 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; break; default: break; } } ......
测试代码
#include <iostream> #include "Vector3.h" #include "Matrix3x3.h" using namespace std; int main() { // 测试镜像矩阵 Matrix3x3 m4; m4.setMirror(1); Vector3 v7(1, 1, 1), v8; v8 = v7 * m4; v8.printV(); return EXIT_SUCCESS; }