零基础学图形学(5)⼏何知识——矩阵
在我们介绍为什么矩阵有趣之前,我们先开始说渲染⼀副图⽚,⽽所有的3D图形,所以的相机都在原地静⽌是⼗分受限制的。必须的,矩阵在物体的移动, 灯光和相机,⽅⾯扮演着⼗分重要的⾓⾊,使⽤它们你可以创建你想要得到的图像。如果我们忽略矩阵的话,我们不能创建出⾮常激动⼈⼼的图像。如果你想要实现⾃⼰的渲染器,你不应该忽略它们。因此让我们现在马上就学习它们。
(1)矩阵介绍:它们使变换变得简单
其实矩阵⼀点都不复杂,很多⼈怕它们是因为它们不能完全理解矩阵,它们不知道矩阵代表着什么,以及它们是怎么样⼯作的。它们在图形显⽰上扮演着重要的⾓⾊,你可以经常看到3d应⽤中使⽤到它们。
在之前的⽂章中,我们提到通过线性操作移动或者旋转⼀个点是有可能的。⽐如,我们通过向点的坐标增加⼀个值来移动 它们。我们同时也展⽰了使⽤三⾓函数让矩阵旋转是有可能的。现在,简⽽⾔之(现在还不是定义矩阵是什么的时候),⼀个矩阵就是这些变换(放⼤,旋转,平移)的集合。点或者向量和这种结构相乘就会得到⼀个变换的点或者向量。结合这些变换就相当于将缩放,旋转,平移这些操作组合起来。我们可以创建⼀个矩阵,这个矩阵将⼀个点绕x轴旋转90度,沿着z轴放⼤两倍,和平移它到(-2, 3,1)这个位置。我们可以通过⼀系列的线性操作得到这个点,但是那也意味着需要写更多的代码:
Vec3f translate(Vec3f P, Vec3f translateValue) { ... }
Vec3f scale(Vec3f P, Vec3f scaleValue) { ... }
Vec3f rotate(Vec3f P, Vec3f axis, float angle) { ... }
...
Vec3f P = Vec3f(1, 1, 1);
Vec3f translateVal(-1, 2, 4);
Vec3f scaleVal(1, 1, 2);
Vec3f axis(1, 0, 0);
float angle = 90;
Vec3f Pt;
Pt = translate(P, translateVal): // translate P
Pt = scale(Pt, scaleVal); // then scale the result
Pt = rotateValue(Pt, axis, angle); // finally rotate the point
这⾥的代码看着不是很简介,如果我们使⽤向量的话,就可以简单地写成
Matrix4f M(...); // set the matrix for translation, rotation, scale
Vec3f P = Vec3f(1, 1, 1);
Vec3f Ptranformed = P * M; // do everything at once, translate, rotate, scale
得到类似的P的变换的效果,我们只需要简单地将这个点和⼀个矩阵M相乘。我们刚刚展⽰了在图形管道中矩阵的作⽤以及它的优势。在特殊的例⼦中,我们已经告诉你它们可以被使⽤基本的三种变换合成⼀个很简单的快速的⽅式变换。现在我们要教给你的就是它们是如何⼯作的。
(2)矩阵,它们是什么?
什么是矩阵?我们采⽤⼀个真正的句⼦例⼦,⽽不是通过抽象的数学定义回答你。⼀旦我们看了⼏个具体的例⼦之后然后再去抽象出数学的形式会更加容易。如果你已经阅读了⼏本CG的书籍,你可能已经看到了矩阵在很多地⽅提到,它们通常的出现形式是⼀个⼆维的数组。为了定义⼀个⼆维的数组,我们通常使⽤标注的形式m × n表⽰,m和n是两个数字表⽰矩阵的⼤⼩。就像你猜的那样,它们表⽰矩阵的⾏数和列数。⾏(row)是横着的数字,列是竖着的。这⾥有⼀个[3 × 5]的矩阵
我们会使⽤矩阵的下标表⽰⾥⾯的数字。通常都是⽤i, j表⽰⼀个矩阵中的某个数。矩阵本事使⽤⼤写的字母⽐如(M, A, B)表⽰。
Mij 表⽰矩阵的第i⾏第j列。
对于矩阵我们会做很多很多的简化。其中的⼀个就是CG中的矩阵通常都是正⽅形的,也就是说m和n相等。典型的,在CG中,我们会对
3×3或者4*4的矩阵更感兴趣。在接下来的章节中,我们会告诉你它们是什么以及如何使⽤它们。更普遍的,我们称这些矩阵为⽅形矩阵(square matrices)。现在,这是⼀个简化,因为在现实⽣活中m 和n可以取任意的值,没有必要相等。你可以创建⼀个3 × 1的矩阵,你可以创建其它类型的矩阵。它
们都是合法的,但是正如我们说,在CG中,我们更多地使⽤3*3 和4*4矩阵。
下⾯是我们⽤C++代码实现的4*4矩阵,记住我们使⽤的是模板来因为有些例⼦需要单精度的有些例⼦需要双精度的矩阵。
typedef unsigned int uint8_t;
template<typename T>
class Matrix44 {
public:
Matrix44() {}
const T* operator[] (uint8_t i) const {
return m[i];
数学数组的定义是什么}
T* operator[] (uint8_t i) {
return m[i];
}
// initialize the coefficients of the matrix with the coefficients of the identity matrix
T m[4][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
};
typedef Matrix44<float> Matrix44f;write.blog.csdn/postedit/78433076
下⾯这些操作在Matrix44类中:
const T* operator [] (uint8_t i) const { return m[i]; }
T* operator [] (uint8_t i) { return m[i]; }
有时称它们为获取操作,或者获取。它们⽤于获取矩阵中的值。通常地,你可以通过下⾯的⽅式获取⾥⾯的值
Matrix44f mat;
mat.m[0][3] = 1.f;
但是有这些获取操作,你可以通过
Matrix44f mat;
mat[0][3] = 1.f;
(3)矩阵乘法
矩阵相互之间可以相乘,这种操作是点或者向量矩阵变换的核⼼。矩阵相乘的结果是另外⼀个矩阵
M3 = M1 * M2;
如果你记得开始提到的内容的话,你会知道⼀个矩阵的定义就是应⽤在点和向量的线性变换的组合。它是怎么⼯作的现在我们还没有解释,但是我们将要解决它们。现在需要重点知道的就是矩阵的乘法,
它是⼀种将两个矩阵的操作效果结合称⼀个矩阵。也就是说,M1和M2上的在⼀个点或者向量的操作结果可以⽤⼀个M3来表⽰。想象⼀下,你需要将⼀个点⽤矩阵M1从A点平移到B点,然后⽤矩阵M2从B点平移到C点。如果将M1和M2相乘的话得到M3,M3会直接将A掉平移到C点。通过相乘的获得的矩阵和其它两个矩阵是没有什么不同的。这⾥需要记住的很重要的⼀点就是如果有有两个矩阵M4和M5代表着从A点移到D点,再从D点移动到C点,那么M4和M5的乘积也会等于M3(对于每个特殊的变换都有独⼀⽆⼆的矩阵)。
如果你只处理4*4的矩阵,有个不⼤重要的知识点,是关于矩阵的乘法的(过会你就会知道是怎么回事)。两个矩阵M1和M2只有在M1的列等于M2的⾏数的时候才能相乘。换句话说,如果两个矩阵可以写成m×p和p×n的形式,那么它们可以相乘,得到⼀个m×n的矩阵。两个矩阵p ×m和n × p就不能相乘因为m和n不相等。 下表$ × 2和2×3就可以相乘得到4 × 3的下标矩阵。如果两个4×4的矩阵相乘的话会
得到⼀个4×4的矩阵(这个规则不是很重要,因为我们经常会使⽤4*4d的矩阵进⾏计算,所以我们不关系两个矩阵是否能够相乘)。
让我们看看如果做两个矩阵的乘法,它会转变成为两个矩阵⾥⾯的数的数学运算。换句话说,我们对如何计算新的矩阵⾥⾯的数据感兴趣。只要你记得规则那就会很简单。之前我们说过矩阵中的数据被它们的⾏和列定义。我们使⽤i和j下标来表⽰⾏列。因此想⼀下我们想要得到M3的i和j处的数值。假设i = 1和 j = 2(记住下表0表⽰矩阵的第⼀⾏或者第⼀列),下表3表⽰矩阵的最后⼀⾏或者⼀列。C++中的数组是从下表0开始的。为了计算M3(1, 2),我们选择M1第⼆⾏总所有的参数和M2中第三列中所有的参数。这样我们就得到⼀系列的四个数字相乘后再相加,如下图所⽰:
我们可以⽤这种⽅式求M3中所有的参数:使⽤的是我们想要计算的⾏和列,使⽤那些下表去选择矩阵的数字进⾏计算,其中M1的选择是M1 (M1(i,0), M1(i,1), M1(i,2), M1(i,3)),M2的选择是M2 (M2(0,j),
M2(1,j), M2(2,j), M2(3,j).我们获取这些数之后然后把它们结合起来,使⽤上⾯的公式。将上⾯的所有的参数的下明暗进⾏相乘之后再相加就得到想要的结果:
让我们来看看C++语⾔怎么实现这个过程。让我们定义⼀个矩阵,它是4×4的浮点型数据。下⾯这个函数就是⽤来两个矩阵相乘的:
typedef unsigned int uint8_t;
template<typename T>
class Matrix44 {
public:
Matrix44() {}
const T* operator[] (uint8_t i) const {
return m[i];
}
T* operator[] (uint8_t i) {
return m[i];
}
// initialize the coefficients of the matrix with the coefficients of the identity matrix
T m[4][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
Matrix44 operator* (const Matrix44& rhs) const {
Matrix44 mult;
for (uint8_t i = 0; i < 4; ++i) {
for (uint8_t j = 0; j < 4; ++j) {
mult[i][j] = m[i][0] * rhs[0][j] +
m[i][1] * rhs[1][j] +
m[i][2] * rhs[2][j] +
m[i][3] * rhs[3][j];
}
}
return mult;
}
};
typedef Matrix44<float> Matrix44f;
你知道如何进⾏两个矩阵的乘积是不难的,你会发现M1 × M2和M2 × M1两个获取的结果不同。
矩阵的乘法不遵守交换律。
(4)总结
我们还没有介绍矩阵是怎么⼯作的,但是不要着急这些知识会在下⼀章中降到。从这章中你可以记得矩阵是⼀个⼆维的数组。矩阵的⼤⼩⽤m × n表⽰,这⾥的m代表矩阵的⾏数,n表⽰矩阵的列。两个矩阵只有在前⾯的矩阵的列数等于后⾯矩阵的⾏数才可以相乘。⽐如,你有两个矩阵m × p和p × n,这两个矩阵可以相乘。得到的矩阵是这两个矩阵变换的结合。如果M1将⼀个点从A移动到B,M2将⼀个点从B 移动到C,那么两个矩阵相乘获得的结果M3就是将⼀个点直接从A移动到C.最后,我们学习了如果计算矩阵的参数。记住矩阵是不遵守交换律的,顺序很重要,如果你的代码不⼯作,你也许可以检查⼀下矩阵乘法的先后顺序。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论