mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 12:45:29 +08:00
01-06, 07
This commit is contained in:
@@ -1,18 +1,24 @@
|
||||
本文作者JoeyDeVries,由[Django](http://bullteacher.com/8-transformations.html)翻译自[http://learnopengl.com](http://learnopengl.com/#!Getting-started/Transformations)
|
||||
|
||||
# 变换(Transformations)
|
||||
|
||||
我们现在已经知道了如何创建一个物体,为它们上色,使用纹理给它们一些细节表现,但是它们仍然还是不够有趣,因为它们都是静态的物体。我们可以尝试通过在每一帧中重新配置它们的缓冲,移动它们的顶点来使它们动起来,但这样就复杂了,而且会耗费处理能力。使用(多个)矩阵(matrix)对象可以更好的变换(transform)一个物体。这并不意味着我们会去讨论武术和数字虚拟世界(译注:Matrix是电影「黑客帝国」的英文名,电影中人类生活在数字虚拟世界,主角会武术)。
|
||||
// TODO 校对过程中有对原来的翻译大段大段重写,变动比较大(特别前半部分),可能还需要重新校对一遍
|
||||
|
||||
矩阵是非常强大的数学概念,开始看起来可怕,但是当你了解它们后,它们会非常有用。讨论矩阵之前,我们需要先了解一些数学,对于一些愿意多了解一些的读者我会附加一些资源给你们之后阅读。
|
||||
原文 | [Transformations](http://learnopengl.com/#!Getting-started/Transformations)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | Django
|
||||
校对 | Meow J
|
||||
|
||||
然而, 要完全理解变换,我们首先要在讨论矩阵之前了解一点向量(vector)。本章的目标是给你一个后面内容需要的基本的数学背景知识。如果这个主题很难,尝试尽量理解它们,当你需要它们的时候回过头来复习这些概念。
|
||||
尽管我们现在已经知道了如何创建一个物体、着色、加入纹理从而给它们一些细节的表现,但是它们仍然还是不够有趣,因为它们都还是静态的物体。我们可以尝试着在每一帧改变物体的顶点并且重设缓冲区从而使他们移动,但这太繁琐了,而且会消耗很多的处理时间。然而,我们现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。当然,这并不是说我们会去讨论武术和数字虚拟世界(译注:Matrix同样也是电影「黑客帝国」的英文名,电影中人类生活在数字虚拟世界,主角会武术)。
|
||||
|
||||
# 向量
|
||||
**矩阵**(Matrix)是一种非常有用的数学工具,尽管听起来可能有些吓人,不过一旦你理解了它们后,它们会非常有用。在讨论矩阵的过程中,我们需要使用到一些数学知识。对于一些愿意多了解这些知识的读者,我会附加一些资源给你们阅读。
|
||||
|
||||
向量最近本的定义是它是一个方向。向量有一个方向和大小(也叫做强度或长度)。你可以把向量想成一个藏宝图上的方向:“向左走10步,向北走3步,然后向右走5步”;这里的“左”是方向,“10步”是向量的长度。藏宝图上的方向包含3个向量。向量可以有任何维度,但是我们通常只是用2至4维。如果一个向量有2个维度,它表示一个平面的方向(想象2D图像),当它有3个维度的时候它可以表达一个3D世界的方向。
|
||||
为了深入了解变换,我们首先要在讨论矩阵之前了解一点向量(Vector)。这一节的目标是让你拥有将来需要的最基础的数学背景知识. 如果你发现这节十分困难,尽量尝试去理解它们,当你以后需要它们的时候回过头来复习这些概念。
|
||||
|
||||
下面你会看到3个向量,每个向量在2D图像中用一个箭头(x, y)表示。因为在2D中表示向量很容易,你可以想象2D向量有一个z坐标0,这样它就是一个3D向量了。由于向量表示的是方向,起始于何处并不会改变它的值。下图我们可以看到向量和是相等的,尽管他们的起始点不同:
|
||||
## 向量(Vector)
|
||||
|
||||
向量最最基本的定义就是一个方向。或者更正式的说,向量有一个方向(Direction)和大小(Magnitude,也叫做强度或长度)。你可以把向量想成一个藏宝图上的指示:“向左走10步,向北走3步,然后向右走5步”;这里的“左”是方向,“10步”是向量的长度。藏宝图上的方向包含3个向量。向量可以在任意**维度**(Dimension)上,但是我们通常只使用2至4维。如果一个向量有2个维度,它表示一个平面的方向(想象一下2D的图像),当它有3个维度的时候它可以表达一个3D世界的方向。
|
||||
|
||||
下面你会看到3个向量,每个向量在图像中都用一个箭头(x, y)表示。我们在2D图片中展示这些向量,因为这样子会更直观. 你仍然可以把这些2D向量当做z坐标为0的3D向量。由于向量表示的是方向,起始于何处**并不会**改变它的值。下图我们可以看到向量和是相等的,尽管他们的起始点不同:
|
||||
|
||||

|
||||
|
||||
@@ -20,27 +26,27 @@
|
||||
|
||||

|
||||
|
||||
由于向量是一个方向,因此此有时候很难把它们显示为一个位置。我们简单的把方向的起点设置为(0, 0, 0)然后指向某个方向,它的终点就是一个位置向量(position vector)了(你也可以把起点设置为其他的点,然后说:这个向量从这个点起始指向另一个点)。位置向量(3, 5)的起点是(0, 0)指向(3, 5)。我们可以这样描述2D和3D空间中的方向和位置。
|
||||
由于向量是一个方向,所以有些时候会很难形象地将它们用位置(Position)表示出来。我们通常设定这个方向的原点为(0,0,0),然后指向对应坐标的点,使其变为位置向量(Position Vector)来表示(你也可以把起点设置为其他的点,然后说:这个向量从这个点起始指向另一个点)。位置向量(3, 5)的在图像中起点是(0, 0),指向(3, 5)。我们可以使用向量在2D或3D空间中表示方向**与**位置.
|
||||
|
||||
和普通数字一样,我们也可以用向量进行多种操作(其中一些你已经看到了)。
|
||||
和普通数字一样,我们也可以用向量进行多种运算(其中一些你可能已经知道了)。
|
||||
|
||||
## 向量和标量运算
|
||||
### 向量和标量运算
|
||||
|
||||
标量只是一个数字(如果你想用向量表示,那么这个向量只能有一个元素)。当把一个向量加减乘除一个标量,我们可以简单的把向量的每个元素分别加减乘除标量。就像这样:
|
||||
**标量(Scalar)**只是一个数字(或者说是仅有一个分量的矢量)。当把一个向量加/减/乘/除一个标量,我们可以简单的把向量的每个分量分别进行该运算。对于加法来说会像这样:
|
||||
|
||||

|
||||
|
||||
+可以是+和-,·,÷,其中·是乘号。记住,-和÷的逆序操作不存在。
|
||||
其中的+可以是+,-,·或÷,其中·是乘号。注意-和÷运算时不能颠倒,颠倒的运算是没有定义的(标量-/÷矢量)
|
||||
|
||||
## 向量取反
|
||||
### 向量取反
|
||||
|
||||
向量的相反数是一个有着相反方向的向量。一个指向东北的向量的相反数是指向西南的向量。向量取相反数是在向量的每个元素前加上一个减号(你也可以用一个标量-1乘以这个向量来表示):
|
||||
对一个向量取反会将其方向逆转。一个指向东北的向量的相反数是指向西南的向量。我们在一个向量的每个分量前加负号从而实现取反(或者说用-1数乘该向量):
|
||||
|
||||

|
||||
|
||||
## 向量加减
|
||||
### 向量加减
|
||||
|
||||
两个向量相加被定义为元素逐个相加,一个向量的元素与另一个向量相同的元素相加,就像这样:
|
||||
向量的加法可以被定义为是分量的相加,即将一个向量中的每一个分量加上另一个向量的对应分量:
|
||||
|
||||

|
||||
|
||||
@@ -52,21 +58,21 @@
|
||||
|
||||

|
||||
|
||||
两个向量的差的结果是两个向量所指的位置的向量。在我们需要获得两点之间的差的向量这种情境这很有用。
|
||||
两个向量的相减会得到这两个向量指向位置的差. 这在我们想要获取两点的差会非常有用.
|
||||
|
||||

|
||||
|
||||
## 长度
|
||||
### 长度(Length)
|
||||
|
||||
我们使用勾股定理获取向量的长度/大小,你可能想起来数学课上学过了。一个向量的x和y元素可以看作是三角形的两条边,这个向量可以表示成一个三角形:
|
||||
我们使用勾股定理(Pythagoras theorem)来获取向量的长度(大小). 如果你把向量的x与y分量画出来,该向量会形成一个以x与y分量为边的三角形:
|
||||
|
||||

|
||||
|
||||
因为两条边(x和y)是已知的,我们希望知道斜边 ,我们可以通过勾股定理来计算出它:
|
||||
因为两条边(x和y)是已知的,而且我们希望知道斜边的长度,所以我们可以通过勾股定理来计算出它:
|
||||
|
||||

|
||||
|
||||
表示向量的长度,通过添加很容易能扩展到3D空间。
|
||||
表示向量的大小,我们也可以很容易加上把这个公式拓展到三维空间
|
||||
|
||||
例子中向量(4, 2)的长度等于:
|
||||
|
||||
@@ -74,71 +80,71 @@
|
||||
|
||||
结果是4.47。
|
||||
|
||||
有一个特殊类型向量叫做单位向量(unit vector)。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个元素除以向量的长度得到单位向量:
|
||||
有一个特殊类型向量叫做**单位向量(Unit Vector)**。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个分量除以向量的长度得到单位向量:
|
||||
|
||||

|
||||
|
||||
我们把这种方法叫做一个向量的标准化(normalizing)。单位向量头上有一个小屋顶,特别是在我们只关心方向不关系长度的时候,它变得很有用(如果我们改变向量的长度,它的方向并不会改变)。
|
||||
我们把这种方法叫做一个向量的**标准化(Normalizing)**。单位向量头上有一个小屋顶,特别是在我们只关心方向不关系长度的时候,它变得很有用(如果我们改变向量的长度,它的方向并不会改变)。
|
||||
|
||||
## 向量与向量相乘
|
||||
### 向量与向量相乘
|
||||
|
||||
两个向量相乘有点怪异。在向量中没有普通乘法的定义,这是因为它没有视觉意义,但是有两种特定情境,当需要乘法时我们可以从中选择:一个是点乘(dot product)写作另一个是叉乘(cross product)表示为。
|
||||
两个向量相乘是一种很奇怪的情况。普通的乘法在向量上是没有定义的,因为它在视觉上是没有意义的,但是有两种特定情境,当需要乘法时我们可以从中选择:一个是**点乘(Dot Product)**,记作,另一个是**叉乘(Cross Product)**,记作。
|
||||
|
||||
### 点乘(Dot Product)
|
||||
#### 点乘(Dot Product)
|
||||
|
||||
两个向量的点乘等于它们的模乘以两个向量之间夹角的余弦。听起来有点费解,先看一下公式:
|
||||
两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值。听起来有点费解,先看一下公式:
|
||||
|
||||

|
||||
|
||||
它们之间的夹角是。为什么这很有趣?想象如果和是单位向量,它们的长度等于1。公式会有效简化:
|
||||
它们之间的夹角我们记作。为什么这很有用?想象如果和都是单位向量,它们的长度等于1。公式会有效简化成:
|
||||
|
||||

|
||||
|
||||
现在点乘只和两个向量的角度有关。你也许记得当90度的余弦是0,0度的余弦是1。使用点乘可以很容易测试两个向量是否垂直或平行(垂直意味着两个向量互为直角)。你可能想要了解更多的关于正弦或余弦的知识,我推荐你看[可汗学院](https://www.khanacademy.org/math/trigonometry/basic-trigonometry/basic_trig_ratios/v/basic-trigonometry)的基础三角学视频。
|
||||
现在点乘**只**和两个向量的角度有关。你也许记得当90度的余弦是0,0度的余弦是1。使用点乘可以很容易测试两个向量是否正交(Orthogonal)或平行(正交意味着两个向量互为直角)。你可能想要了解更多的关于正弦或余弦的知识,我推荐你看[可汗学院](https://www.khanacademy.org/math/trigonometry/basic-trigonometry/basic_trig_ratios/v/basic-trigonometry)的基础三角学视频。
|
||||
|
||||
!!! Important
|
||||
|
||||
你可以计算两个非单位向量的角度,但是你必须除把结果以两个向量的长度,再除以。
|
||||
|
||||
所以,我们如何计算点乘?点乘是按元素逐个相乘,然后再把结果相加。两个单位向量点乘就像这样(你可以用两个长度为1的验证):
|
||||
所以,我们如何计算点乘?点乘是按分量逐个相乘,然后再把结果相加。两个单位向量点乘就像这样(你可以用两个长度为1的验证):
|
||||
|
||||

|
||||
|
||||
计算两个单位余弦的角度,我们使用反余弦 ,结果是143.1度。现在我们很快就计算出了两个向量的角度。当计算光照的时候点乘被证明很有用。
|
||||
计算两个单位余弦的角度,我们使用反余弦 ,结果是143.1度。现在我们很快就计算出了两个向量的角度。点乘在计算光照的时候会很有用。
|
||||
|
||||
### 叉乘(Cross Product)
|
||||
#### 叉乘(Cross Product)
|
||||
|
||||
叉乘只在3D空间才被定义,它需要两个不平行向量作为输入,生成垂直于两个向量的第三个向量。如果输入的两个向量也垂直,那么叉乘的结果返回3个垂直向量。接下来的教程中,这很有用。下面的图片展示了3D空间中叉乘的样子:
|
||||
叉乘只在3D空间有定义,它需要两个不平行向量作为输入,生成正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的,那么叉乘的结果将会返回3个互相正交的向量。接下来的教程中,这很有用。下面的图片展示了3D空间中叉乘的样子:
|
||||
|
||||

|
||||
|
||||
不同于其他操作,如果没有钻研过线性代数,叉乘很饭直觉,所以最好记住公式,就没问题(记不住也没问题)。下面你会看到两个垂直向量A和B叉乘结果:
|
||||
不同于其他运算,如果你没有钻研过线性代数,会觉得叉乘很反直觉,所以最好记住公式,就没问题(记不住也没问题)。下面你会看到两个正交向量A和B叉乘结果:
|
||||
|
||||

|
||||
|
||||
就像你所看到的,看起来毫无头绪。可如果你这么做了,你会得到第三个向量,它垂直于你的输入向量。
|
||||
就像你所看到的,看起来毫无头绪。可如果你这么做了,你会得到第三个向量,它正交于你的输入向量。
|
||||
|
||||
# 矩阵
|
||||
## 矩阵(Matrix)
|
||||
|
||||
现在我们已经讨论了向量的全部内容,是时候看看矩阵了!矩阵简单说是一个矩形的数字、符号或表达式阵列。矩阵中每个单独内容叫做矩阵的元素(element)。下面是一个2×3矩阵的例子:
|
||||
现在我们已经讨论了向量的全部内容,是时候看看矩阵了!矩阵简单说是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的**元素(Element)**。下面是一个2×3矩阵的例子:
|
||||
|
||||

|
||||
|
||||
矩阵可以通过(i, j)进行索引,i是行,j是列,这就是上面的矩阵叫做2×3矩阵的原因(3列、2行,也叫做矩阵的维度)。与你在索引2D图像(x, y)相反,获取4的索引是(2, 1)(第二行,第一列)(译注:如果是图像索引应该是(1, 2),先算列,再算行)。
|
||||
矩阵可以通过(i, j)进行索引,i是行,j是列,这就是上面的矩阵叫做2×3矩阵的原因(3列2行,也叫做矩阵的**维度(Dimension)**)。这与你在索引2D图像时的(x, y)相反,获取4的索引是(2, 1)(第二行,第一列)(译注:如果是图像索引应该是(1, 2),先算列,再算行)。
|
||||
|
||||
关于矩阵基本也就是这些了,它就是矩形数学表达式阵列。矩阵也有非常漂亮的数学属性,就跟向量一样。矩阵有几个操作,叫做:矩阵加法、减法和乘法。
|
||||
关于矩阵基本也就是这些了,它就是矩形数学表达式阵列。矩阵也有非常漂亮的数学属性,就跟向量一样。矩阵有几个运算,叫做:矩阵加法、减法和乘法。
|
||||
|
||||
## 矩阵加减
|
||||
### 矩阵的加减
|
||||
|
||||
矩阵与标量的加减如下所示:
|
||||
|
||||

|
||||
|
||||
标量值要加到矩阵的每个独立元素上。同样的方式也能应用到矩阵与标量的减法上:
|
||||
标量值要加到矩阵的每一个元素上。矩阵与标量的减法也是同样的:
|
||||
|
||||

|
||||
|
||||
矩阵之间的加减可以用元素相加减。和普通数字适用同样的法则,但是只能在矩阵间的相同索引的元素上进行。这意味着加法和减法只能在同样维度的矩阵之间进行。一个3×2矩阵和一个2×3矩阵(或一个3×3矩阵与4×4矩阵)是不能进行加减的。我们看看两个2×2矩阵是怎样加减的:
|
||||
矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,所以总体的规则和与标量运算是差不多的,只不过在相同索引下的元素才能进行运算。这也就是说加法和减法只在同维度的矩阵中是有定义的。一个3×2矩阵和一个2×3矩阵(或一个3×3矩阵与4×4矩阵)是不能进行加减的。我们看看两个2×2矩阵是怎样加减的:
|
||||
|
||||

|
||||
|
||||
@@ -146,120 +152,122 @@
|
||||
|
||||

|
||||
|
||||
## 矩阵与标量相乘
|
||||
### 矩阵与标量相乘
|
||||
|
||||
和加减一样,矩阵与标量之间的乘法也是矩阵的元素分别乘以标量。下面的例子描述了乘法:
|
||||
和矩阵与标量的加减一样,矩阵与标量之间的乘法也是矩阵的每一个元素分别乘以该标量。下面的例子展示了乘法的过程:
|
||||
|
||||

|
||||
|
||||
现在我们也能明白为什么一个单独数字叫做标量(Scalar)了。简单来说,标量就是用它的值缩放(Scale)矩阵的所有元素(译注:注意Scalar是由Scale + -ar演变过来的)。前面的例子里,所有的元素都被放大2。
|
||||
现在我们也就能明白为什么一个单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值缩放(Scale)矩阵的所有元素(译注:注意Scalar是由Scale + -ar演变过来的)。前面的例子里,所有的元素都被放大2。
|
||||
|
||||
到目前为止都还好,我们的例子都不复杂。不过矩阵与矩阵的乘法就不一样了。
|
||||
|
||||
## 矩阵与矩阵相乘
|
||||
### 矩阵与矩阵相乘
|
||||
|
||||
矩阵之间的乘法不见得多难,但的确不太让人适应。矩阵乘法基本上意味着遵照预定好的法则进行相乘。有一些限制存在:
|
||||
矩阵之间的乘法不见得有多复杂,但的确很难让人适应。矩阵乘法基本上意味着遵照规定好的法则进行相乘。当然,相乘还有一些限制:
|
||||
|
||||
1. 只有当左侧的矩阵列数等于右侧矩阵的行数,两个矩阵才能相乘。
|
||||
2. 矩阵间相乘不能交换,。
|
||||
1. 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
|
||||
2. 矩阵相乘不遵守**交换律(Commutative)**,。
|
||||
|
||||
我们先看一个两个2×2矩阵相乘的例子:
|
||||
|
||||

|
||||
|
||||
现在你可能正尝试看出到底发生了什么?矩阵乘法结合了普通的乘法和用左侧矩阵行加上右侧矩阵的列。我们可以看下面的图片:
|
||||
现在你可能会在想了:我勒个去,刚刚到底发生了什么? 矩阵的乘法是一系列乘法和加法组合的结果,它使用到了左侧矩阵的行和右侧矩阵的列。我们可以看下面的图片:
|
||||
|
||||

|
||||
|
||||
我们先把左侧矩阵的行和右侧矩阵的列拿出来。这些我们挑出来行和列决定着作为结果的2×2矩阵的输出值。如果我们拿出来的是左矩阵的第一行,最终的值就会出现在作为结果的矩阵的第一行,如果我们拿出来的是右矩阵的第一列,最终值会出现在作为结果的矩阵的第一列。这正是红框框住的情况。计算结果矩阵右下的值,我们要用第一个矩阵的第二行和第二个矩阵的第二列(译注:简单来说就是结果矩阵的元素的行取决于第一个矩阵,列取决于第二个矩阵)。
|
||||
我们先把左侧矩阵的行和右侧矩阵的列拿出来。这些我们挑出来行和列决定着作为结果的2×2矩阵的输出值。如果我们拿出来的是左矩阵的第一行,最终的值就会出现在作为结果的矩阵的第一行,如果我们拿出来的是右矩阵的第一列,最终值会出现在作为结果的矩阵的第一列。这正是红框里的情况。如果想计算结果矩阵右下角的值,我们要用第一个矩阵的第二行和第二个矩阵的第二列(译注:简单来说就是结果矩阵的元素的行取决于第一个矩阵,列取决于第二个矩阵)。
|
||||
|
||||
计算结果值的方式是先计算行和列的第一个元素之间的积,然后是第二个,第三个,第四个等等。然后把所有的乘积相加。现在就能解释为什么左矩阵的列数必须和右矩阵的行数想等了,否则就不能进行操作了!
|
||||
计算一项的结果值的方式是先计算左侧矩阵对应行和右侧矩阵对应列的第一个元素之积,然后是第二个,第三个,第四个等等,然后把所有的乘积相加,这就是结果了。现在我们就能解释为什么左侧矩阵的列数必须和右侧矩阵的行数相等了,如果不相等这一步的操作我们就无法完成了!
|
||||
|
||||
结果的矩阵的维度是(n, m),n等于左矩阵的行数,m等于右矩阵的列数。
|
||||
结果的矩阵的维度是(n, m),n等于左侧矩阵的行数,m等于右侧矩阵的列数。
|
||||
|
||||
如果你在脑子里想象出乘法有困难别担心。用笔写下来,如果遇到困难回头看这页的内容。随着时间流逝,矩阵乘法对你来说会成为自然的事。
|
||||
如果你在脑子里想象出乘法有困难别担心。用笔写下来,如果遇到困难回头看这页的内容。随着时间流逝,矩阵乘法对你来说会变成很自然的事。
|
||||
|
||||
我们用一个更大的例子来结束矩阵与矩阵乘法的讨论。尝试使用颜色来视觉化公式。作为一个有用的练习你可以自己回答这个乘法问题然后对比结果矩阵(如果你用笔计算,你很快就能掌握它们)。
|
||||
我们用一个更大的例子来结束矩阵与矩阵乘法的讨论。尝试使用颜色来让这个公式更容易理解。作为一个有用的练习,你可以自己回答这个乘法问题然后对比你的结果和图中的这个(如果你用笔计算,你很快就能掌握它们)。
|
||||
|
||||

|
||||
|
||||
就像你所看到的那样,矩阵与矩阵相乘复杂而容易犯错(这就是我们通常让计算机做这件事的原因),而且当矩阵变大以后很快就会出现问题。如果你仍然希望了解更多,对矩阵的数学属性感到好奇,我强烈推荐你看看[可汗学院](https://www.khanacademy.org/math/algebra2/algebra-matrices)的矩阵内容视频。
|
||||
就像你所看到的那样,矩阵与矩阵相乘复杂而容易犯错(这就是我们通常让计算机做这件事的原因),而且当矩阵变大以后很快就会出现问题。如果你仍然希望了解更多,对矩阵的数学属性感到好奇,我强烈推荐你看看[可汗学院](https://www.khanacademy.org/math/algebra2/algebra-matrices)的矩阵内容视频。
|
||||
|
||||
不管怎样,反正现在我们知道如何进行矩阵相乘了,我们可以开始了解好东西了。
|
||||
|
||||
# 矩阵与向量相乘
|
||||
## 矩阵与向量相乘
|
||||
|
||||
到目前,通过这些教程我们已经相当了解向量了。我们用向量来表示位置、颜色和纹理坐标。让我们进到兔子洞更深处:向量基本上就是一个N×1矩阵,N是向量元素的个数(也叫N维向量)。如果你仔细思考这个问题,会很有意思。向量和矩阵一样都是一个数字序列,但是它只有1列。所以,这个新信息能如何帮助我们?如果我们有一个M×N矩阵,我们可以用这个矩阵乘以我们的N×1向量,因为我们的矩阵的列数等于向量的行数,所以它们就能相乘。
|
||||
到目前,通过这些教程我们已经相当了解向量了。我们用向量来表示位置、颜色和纹理坐标。让我们进到兔子洞更深处:向量基本上就是一个N×1矩阵,N是向量分量的个数(也叫N维向量)。如果你仔细思考这个问题,会很有意思。向量和矩阵一样都是一个数字序列,但是它只有1列。所以,这个新信息能如何帮助我们?如果我们有一个M×N矩阵,我们可以用这个矩阵乘以我们的N×1向量,因为我们的矩阵的列数等于向量的行数,所以它们就能相乘。
|
||||
|
||||
但是为什么我们关心矩阵是否能够乘以一个向量?有很多有意思的2D/3D变换本质上都是矩阵,而矩阵与我们的向量相乘会变换我们的向量。假如你仍然有些困惑,我们看一些例子,你很快就能明白了。
|
||||
|
||||
## 单位矩阵
|
||||
### 单位矩阵(Identity Matrix)
|
||||
|
||||
因为一些缘故,在OpenGL中,我们通常使用4 × 4变换矩阵,每一个元素都是4元素向量。我们能想到的最简单的变换矩阵是单位矩阵。单位矩阵是一个除了对角线以外都是0的N × N矩阵。就像你看到的,这个变换矩阵使一个向量完全不变:
|
||||
在OpenGL中,因为有一些原因我们通常使用4×4的变换矩阵,而其中最重要的原因就是因为每一个向量都有4个分量的。我们能想到的最简单的变换矩阵就是**单位矩阵(Identity Matrix)**。单位矩阵是一个除了对角线以外都是0的N × N矩阵。就像你看到的,这个变换矩阵使一个向量完全不变:
|
||||
|
||||

|
||||
|
||||
向量看起来完全没动。从乘法法则来看很明显:第一个结果元素是矩阵的第一行的每个对立元素乘以向量的每一个元素。因为每行的元素除了第一个都是0,可得: ,这对向量的其他3个元素同样适用。
|
||||
向量看起来完全没动。从乘法法则来看很明显:第一个结果分量是矩阵的第一行的每个对应分量乘以向量的每一个分量。因为每行的分量除了第一个都是0,可得: ,这对向量的其他3个分量同样适用。
|
||||
|
||||
!!! Important
|
||||
|
||||
你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这就是一个对证明定理、解线性方程非常有用的矩阵。
|
||||
|
||||
## 缩放
|
||||
### 缩放(Scaling)
|
||||
|
||||
当我们对一个向量进行缩放的时候就是对向量的长度进行缩放,而它的方向保持不变。如果我们进行2或3维操作,那么我们可以定义一个2或3个的缩放向量,每个缩放一个轴(x、y或z)。
|
||||
当我们对一个向量进行缩放的时候就是对向量的长度进行缩放,而它的方向保持不变。如果我们进行2或3维操作,那么我们可以分别定义一个有2或3个缩放变量的向量,每个变量缩放一个轴(x、y或z)。
|
||||
|
||||
我们可以尝试去缩放向量。我们可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;我们可以沿着y轴把向量的高度缩放为原来的两倍。我们看看把向量缩放(0.5, 2)所获得的是什么样的:
|
||||
我们可以尝试去缩放向量。我们可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;我们可以沿着y轴把向量的高度缩放为原来的两倍。我们看看把向量缩放(0.5, 2)所获得的是什么样的:
|
||||
|
||||

|
||||
|
||||
记住,OpenGL通常是在3D空间操作的,对于2D的情况我们可以把z轴缩放1这样就无害了。我们刚刚的缩放操作是不均匀(non-uniform)缩放,因为每个轴的缩放因子都不一样。如果每个轴的缩放都一样那么就叫均匀(uniform)缩放。
|
||||
记住,OpenGL通常是在3D空间操作的,对于2D的情况我们可以把z轴缩放1这样z轴的值就不变了。我们刚刚的缩放操作是**不均匀(Non-uniform)**缩放,因为每个轴的缩放因子(Scaling Factor)都不一样。如果每个轴的缩放都一样那么就叫**均匀缩放(Uniform Scale)**。
|
||||
|
||||
我们下面设置一个变换矩阵来为我们提供缩放功能。我们从单位矩阵了解到,每个对角线元素曾以对应的向量元素。如果我们把1变为3会怎样?这种情况,我们就把向量的每个元素乘以3了,这事实上就把向量缩放3。如果我们把缩放变量表示为我们可以为任意向量(x, y, z)定义一个缩放矩阵:
|
||||
我们下面设置一个变换矩阵来为我们提供缩放功能。我们从单位矩阵了解到,每个对角线元素乘以对应的向量分量。如果我们把1变为3会怎样?这种情况,我们就把向量的每个分量乘以3了,这事实上就把向量缩放3。如果我们把缩放变量表示为我们可以为任意向量(x, y, z)定义一个缩放矩阵:
|
||||
|
||||

|
||||
|
||||
注意,第四个缩放的向量仍然是1,因为不会缩放3D空间中的w元素。w元素另有其他用途,后面我们会看到。
|
||||
注意,第四个缩放的向量仍然是1,因为不会缩放3D空间中的w分量。w分量另有其他用途,在后面我们会看到。
|
||||
|
||||
## 平移(Translation)
|
||||
### 平移(Translation)
|
||||
|
||||
平移是把一个新向量放在原向量的上面来返回一个具有不用位置的新向量,这样就基于平移向量移动了向量。我们已经讨论了向量加法,所以这不是什么新东西。
|
||||
如同缩放矩阵,有几个4×4矩阵的位置,用来执行特定的操作,对于平移它们是第四列前三个值。如果我们把缩放向量表示为我们就能把平移矩阵定义为:
|
||||
平移是在原来向量的基础上加上另一个的向量从而获得一个在不同位置的新向量的过程,这样就基于平移向量**移动(Move)**了向量。我们已经讨论了向量加法,所以你应该不会陌生。
|
||||
|
||||
和缩放矩阵一样,在4×4矩阵上有几个特别的位置用来执行特定的操作,对于平移来说它们是第四列最上面的3个值。如果我们把缩放向量表示为我们就能把平移矩阵定义为:
|
||||
|
||||

|
||||
|
||||
因为所有的平移值都要乘以向量的w列,然后加到向量的起点值上(想想矩阵乘法法则)。用3×3矩阵是不行的。
|
||||
这样是能工作的,因为所有的平移值都要乘以向量的w列,所以平移值会加到向量的原始坐标上(想想矩阵乘法法则)。而如果你用3x3矩阵我们的平移值就没地方放也没地方乘了,所以是不行的。
|
||||
|
||||
!!! Important
|
||||
|
||||
**齐次坐标(Homogeneous coordinates)**
|
||||
**齐次坐标(Homogeneous coordinates)**
|
||||
|
||||
向量的w元素也叫齐次坐标。从齐次坐标得到3D坐标,我们可以把x、y和z坐标除以w坐标。我们通常不会注意这个问题,因为w元素通常是1.0。使用齐次坐标有几点好处:它允许我们在3D向量上进行平移(如果没有w元素我们是不能尽心平移向量的),下一章我们会用w值创建3D图像。
|
||||
如果一个向量的齐次坐标是0,这个坐标就是方向向量(direction vector),因为w坐标是0,这个向量就不能平移。
|
||||
向量的w分量也叫齐次坐标。从齐次坐标得到3D坐标,我们可以把x、y和z坐标除以w坐标。我们通常不会注意这个问题,因为w分量通常是1.0。使用齐次坐标有几点好处:它允许我们在3D向量上进行平移(如果没有w分量我们是不能平移向量的),下一章我们会用w值创建3D图像。
|
||||
如果一个向量的齐次坐标是0,这个坐标就是方向向量(Direction Vector),因为w坐标是0,这个向量就不能平移(译注:这也就是我们说的不能平移一个方向)。
|
||||
|
||||
有了平移矩阵我们就可以在3个方向(x、y、z)上移动物体,它是我们的变换工具箱中非常有用的一个变换矩阵。
|
||||
有了平移矩阵我们就可以在3个方向(x、y、z)上移动物体,它是我们的变换工具箱中非常有用的一个变换矩阵。
|
||||
|
||||
## 旋转
|
||||
### 旋转(Rotation)
|
||||
|
||||
上面几个的变换内容相对容易理解,在2D或3D空间中也容易表示出来,但旋转稍复杂些。如果你想知道矩阵是如何构造出来的,我推荐你去看可汗学院线性代数视频。
|
||||
上面几个的变换内容相对容易理解,在2D或3D空间中也容易表示出来,但旋转稍复杂些。如果你想知道旋转矩阵是如何构造出来的,我推荐你去看可汗学院[线性代数](https://www.khanacademy.org/math/linear-algebra/matrix_transformations)视频。
|
||||
|
||||
首先我们来定义一个向量的旋转到底是什么。2D或3D空间中点的旋转用角来表示。角可以是角度或弧度,周角是360度或2PI弧度。我个人更喜欢用角度,因为它们看起来更直观。
|
||||
首先我们来定义一个向量的旋转到底是什么。2D或3D空间中点的旋转用**角(Angle)**来表示。角可以是角度制或弧度制的,周角是360度或2 [PI](https://en.wikipedia.org/wiki/Pi)弧度。我个人更喜欢用角度,因为它们看起来更直观。
|
||||
|
||||
!!! Important
|
||||
|
||||
大多数旋转函数需要用弧度制的角,但是角度制的角也可以很容易地转化为弧度制:
|
||||
弧度转角度:角度 = 弧度 * (180.0f / PI)
|
||||
角度转弧度:弧度 = 角度 * (PI / 180.0f)
|
||||
弧度转角度:角度 = 弧度 * (180.0f / PI)
|
||||
角度转弧度:弧度 = 角度 * (PI / 180.0f)
|
||||
PI约等于3.14159265359。
|
||||
|
||||
平角会向右旋转向右360/2 = 180度,向右旋转1/5表示向右360/5 = 72度。这表明2D空间的向量是向右旋转72度得到的:
|
||||
转半圈会向右旋转360/2 = 180度,向右旋转1/5圈表示向右旋转360/5 = 72度。这表明2D空间的向量是由向右旋转72度得到的:
|
||||
|
||||

|
||||
|
||||
在3D空间中旋转需要一个角和一个旋转轴。物体会沿着给定的旋转轴旋转特定角度。你可以尝试慢慢向下看,头部向下旋转一定角度,这就会沿着一个旋转轴旋转。2D向量在3D空间中旋转,我们把旋转轴设为z轴(尝试想象)。
|
||||
在3D空间中旋转需要一个角**和**一个旋转轴(Rotation Axis)。物体会沿着给定的旋转轴旋转特定角度。如果你想要更形象化的描述,可以试试向下看着一个特定的旋转轴,同时将你的头部旋转一定角度。比如2D向量在3D空间中旋转时,我们把旋转轴设为z轴(尝试想象这种情况)。
|
||||
|
||||
使用三角学就能把一个向量变换为一个经过旋转给定角度的新向量。这通常是通过把正弦和余弦函数结合得到的(一般简称sin和cos)。讨论如何生成变换矩阵超出了这个教程的范围。
|
||||
使用三角学就能把一个向量变换为一个经过旋转特定角度的新向量。这通常是使用一系列正弦和余弦各种巧妙的组合得到的(一般简称sin和cos)。当然,讨论如何生成变换矩阵超出了这个教程的范围。
|
||||
|
||||
旋转矩阵在3D空间中每个轴都有不同定义,这个角度表示为。
|
||||
旋转矩阵在3D空间中每个单位轴都有不同定义,这个角度表示为:
|
||||
|
||||
沿x轴旋转:
|
||||
|
||||
@@ -273,37 +281,37 @@
|
||||
|
||||

|
||||
|
||||
利用旋转矩阵我们可以把我们的位置矩阵(position vectors)沿一个或多个轴进行旋转。也可以把多个矩阵结合起来,比如第一个沿X轴旋转第二个沿Y轴旋转。然而,这会很快导致一个问题——Gimbal lock(译注:万向节死锁,这个视频描述了这个问题)。我们不会讨论它的细节,但是更好的解决方案是沿着任意轴比如(0.662, 0.2, 0.7222)旋转(注意,它是个单位向量上),而不再使用旋转矩阵相结合。这样一个(难看)的矩阵是存在的,下面代表任意旋转轴:
|
||||
利用旋转矩阵我们可以把我们的位置向量(Position Vectors)沿一个或多个轴进行旋转。也可以把多个矩阵结合起来,比如先沿着X轴旋转再沿着Y轴旋转。但是这会很快导致一个问题——**万向节死锁(Gimbal Lock,可以看看[这个视频](https://www.youtube.com/watch?v=zc8b2Jo7mno)来了解)**。我们不会讨论它的细节,但是一个更好的解决方案是沿着任意轴比如(0.662, 0.2, 0.7222)(注意,这是个单位向量)旋转,而不是使用一系列旋转矩阵的组合。这样一个(超级麻烦)的矩阵是存在的,下面代表任意旋转轴:
|
||||
|
||||

|
||||
|
||||
在数学上讨论如何生成这样的矩阵超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题(尽管会极大地避免)。避免万向节死锁的真正解决方案是使用四元数(quaternions),它不仅安全,而且计算更友好。有关四元数会在后面的教程中讨论。
|
||||
在数学上讨论如何生成这样的矩阵仍然超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题(尽管会极大地避免)。避免万向节死锁的真正解决方案是使用四元数(Quaternion),它不仅安全,而且计算更加友好。有关四元数会在后面的教程中讨论。
|
||||
|
||||
## 矩阵的组合
|
||||
### 矩阵的组合
|
||||
|
||||
感谢矩阵与矩阵乘法,使用矩阵变换的真正力量在于,我们可以把多个变换结合到一个矩阵中。让我们看看我们是否能生成一个多个变换相结合而成的变换矩阵。我们有一个顶点(x, y, z),我们希望用2对它进行缩放,然后平移(1, 2, 3)。为了必不可少的步骤,我们需要一个平移和缩放矩阵。结果的变换矩阵看起来像这样:
|
||||
使用矩阵变换的真正力量在于,根据矩阵之前的乘法,我们可以把多个变换组合到一个矩阵中。让我们看看我们是否能生成一个多个变换相结合而成的变换矩阵。我们有一个顶点(x, y, z),我们希望将其缩放2倍,然后用位移(1, 2, 3)来平移它。我们需要一个平移和缩放矩阵来完成这些变换。结果的变换矩阵看起来像这样:
|
||||
|
||||

|
||||
|
||||
注意,当矩阵相乘时我们先写平移,然后写缩放变换。矩阵乘法是不可交换的,这意味着它们的顺序很重要。当矩阵相乘,最右边的矩阵是第一个乘以向量的,所以你应该从右向左读这个乘法。建议当在结合矩阵时,先做缩放操作,然后旋转,最后平移,否则它们会(消极地)影响其他变换。比如,如果你先平移然后缩放,平移向量也会缩放(译注:比如向某方向移动2米,2米也许会被缩放成1米)!
|
||||
注意,当矩阵相乘时我们先写平移再写缩放变换的。矩阵乘法是不可交换的,这意味着它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个乘以向量的,所以你应该从右向左读这个乘法。我们建议您在组合矩阵时,先进行缩放操作,然后是旋转,最后才是平移,否则它们会(消极地)互相影响。比如,如果你先平移然后缩放,平移的向量也会同样被缩放(译注:比如向某方向移动2米,2米也许会被缩放成1米)!
|
||||
|
||||
我们的顶点运行最终的变换矩阵,最为结果的顶点是:
|
||||
将我们的矢量左乘最终的变换矩阵会得到以下结果:
|
||||
|
||||

|
||||
|
||||
不错!向量先缩放2,然后平移(1, 2, 3)。
|
||||
不错!向量先缩放2倍,然后平移了(1, 2, 3)个单位。
|
||||
|
||||
# 实践
|
||||
## 实践
|
||||
|
||||
现在我们已经解释了所有变换背后的理论,是时候白这些知识利用起来了。OpenGL没有任何内建的矩阵和向量形式,所以我们必须自己定义数学类和方法。在这个教程中我们更愿意使用已抽象出来的、做好了的数学库。幸运的是有个使用简单的专门为OpenGL量身定做的数学库,它就是GLM。
|
||||
现在我们已经解释了所有变换背后的理论,是时候将这些知识利用起来了。OpenGL没有任何自带的矩阵和向量形式,所以我们必须自己定义数学类和方法。在这个教程中我们更愿意抽象所有的数学细节,使用已经做好了的数学库。幸运的是有个使用简单的专门为OpenGL量身定做的数学库,那就是GLM。
|
||||
|
||||
## GLM
|
||||
### GLM
|
||||
|
||||
GLM代表OpenGL Mathematics,它是一个只有头文件的库,这意味着我们只需包含合适的头文件就行了;不用链接和编译。GLM可以从他们的[网站](http://glm.g-truc.net/0.9.5/index.html)上下载。把头文件的根目录复制到你的`includes`文件夹,让我们开始摇滚吧。
|
||||
GLM是Open**GL** **M**athematics的缩写,它是一个只有头文件的库,也就是说我们只需包含合适的头文件就行了;不用链接和编译。GLM可以从他们的[网站](http://glm.g-truc.net/0.9.5/index.html)上下载。把头文件的根目录复制到你的`includes`文件夹,然后你就可以使用这个库了。
|
||||
|
||||

|
||||
|
||||
GLM的大多数功能都可以从3个头文件中找到,这里我们会包含:
|
||||
我们需要的GLM的大多数功能都可以从下面这3个头文件中找到:
|
||||
|
||||
```c++
|
||||
#include <glm/glm.hpp>
|
||||
@@ -311,7 +319,7 @@ GLM的大多数功能都可以从3个头文件中找到,这里我们会包含
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
```
|
||||
|
||||
我们来看看是否可以利用变换知识把一个向量(1, 0, 0)平移(1, 1, 0)(注意,我们把它定义成一个齐次坐标为1.0的。
|
||||
我们来看看是否可以利用我们刚学的变换知识把一个向量(1, 0, 0)平移(1, 1, 0)个单位(注意,我们把它定义为一个glm::vec4类型的值,其中齐次坐标我们设定为1.0):
|
||||
|
||||
```c++
|
||||
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
@@ -321,11 +329,11 @@ vec = trans * vec;
|
||||
std::cout << vec.x << vec.y << vec.z << std::endl;
|
||||
```
|
||||
|
||||
我们先用GLM的内建vec类定义一个叫做vec的向量。下一步使我们定义一个默认mat4的4×4单位矩阵。接下来把单位矩阵传递给glm::translate函数创建一个变换矩阵,用来与平移向量结合(用给定的矩阵乘以平移矩阵就能获得需要的最后的矩阵)。
|
||||
我们先用GLM内建的向量类定义一个叫做`vec`的向量。接下来我们定义一个`mat4`类型的`trans`,默认是4×4单位矩阵。接下来我们创建一个变换矩阵,我们是把单位矩阵和一个平移向量传递给`glm::translate`函数来完成这个工作的(然后用给定的矩阵乘以平移矩阵就能获得最后需要的矩阵)。
|
||||
|
||||
我们把向量乘以平移矩阵就能获得最后的结果。如果我们仍然记得平移矩阵是如何工作的话,得到的向量应该是(1 + 1, 0 + 1, 0 + 0)就是(2, 1, 0)。这段代码用平移矩阵输出210。
|
||||
之后我们把向量乘以平移矩阵并且输出最后的结果。如果我们仍然记得平移矩阵是如何工作的话,得到的向量应该是(1 + 1, 0 + 1, 0 + 0),也就是(2, 1, 0)。这个代码片段将会输出210,所以这个平移矩阵是正确的。
|
||||
|
||||
我们来做些更有意思的是,旋转和缩放前面教程中的那个箱子。首先我们把箱子逆时针旋转90度。然后缩放0.5,使它变成原来的二分之一。我们先来创建变换矩阵:
|
||||
我们来做些更有意思的事情,让我们来旋转和缩放之前教程中的那个箱子。首先我们把箱子逆时针旋转90度。然后缩放0.5倍,使它变成原来的二分之一。我们先来创建变换矩阵:
|
||||
|
||||
```c++
|
||||
glm::mat4 trans;
|
||||
@@ -333,13 +341,13 @@ trans = glm::rotate(trans, 90.0f, glm::vec3(0.0, 0.0, 1.0));
|
||||
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
|
||||
```
|
||||
|
||||
我们先把箱子在每个轴缩放到0.5然后沿Z轴旋转90度。因为我们把矩阵传递给了GLM的每个函数,GLM会自动乘以矩阵,返回的结果是一个变换矩阵,它结合了多个变换。
|
||||
首先,我们把箱子在每个轴缩放到0.5倍,然后沿Z轴旋转90度。注意有纹理的那面矩形是在XY平面上的,我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数,GLM会自动将矩阵相乘,返回的结果是一个包括了多个变换的变换矩阵。
|
||||
|
||||
!!! Attention
|
||||
|
||||
有些GLM版本接收的是弧度而不是角度,这种情况下你可以用`glm::radians(90.0f)`转换为弧度。
|
||||
有些GLM版本接收的是弧度而不是角度,这种情况下你可以用`glm::radians(90.0f)`将角度转换为弧度。
|
||||
|
||||
下一个大问题是:如何把矩阵传递给着色器?我们在前面简单提到过GLSL里的mat4类型。所以我们改写顶点着色器来接收一个mat4的uniform变量,然后用矩阵uniform乘以位置向量:
|
||||
下一个大问题是:如何把矩阵传递给着色器?我们在前面简单提到过GLSL里的`mat4`类型。所以我们改写顶点着色器来接收一个`mat4`的uniform变量,然后再用矩阵uniform乘以位置向量:
|
||||
|
||||
```c++
|
||||
#version 330 core
|
||||
@@ -362,22 +370,22 @@ void main()
|
||||
|
||||
!!! Attention
|
||||
|
||||
GLSL也有mat2和mat3类型,允许如同向量一样的混合运算。前面提到的所有数学运算(比如标量-矩阵乘法,矩阵-向量乘法和矩阵-矩阵乘法)都被允许使用矩阵类型。无论在哪里只要指定矩阵运算我们都会解释发生了什么。
|
||||
GLSL也有`mat2`和`mat3`类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(比如标量-矩阵乘法,矩阵-向量乘法和矩阵-矩阵乘法)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明发生了什么的。
|
||||
|
||||
我们添加一个uniform,在把它传给gl_Position之前用变换矩阵乘以位置向量。我们的箱子现在应该是原来的二分之一大小并旋转了90度(向左倾斜)。我们仍需要把变换矩阵传给着色器:
|
||||
在把位置向量传给`gl_Position`之前,我们添加一个uniform,并且用变换矩阵乘以它。我们的箱子现在应该是原来的二分之一大小并旋转了90度(向左倾斜)。当然,我们仍需要把变换矩阵传递给着色器:
|
||||
|
||||
```c++
|
||||
GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform");
|
||||
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
|
||||
```
|
||||
|
||||
首先请求uniform变量的地址,然后用glUniform函数把矩阵数据发送给着色器,这个函数有一个Matrix4fv的后缀。第一个参数现在应该很熟悉了,它是uniform的地址。第二个参数告诉OpenGL多少个矩阵会被发送过去,目前是1。第三个参数询问我们我们是否希望对我们的矩阵进行置换,这样会把矩阵的行转换为列。OpenGL使用的内部的矩阵布局叫做以列为主顺序(column-major ordering)布局。GLM已经是用以列为主顺序定义了它的矩阵,所以并不需要置换矩阵,我们可以填为GL_FALSE、最后一个参数是实际的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望的那种,因此我们要先用GLM的内建函数value_ptr来变换这些数据。
|
||||
我们首先请求uniform变量的地址,然后用有`Matrix4fv`后缀的`glUniform`函数把矩阵数据发送给着色器。第一个参数你现在应该很熟悉了,它是uniform的地址(Location)。第二个参数告诉OpenGL我们将要发送多少个矩阵,目前是1。第三个参数询问我们我们是否希望对我们的矩阵进行置换(Transpose),也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局叫做以列为主顺序的(Column-major Ordering)布局。GLM已经是用以列为主顺序定义了它的矩阵,所以并不需要置换矩阵,我们填`GL_FALSE`、最后一个参数是实际的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望的那种,因此我们要先用GLM的自带的函数`value_ptr`来变换这些数据。
|
||||
|
||||
我们创建了一个变换矩阵,在顶点着色器中声明了一个uniform,并把矩阵发送给了着色器,着色器会变换我们的顶点坐标。最后的结果应该看起来像这样:
|
||||
|
||||

|
||||
|
||||
完美!我们的箱子向左侧倾斜,是原来的二分之一大小,看来变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会把箱子重置在窗口的左下角。要随着时间旋转箱子我们必须在游戏循环中更新变换矩阵,因为每一次渲染循环都要更新它。我们使用GLFW的时间函数来推导出不同时间的不同角度:
|
||||
完美!我们的箱子向左侧倾斜,是原来的二分之一大小,看来变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的左下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它需要在每一次渲染迭代中被更新。我们使用GLFW的时间函数来获取不同时间的角度:
|
||||
|
||||
```c++
|
||||
glm::mat4 trans;
|
||||
@@ -385,21 +393,22 @@ trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
|
||||
trans = glm::rotate(trans,(GLfloat)glfwGetTime() * 50.0f, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
```
|
||||
|
||||
要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一帧中创建它,所以才能持续更新旋转。这意味着我们不得不在每次循环中重新创建变换矩阵。通常在渲染场景的时候,我们有多个变换矩阵用来重新创建每次渲染的新值。
|
||||
要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一次迭代中创建它,从而保证我们能够更新旋转矩阵。这也就意味着我们不得不在每次迭代中中重新创建变换矩阵。通常在渲染场景的时候,我们也会有多个在每次渲染迭代中都用新的值重新创建的变换矩阵
|
||||
|
||||
在这里我们先把箱子围绕原点(0, 0, 0)旋转,之后,我们把旋转过后的箱子平移到屏幕的右下角。记住,实际的变换顺序应该从下向上阅读:尽管在代码中我们先平移再旋转,实际的变换却是先应用旋转然后平移的。明白所有这些变换的组合,并且知道它们是如何应用到物体上的并不简单。只有尝试和实验这些变换你才能快速地掌握它们。
|
||||
|
||||
在这儿我们先把箱子围绕原点(0, 0, 0)旋转,之后,我们把它平移到屏幕的右下角。记住,实际的变换顺序应该从后往前阅读:尽管在代码中我们先平移再旋转,实际的变换确实先应用旋转然后平移的。明白所有这些变换结合,并知道它们是如何应用到物体上的并不简单。尝试和实验才能快速掌握它。
|
||||
如果你做对了,你将看到下面的结果:
|
||||
|
||||
<video src="http://learnopengl.com/video/getting-started/transformations.mp4" controls="controls">
|
||||
</video>
|
||||
|
||||
这就是你所得到的!一个平移过的箱子,它会一直转,一个变换矩阵就做到了!现在你可以明白为什么矩阵在图形领域是一个如此重要的工具了。我们可以定义一个无限数量的变换,把它们结合为一个单独矩阵,如果愿意的话我们可以重复使用。在着色器中使用矩阵可以省区重新定义顶点数据的时间,也会节约处理资源,因为我们没有总是重新发送数据(这很慢)。
|
||||
这就是我们刚刚做到的!一个平移过的箱子,它会一直转,一个变换矩阵就做到了!现在你可以明白为什么矩阵在图形领域是一个如此重要的工具了。我们可以定义一个无限数量的变换,把它们组合为一个单独的矩阵,如果愿意的话我们还可以重复使用它。在着色器中使用矩阵可以省去重新定义顶点数据的力气,它也能够节省处理时间,因为我们没有一直重新发送我们的数据(这是个非常慢的过程)。
|
||||
|
||||
如果你没有得到正确的结果,或者你有哪儿不清楚的地方。可以看[源码](http://learnopengl.com/code_viewer.php?code=getting-started/transformations)和[顶点](http://learnopengl.com/code_viewer.php?code=getting-started/transformations&type=vertex)、[片段](http://learnopengl.com/code_viewer.php?code=getting-started/transformations&type=fragment)着色器。
|
||||
|
||||
下个教程中,我们会讨论怎样使用矩阵为顶点定义不同的坐标空间。这将是我们进入实时3D图像的第一步!
|
||||
|
||||
# 练习
|
||||
## 练习
|
||||
|
||||
- 使用应用在箱子上的最后的变换,尝试将其改变成先旋转,后平移。看看发生了什么,试着想想为什么会发生这样的事情: [参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise1)
|
||||
- 尝试着再次调用`glDrawElements`画出第二个箱子,但是**只能**使用变换将其摆放在不同的位置。保证这个箱子被摆放在窗口的左上角,并且会不断的缩放(而不是旋转)。使用sin函数在这里会很有用;注意使用sin函数取到负值时会导致物体被翻转: [参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise2)
|
Reference in New Issue
Block a user