1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 04:35:28 +08:00
This commit is contained in:
Meow J
2016-08-08 01:06:53 +08:00
parent 2702ffd082
commit 62ece7ce3f
12 changed files with 134 additions and 116 deletions

View File

@@ -6,19 +6,19 @@
翻译 | Django 翻译 | Django
校对 | Meow J, [BLumia](https://github.com/blumia/) 校对 | Meow J, [BLumia](https://github.com/blumia/)
尽管我们现在已经知道了如何创建一个物体、着色、加入纹理从而给它们一些细节的表现,但是它们仍然还是不够有趣,因为它们都还是静态的物体。我们可以尝试着在每一帧改变物体的顶点并且重缓冲区从而使们移动,但这太繁琐了,而且会消耗很多的处理时间。然而,我们现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。当然,这并不是说我们会去讨论武术和数字虚拟世界(译注Matrix同样也是电影「黑客帝国」的英文名电影中人类生活在数字虚拟世界主角会武术) 尽管我们现在已经知道了如何创建一个物体、着色、加入纹理给它们一些细节的表现,但因为它们都还是静态的物体,仍是不够有趣。我们可以尝试着在每一帧改变物体的顶点并且重配置缓冲区从而使们移动,但这太繁琐了,而且会消耗很多的处理时间。我们现在有一个更好的解决方案,使用多个<def>矩阵</def>(Matrix)对象可以更好的<def>变换</def>(Transform)一个物体。当然,这并不是说我们会去讨论武术和数字虚拟世界译注Matrix同样也是电影「黑客帝国」的英文名电影中人类生活在数字虚拟世界主角会武术
**矩阵**(Matrix)是一种非常有用的数学工具,尽管听起来可能有些吓人,不过一旦你理解了它们后,它们会非常有用。在讨论矩阵的过程中,我们需要使用到一些数学知识。对于一些愿意多了解这些知识的读者,我会附加一些资源给你们阅读。 矩阵是一种非常有用的数学工具,尽管听起来可能有些吓人,不过一旦你理解了它们后,它们会变得非常有用。在讨论矩阵的过程中,我们需要使用到一些数学知识。对于一些愿意多了解这些知识的读者,我会附加一些资源给你们阅读。
为了深入了解变换,我们首先要在讨论矩阵之前了解一向量(Vector)。这一节的目标是让你拥有将来需要的最基础的数学背景知识. 如果你发现这节十分困难,尽量尝试去理解它们,当你以后需要它们的时候回过头来复习这些概念。 为了深入了解变换,我们首先要在讨论矩阵之前进一步了解一向量。这一节的目标是让你拥有将来需要的最基础的数学背景知识如果你发现这节十分困难,尽量尝试去理解它们,当你以后需要它们的时候回过头来复习这些概念。
# 向量 # 向量
向量(Vector)最最基本的定义就是一个方向。或者更正式的说,向量有一个**方向(Direction)**和**大小(Magnitude也叫做强度或长度)**。你可以把向量想成一个藏宝图上的指示“向左走10步向北走3步然后向右走5步”“左”就是方向“10步”就是向量的长度。你可以发现,这个藏宝图的指示一共有3个向量。向量可以在任意**维度**(Dimension)上但是我们通常只使用2至4维。如果一个向量有2个维度它表示一个平面的方向(想象一下2D的图像)当它有3个维度的时候它可以表达一个3D世界的方向。 向量最基本的定义就是一个方向。或者更正式的说,向量有一个<def>方向</def>(Direction)和<def>大小</def>(Magnitude也叫做强度或长度)。你可以把向量想成一个藏宝图上的指示“向左走10步向北走3步然后向右走5步”“左”就是方向“10步”就是向量的长度。那么这个藏宝图的指示一共有3个向量。向量可以在任意维度(Dimension)上但是我们通常只使用2至4维。如果一个向量有2个维度它表示一个平面的方向(想象一下2D的图像)当它有3个维度的时候它可以表达一个3D世界的方向。
下面你会看到3个向量每个向量在图像中都用一个箭头(x, y)表示。我们在2D图片中展示这些向量因为这样子会更直观. 你仍然可以把这些2D向量当做z坐标为0的3D向量。由于向量表示的是方向起始于何处**并不会**改变它的值。下图我们可以看到向量\(\color{red}{\bar{v}}\)和\(\color{blue}{\bar{w}}\)是相等的,尽管他们的起始点不同: 下面你会看到3个向量每个向量在2D图像中都用一个箭头(x, y)表示。我们在2D图片中展示这些向量因为这样子会更直观一点。你可以把这些2D向量当做z坐标为0的3D向量。由于向量表示的是方向起始于何处并不会改变它的值。下图我们可以看到向量\(\color{red}{\bar{v}}\)和\(\color{blue}{\bar{w}}\)是相等的,尽管他们的起始点不同:
![](http://learnopengl.com/img/getting-started/vectors.png) ![](../img/01/07/vectors.png)
数学家喜欢在字母上面加一横表示向量,比如说\(\bar{v}\)。当用在公式中时它们通常是这样的: 数学家喜欢在字母上面加一横表示向量,比如说\(\bar{v}\)。当用在公式中时它们通常是这样的:
@@ -26,23 +26,23 @@ $$
\bar{v} = \begin{pmatrix} \color{red}x \\ \color{green}y \\ \color{blue}z \end{pmatrix} \bar{v} = \begin{pmatrix} \color{red}x \\ \color{green}y \\ \color{blue}z \end{pmatrix}
$$ $$
由于向量是一个方向,所以有些时候会很难形象地将它们用位置(Position)表示出来。我们通常设定这个方向的原点为(0,0,0),然后指向对应坐标的点,使其变为**位置向量(Position Vector)**来表示(你也可以把起点设置为其他的点,然后说:这个向量从这个点起始指向另一个点)。位置向量(3, 5)在图像中起点是(0, 0),指向(3, 5)。我们可以使用向量在2D或3D空间中表示方向**与**位置. 由于向量是一个方向,所以有些时候会很难形象地将它们用位置(Position)表示出来。为了让其更为直观,我们通常设定这个方向的原点为(0, 0, 0),然后指向一个方向,对应一个点,使其变为<def>位置向量</def>(Position Vector)你也可以把起点设置为其他的点,然后说:这个向量从这个点起始指向另一个点)。比如说位置向量(3, 5)在图像中起点是(0, 0)并会指向(3, 5)。我们可以使用向量在2D或3D空间中表示方向**与**位置.
和普通数字一样,我们也可以用向量进行多种运算(其中一些你可能已经知道了) 和普通数字一样,我们也可以用向量进行多种运算其中一些你可能已经看到过了)
## 向量与标量运算 ## 向量与标量运算
**标量(Scalar)**只是一个数字(或者说是仅有一个分量的矢量)。当把一个向量加/减/乘/除一个标量,我们可以简单的把向量的每个分量分别进行该运算。对于加法来说会像这样: <def>标量</def>(Scalar)只是一个数字或者说是仅有一个分量的向量)。当把一个向量加/减/乘/除一个标量,我们可以简单的把向量的每个分量分别进行该运算。对于加法来说会像这样:
$$ $$
\begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + x = \begin{pmatrix} \color{red}1 + x \\ \color{green}2 + x \\ \color{blue}3 + x \end{pmatrix} \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + x = \begin{pmatrix} \color{red}1 + x \\ \color{green}2 + x \\ \color{blue}3 + x \end{pmatrix}
$$ $$
其中的+可以是+-,·或÷,其中·是乘号。注意-和÷运算时不能颠倒,因为颠倒的运算是没有定义的(标量-/÷矢量) 其中的+可以是+-,·或÷,其中·是乘号。注意-和÷运算时不能颠倒(标量-/÷向量),因为颠倒的运算是没有定义的
## 向量取反 ## 向量取反
对一个向量取反(Negation)会将其方向逆转。一个指向东北的向量取反后就指向西南方向了。我们在一个向量的每个分量前加负号就可以实现取反了(或者说用-1数乘该向量): 对一个向量取反(Negate)会将其方向逆转。一个指向东北的向量取反后就指向西南方向了。我们在一个向量的每个分量前加负号就可以实现取反了或者说用-1数乘该向量:
$$ $$
-\bar{v} = -\begin{pmatrix} \color{red}{v_x} \\ \color{blue}{v_y} \\ \color{green}{v_z} \end{pmatrix} = \begin{pmatrix} -\color{red}{v_x} \\ -\color{blue}{v_y} \\ -\color{green}{v_z} \end{pmatrix} -\bar{v} = -\begin{pmatrix} \color{red}{v_x} \\ \color{blue}{v_y} \\ \color{green}{v_z} \end{pmatrix} = \begin{pmatrix} -\color{red}{v_x} \\ -\color{blue}{v_y} \\ -\color{green}{v_z} \end{pmatrix}
@@ -50,39 +50,39 @@ $$
## 向量加减 ## 向量加减
向量的加法可以被定义为是**分量的(Component-wise)**相加,即将一个向量中的每一个分量加上另一个向量的对应分量: 向量的加法可以被定义为是<def>分量的</def>(Component-wise)相加,即将一个向量中的每一个分量加上另一个向量的对应分量:
$$ $$
\bar{v} = \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}4 \\ \color{green}5 \\ \color{blue}6 \end{pmatrix} \rightarrow \bar{v} + \bar{k} = \begin{pmatrix} \color{red}1 + \color{red}4 \\ \color{green}2 + \color{green}5 \\ \color{blue}3 + \color{blue}6 \end{pmatrix} = \begin{pmatrix} \color{red}5 \\ \color{green}7 \\ \color{blue}9 \end{pmatrix} \bar{v} = \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}4 \\ \color{green}5 \\ \color{blue}6 \end{pmatrix} \rightarrow \bar{v} + \bar{k} = \begin{pmatrix} \color{red}1 + \color{red}4 \\ \color{green}2 + \color{green}5 \\ \color{blue}3 + \color{blue}6 \end{pmatrix} = \begin{pmatrix} \color{red}5 \\ \color{green}7 \\ \color{blue}9 \end{pmatrix}
$$ $$
向量v = (4, 2)k = (1, 2)直观地表示为: 向量`v = (4, 2)``k = (1, 2)`可以直观地表示为:
![](http://learnopengl.com/img/getting-started/vectors_addition.png) ![](../img/01/07/vectors_addition.png)
就像普通数字的加减一样,向量的减法等于加上第二个向量的相反 就像普通数字的加减一样,向量的减法等于加上第二个向量的相反向量
$$ $$
\bar{v} = \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}4 \\ \color{green}5 \\ \color{blue}6 \end{pmatrix} \rightarrow \bar{v} + -\bar{k} = \begin{pmatrix} \color{red}1 + (-\color{red}{4}) \\ \color{green}2 + (-\color{green}{5}) \\ \color{blue}3 + (-\color{blue}{6}) \end{pmatrix} = \begin{pmatrix} -\color{red}{3} \\ -\color{green}{3} \\ -\color{blue}{3} \end{pmatrix} \bar{v} = \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}4 \\ \color{green}5 \\ \color{blue}6 \end{pmatrix} \rightarrow \bar{v} + -\bar{k} = \begin{pmatrix} \color{red}1 + (-\color{red}{4}) \\ \color{green}2 + (-\color{green}{5}) \\ \color{blue}3 + (-\color{blue}{6}) \end{pmatrix} = \begin{pmatrix} -\color{red}{3} \\ -\color{green}{3} \\ -\color{blue}{3} \end{pmatrix}
$$ $$
两个向量的相减会得到这两个向量指向位置的差. 这在我们想要获取两点的差会非常有用. 两个向量的相减会得到这两个向量指向位置的差这在我们想要获取两点的差会非常有用
![](http://learnopengl.com/img/getting-started/vectors_subtraction.png) ![](../img/01/07/vectors_subtraction.png)
## 长度 ## 长度
我们使用**勾股定理(Pythagoras Theorem)**来获取向量的长度(Length)/大小(Magnitude). 如果你把向量的x与y分量画出来该向量会形成一个以x与y分量为边三角形: 我们使用<def>勾股定理</def>(Pythagoras Theorem)来获取向量的长度(Length)/大小(Magnitude)如果你把向量的x与y分量画出来该向量会x与y分量为边形成一个三角形:
![](http://learnopengl.com/img/getting-started/vectors_triangle.png) ![](../img/01/07/vectors_triangle.png)
因为两条边(x和y)是已知的,而且我们希望知道斜边\(\color{red}{\bar{v}}\)的长度,所以我们可以通过勾股定理来计算出它: 因为两条边x和y是已知的,如果希望知道斜边\(\color{red}{\bar{v}}\)的长度,我们可以直接通过勾股定理来计算
$$ $$
||\color{red}{\bar{v}}|| = \sqrt{\color{green}x^2 + \color{blue}y^2} ||\color{red}{\bar{v}}|| = \sqrt{\color{green}x^2 + \color{blue}y^2}
$$ $$
\(||\color{red}{\bar{v}}||\)表示向量\(\color{red}{\bar{v}}\)的大小,我们也可以很容易加上\(z^2\)把这个公式拓展到三维空间 \(||\color{red}{\bar{v}}||\)表示向量\(\color{red}{\bar{v}}\)的长度,我们也可以加上\(z^2\)把这个公式拓展到三维空间
例子中向量(4, 2)的长度等于: 例子中向量(4, 2)的长度等于:
@@ -92,37 +92,37 @@ $$
结果是4.47。 结果是4.47。
有一个特殊类型向量叫做**单位向量(Unit Vector)**。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个分量除以向量的长度得到它的单位向量\(\hat{n}\) 有一个特殊类型向量叫做<def>单位向量</def>(Unit Vector)。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个分量除以向量的长度得到它的单位向量\(\hat{n}\)
$$ $$
\hat{n} = \frac{\bar{v}}{||\bar{v}||} \hat{n} = \frac{\bar{v}}{||\bar{v}||}
$$ $$
我们把这种方法叫做一个向量的**标准化(Normalizing)**。单位向量头上有一个^样子的记号,并且它会变得很有用,特别是在我们只关心方向不关长度的时候(如果我们改变向量的长度,它的方向并不会改变) 我们把这种方法叫做一个向量的<def>标准化</def>(Normalizing)。单位向量头上有一个^样子的记号。通常单位向量会变得很有用,特别是在我们只关心方向不关长度的时候如果改变向量的长度,它的方向并不会改变
## 向量相乘 ## 向量相乘
两个向量相乘是一种很奇怪的情况。普通的乘法在向量上是没有定义的,因为它在视觉上是没有意义的但是有两种特定情境,当需要乘法时我们可以从中选择:一个是**点乘(Dot Product)**,记作\(\bar{v} \cdot \bar{k}\),另一个是**叉乘(Cross Product)**,记作\(\bar{v} \times \bar{k}\)。 两个向量相乘是一种很奇怪的情况。普通的乘法在向量上是没有定义的,因为它在视觉上是没有意义的但是在相乘的时候我们有两种特定情况可以选择:一个是<def>点乘</def>(Dot Product),记作\(\bar{v} \cdot \bar{k}\),另一个是<def>叉乘</def>(Cross Product),记作\(\bar{v} \times \bar{k}\)。
### 点乘 ### 点乘
两个向量的点乘(Dot Product)等于它们的数乘结果乘以两个向量之间夹角的余弦值。听起来有点费解,看一下公式: 两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值。可能听起来有点费解,我们来看一下公式:
$$ $$
\bar{v} \cdot \bar{k} = ||\bar{v}|| \cdot ||\bar{k}|| \cdot \cos \theta \bar{v} \cdot \bar{k} = ||\bar{v}|| \cdot ||\bar{k}|| \cdot \cos \theta
$$ $$
它们之间的夹角我们记作\(\theta\)。为什么这很有用?想象如果\(\bar{v}\)和\(\bar{k}\)都是单位向量它们的长度等于1。公式会有效简化成 它们之间的夹角记作\(\theta\)。为什么这很有用?想象如果\(\bar{v}\)和\(\bar{k}\)都是单位向量,它们的长度等于1。这样公式会有效简化成:
$$ $$
\bar{v} \cdot \bar{k} = 1 \cdot 1 \cdot \cos \theta = \cos \theta \bar{v} \cdot \bar{k} = 1 \cdot 1 \cdot \cos \theta = \cos \theta
$$ $$
现在点**只**两个向量的角度有关。你也许记得90度的余弦是00度的余弦是1。使用点乘可以很容易测试两个向量是否正交(Orthogonal)或平行(正交意味着两个向量互为**直角**)。你可能想要了解更多关于正弦或余弦的知识,我推荐你看[可汗学院](https://www.khanacademy.org/math/trigonometry/basic-trigonometry/basic_trig_ratios/v/basic-trigonometry)的基础三角学视频。 现在点**只**定义了两个向量的角。你也许记得90度的余弦是00度的余弦是1。使用点乘可以很容易测试两个向量是否<def>正交</def>(Orthogonal)或平行正交意味着两个向量互为<def>直角</def>)。如果你想要了解更多关于正弦或余弦函数的知识,我推荐你看[可汗学院](https://www.khanacademy.org/math/trigonometry/basic-trigonometry/basic_trig_ratios/v/basic-trigonometry)的基础三角学视频。
!!! Important !!! Important
你可以通过点乘的结果计算两个非单位向量的夹角,点乘的结果除以两个向量的大小之积,得到的结果就是夹角的余弦值,即\(cos \theta\)。 可以通过点乘的结果计算两个非单位向量的夹角,点乘的结果除以两个向量的长度之积,得到的结果就是夹角的余弦值,即\(cos \theta\)。
译注:通过上面点乘定义式可推出: 译注:通过上面点乘定义式可推出:
@@ -130,55 +130,55 @@ $$
\cos \theta = \frac{\bar{v} \cdot \bar{k}}{||\bar{v}|| \cdot ||\bar{k}||} \cos \theta = \frac{\bar{v} \cdot \bar{k}}{||\bar{v}|| \cdot ||\bar{k}||}
$$ $$
所以,我们如何计算点乘?点乘是分量逐个相乘,然后再把结果相加。两个单位向量点乘就像这样(你可以用两个长度为1的验证) 所以,我们如何计算点乘?点乘是通过将对应分量逐个相乘,然后再把所得积相加来计算的。两个单位向量的(你可以验证它们的长度为1)点乘会像是这样
$$ $$
\begin{pmatrix} \color{red}{0.6} \\ -\color{green}{0.8} \\ \color{blue}0 \end{pmatrix} \cdot \begin{pmatrix} \color{red}0 \\ \color{green}1 \\ \color{blue}0 \end{pmatrix} = (\color{red}{0.6} * \color{red}0) + (-\color{green}{0.8} * \color{green}1) + (\color{blue}0 * \color{blue}0) = -0.8 \begin{pmatrix} \color{red}{0.6} \\ -\color{green}{0.8} \\ \color{blue}0 \end{pmatrix} \cdot \begin{pmatrix} \color{red}0 \\ \color{green}1 \\ \color{blue}0 \end{pmatrix} = (\color{red}{0.6} * \color{red}0) + (-\color{green}{0.8} * \color{green}1) + (\color{blue}0 * \color{blue}0) = -0.8
$$ $$
计算两个单位余弦的角度,我们使用反余弦\(cos^{-1}\) 结果是143.1度。现在我们很快就计算出了两个向量的角。点乘在计算光照的时候会很有用。 计算两个单位向量间的夹角,我们可以使用反余弦函数\(cos^{-1}\) 可得结果是143.1度。现在我们很快就计算出了两个向量的角。点乘在计算光照的时候非常有用。
### 叉乘 ### 叉乘
叉乘(Cross Product)只在3D空间有定义它需要两个不平行向量作为输入生成正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的那么叉乘的结果将会返回3个互相正交的向量。接下来的教程中,这很有用。下面的图片展示了3D空间中叉乘的样子 叉乘只在3D空间有定义,它需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量。接下来的教程中这会非常有用。下面的图片展示了3D空间中叉乘的样子
![](http://learnopengl.com/img/getting-started/vectors_crossproduct.png) ![](../img/01/07/vectors_crossproduct.png)
不同于其他运算,如果你没有钻研过线性代数,会觉得叉乘很反直觉,所以最好记住公式就没问题(记不住也没问题)。下面你会看到两个正交向量A和B叉乘结果 不同于其他运算,如果你没有钻研过线性代数,可能会觉得叉乘很反直觉,所以记住公式就没问题啦(记不住也没问题。下面你会看到两个正交向量A和B叉
$$ $$
\begin{pmatrix} \color{red}{A_{x}} \\ \color{green}{A_{y}} \\ \color{blue}{A_{z}} \end{pmatrix} \times \begin{pmatrix} \color{red}{B_{x}} \\ \color{green}{B_{y}} \\ \color{blue}{B_{z}} \end{pmatrix} = \begin{pmatrix} \color{green}{A_{y}} \cdot \color{blue}{B_{z}} - \color{blue}{A_{z}} \cdot \color{green}{B_{y}} \\ \color{blue}{A_{z}} \cdot \color{red}{B_{x}} - \color{red}{A_{x}} \cdot \color{blue}{B_{z}} \\ \color{red}{A_{x}} \cdot \color{green}{B_{y}} - \color{green}{A_{y}} \cdot \color{red}{B_{x}} \end{pmatrix} \begin{pmatrix} \color{red}{A_{x}} \\ \color{green}{A_{y}} \\ \color{blue}{A_{z}} \end{pmatrix} \times \begin{pmatrix} \color{red}{B_{x}} \\ \color{green}{B_{y}} \\ \color{blue}{B_{z}} \end{pmatrix} = \begin{pmatrix} \color{green}{A_{y}} \cdot \color{blue}{B_{z}} - \color{blue}{A_{z}} \cdot \color{green}{B_{y}} \\ \color{blue}{A_{z}} \cdot \color{red}{B_{x}} - \color{red}{A_{x}} \cdot \color{blue}{B_{z}} \\ \color{red}{A_{x}} \cdot \color{green}{B_{y}} - \color{green}{A_{y}} \cdot \color{red}{B_{x}} \end{pmatrix}
$$ $$
就像你所看到的,看起来毫无头绪。可如果你这么做了,你会得到第三个向量,它正交于你的输入向量。 是不是看起来毫无头绪?不过只要你按照步骤来了,你就能得到一个正交于两个输入向量的第三个向量。
# 矩阵 # 矩阵
现在我们已经讨论了向量的全部内容,是时候看看矩阵(Matrix)了!矩阵简单说是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的**元素(Element)**。下面是一个2×3矩阵的例子 现在我们已经讨论了向量的全部内容,是时候看看矩阵了!简单来说矩阵就是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的<def>元素</def>(Element)。下面是一个2×3矩阵的例子
$$ $$
\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}
$$ $$
矩阵可以通过(i, j)进行索引i是行j是列这就是上面的矩阵叫做2×3矩阵的原因(3列2行也叫做矩阵的**维度(Dimension)**)。这与你在索引2D图像时的(x, y)相反获取4的索引是(2, 1)(第二行,第一列)(译注:如果是图像索引应该是(1, 2),先算列,再算行) 矩阵可以通过(i, j)进行索引i是行j是列这就是上面的矩阵叫做2×3矩阵的原因3列2行也叫做矩阵的<def>维度</def>(Dimension)。这与你在索引2D图像时的(x, y)相反获取4的索引是(2, 1)第二行,第一列译注:如果是图像索引应该是(1, 2),先算列,再算行
关于矩阵基本也就是这些了,它就是矩形数学表达式阵列。矩阵也有非常漂亮的数学属性,就跟向量一样。矩阵有几个运算,叫做:矩阵加法、减法和乘法。 矩阵基本也就是这些了,它就是一个矩形数学表达式阵列。和向量一样,矩阵也有非常漂亮的数学属性。矩阵有几个运算,分别是:矩阵加法、减法和乘法。
## 矩阵的加减 ## 矩阵的加减
矩阵与标量的加减如下所示 矩阵与标量之间的加减定义如下:
$$ $$
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \color{green}3 = \begin{bmatrix} 1 + \color{green}3 & 2 + \color{green}3 \\ 3 + \color{green}3 & 4 + \color{green}3 \end{bmatrix} = \begin{bmatrix} 4 & 5 \\ 6 & 7 \end{bmatrix} \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \color{green}3 = \begin{bmatrix} 1 + \color{green}3 & 2 + \color{green}3 \\ 3 + \color{green}3 & 4 + \color{green}3 \end{bmatrix} = \begin{bmatrix} 4 & 5 \\ 6 & 7 \end{bmatrix}
$$ $$
标量值要加到矩阵的每一个元素上。矩阵与标量的减法也是同样的 标量值要加到矩阵的每一个元素上。矩阵与标量的减法也相似
$$ $$
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} - \color{green}3 = \begin{bmatrix} 1 - \color{green}3 & 2 - \color{green}3 \\ 3 - \color{green}3 & 4 - \color{green}3 \end{bmatrix} = \begin{bmatrix} -2 & -1 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} - \color{green}3 = \begin{bmatrix} 1 - \color{green}3 & 2 - \color{green}3 \\ 3 - \color{green}3 & 4 - \color{green}3 \end{bmatrix} = \begin{bmatrix} -2 & -1 \\ 0 & 1 \end{bmatrix}
$$ $$
矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,所以总体的规则和与标量运算是差不多的,只不过在相同索引下的元素才能进行运算。这也就是说加法和减法只同维度的矩阵是有定义的。一个3×2矩阵和一个2×3矩阵(或一个3×3矩阵与4×4矩阵)是不能进行加减的。我们看看两个2×2矩阵是怎样加的: 矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,所以总体的规则和与标量运算是差不多的,只不过在相同索引下的元素才能进行运算。这也就是说加法和减法只同维度的矩阵是有定义的。一个3×2矩阵和一个2×3矩阵或一个3×3矩阵与4×4矩阵是不能进行加减的。我们看看两个2×2矩阵是怎样加的:
$$ $$
\begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} + \begin{bmatrix} \color{red}5 & \color{red}6 \\ \color{green}7 & \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 + \color{red}5 & \color{red}2 + \color{red}6 \\ \color{green}3 + \color{green}7 & \color{green}4 + \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}6 & \color{red}8 \\ \color{green}{10} & \color{green}{12} \end{bmatrix} \begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} + \begin{bmatrix} \color{red}5 & \color{red}6 \\ \color{green}7 & \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 + \color{red}5 & \color{red}2 + \color{red}6 \\ \color{green}3 + \color{green}7 & \color{green}4 + \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}6 & \color{red}8 \\ \color{green}{10} & \color{green}{12} \end{bmatrix}
@@ -198,16 +198,16 @@ $$
\color{green}2 \cdot \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} \color{green}2 \cdot 1 & \color{green}2 \cdot 2 \\ \color{green}2 \cdot 3 & \color{green}2 \cdot 4 \end{bmatrix} = \begin{bmatrix} 2 & 4 \\ 6 & 8 \end{bmatrix} \color{green}2 \cdot \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} \color{green}2 \cdot 1 & \color{green}2 \cdot 2 \\ \color{green}2 \cdot 3 & \color{green}2 \cdot 4 \end{bmatrix} = \begin{bmatrix} 2 & 4 \\ 6 & 8 \end{bmatrix}
$$ $$
现在我们也就能明白为什么一个单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值缩放(Scale)矩阵的所有元素(译注注意Scalar是由Scale + -ar演变过来的)。前面例子所有的元素都被放大了2倍。 现在我们也就能明白为什么这些单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值**缩放**(Scale)矩阵的所有元素译注注意Scalar是由Scale + -ar演变过来的。前面那个例子所有的元素都被放大了2倍。
到目前为止都还好,我们的例子都不复杂。不过矩阵与矩阵的乘法就不一样了。 到目前为止都还好,我们的例子都不复杂。不过矩阵与矩阵的乘法就不一样了。
### 矩阵相乘 ## 矩阵相乘
矩阵之间的乘法不见得有多复杂,但的确很难让人适应。矩阵乘法基本上意味着遵照规定好的法则进行相乘。当然,相乘还有一些限制: 矩阵之间的乘法不见得有多复杂,但的确很难让人适应。矩阵乘法基本上意味着遵照规定好的法则进行相乘。当然,相乘还有一些限制:
1. 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。 1. 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
2. 矩阵相乘不遵守**交换律(Commutative)**\(A \cdot B \neq B \cdot A\)。 2. 矩阵相乘不遵守<def>交换律</def>(Commutative)也就是说\(A \cdot B \neq B \cdot A\)。
我们先看一个两个2×2矩阵相乘的例子 我们先看一个两个2×2矩阵相乘的例子
@@ -215,113 +215,113 @@ $$
\begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} \cdot \begin{bmatrix} \color{blue}5 & \color{purple}6 \\ \color{blue}7 & \color{purple}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot \color{blue}5 + \color{red}2 \cdot \color{blue}7 & \color{red}1 \cdot \color{purple}6 + \color{red}2 \cdot \color{purple}8 \\ \color{green}3 \cdot \color{blue}5 + \color{green}4 \cdot \color{blue}7 & \color{green}3 \cdot \color{purple}6 + \color{green}4 \cdot \color{purple}8 \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix} \begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} \cdot \begin{bmatrix} \color{blue}5 & \color{purple}6 \\ \color{blue}7 & \color{purple}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot \color{blue}5 + \color{red}2 \cdot \color{blue}7 & \color{red}1 \cdot \color{purple}6 + \color{red}2 \cdot \color{purple}8 \\ \color{green}3 \cdot \color{blue}5 + \color{green}4 \cdot \color{blue}7 & \color{green}3 \cdot \color{purple}6 + \color{green}4 \cdot \color{purple}8 \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix}
$$ $$
现在你可能会在想了:我勒个去,刚刚到底发生了什么? 矩阵的乘法是一系列乘法和加法组合的结果,它使用到了左侧矩阵的行和右侧矩阵的列。我们可以看下面的图片: 现在你可能会在想了:天哪,刚刚到底发生了什么? 矩阵的乘法是一系列乘法和加法组合的结果,它使用到了左侧矩阵的行和右侧矩阵的列。我们可以看下面的图片:
![](http://learnopengl.com/img/getting-started/matrix_multiplication.png) ![](../img/01/07/matrix_multiplication.png)
我们先把左侧矩阵的行和右侧矩阵的列拿出来。这些我们挑出来行和列决定着作为结果的2×2矩阵的输出值。如果我们拿出来的是左矩阵的第一行,最终的值就会出现在作为结果矩阵的第一行,如果我们拿出来的是右矩阵的第一列,最终值会出现在作为结果矩阵的第一列。这正是红框里的情况。如果想计算结果矩阵右下角的值,我们要用第一个矩阵的第二行和第二个矩阵的第二列(译注:简单来说就是结果矩阵的元素的行取决于第一个矩阵,列取决于第二个矩阵) 我们先把左侧矩阵的行和右侧矩阵的列拿出来。这些挑出来行和列决定我们该计算结果2x2矩阵的哪个输出值。如果的是左矩阵的第一行,输出值就会出现在结果矩阵的第一行。接下来再取一列,如果我们的是右矩阵的第一列,最终值会出现在结果矩阵的第一列。这正是红框里的情况。如果想计算结果矩阵右下角的值,我们要用第一个矩阵的第二行和第二个矩阵的第二列译注:简单来说就是结果矩阵的元素的行取决于第一个矩阵,列取决于第二个矩阵
计算一项的结果值的方式是先计算左侧矩阵对应行和右侧矩阵对应列的第一个元素之积,然后是第二个,第三个,第四个等等,然后把所有的乘积相加,这就是结果了。现在我们就能解释为什么左侧矩阵的列数必须和右侧矩阵的行数相等了,如果不相等这一步的操作我们就无法完成了! 计算一项的结果值的方式是先计算左侧矩阵对应行和右侧矩阵对应列的第一个元素之积,然后是第二个,第三个,第四个等等,然后把所有的乘积相加,这就是结果了。现在我们就能解释为什么左侧矩阵的列数必须和右侧矩阵的行数相等了,如果不相等这一步的运算就无法完成了
结果矩阵的维度是(n, m)n等于左侧矩阵的行数m等于右侧矩阵的列数。 结果矩阵的维度是(n, m)n等于左侧矩阵的行数m等于右侧矩阵的列数。
如果在脑子里想象出乘法有困难别担心。用笔写下来,如果遇到困难回头看这页的内容。随着时间流逝,矩阵乘法对你来说会变成很自然的事。 如果在脑子里想象出这一乘法有困难别担心。不断地动手计算,如果遇到困难回头看这页的内容。随着时间流逝,矩阵乘法对你来说会变成很自然的事。
我们用一个更大的例子来结束矩阵与矩阵乘法的讨论。试使用颜色来让这个公式更容易理解。作为一个有用的练习,你可以自己回答这个乘法问题然后对比你的结果和图中的这个(如果用笔计算,你很快就能掌握它们) 我们用一个更大的例子来结束对矩阵相乘的讨论。试使用颜色来寻找规律。作为一个有用的练习,你可以试着自己解答一下这个乘法问题,再将你的结果和图中的这个进行对比(如果用笔计算,你很快就能掌握它们
$$ $$
\begin{bmatrix} \color{red}4 & \color{red}2 & \color{red}0 \\ \color{green}0 & \color{green}8 & \color{green}1 \\ \color{blue}0 & \color{blue}1 & \color{blue}0 \end{bmatrix} \cdot \begin{bmatrix} \color{red}4 & \color{green}2 & \color{blue}1 \\ \color{red}2 & \color{green}0 & \color{blue}4 \\ \color{red}9 & \color{green}4 & \color{blue}2 \end{bmatrix} = \begin{bmatrix} \color{red}4 \cdot \color{red}4 + \color{red}2 \cdot \color{red}2 + \color{red}0 \cdot \color{red}9 & \color{red}4 \cdot \color{green}2 + \color{red}2 \cdot \color{green}0 + \color{red}0 \cdot \color{green}4 & \color{red}4 \cdot \color{blue}1 + \color{red}2 \cdot \color{blue}4 + \color{red}0 \cdot \color{blue}2 \\ \color{green}0 \cdot \color{red}4 + \color{green}8 \cdot \color{red}2 + \color{green}1 \cdot \color{red}9 & \color{green}0 \cdot \color{green}2 + \color{green}8 \cdot \color{green}0 + \color{green}1 \cdot \color{green}4 & \color{green}0 \cdot \color{blue}1 + \color{green}8 \cdot \color{blue}4 + \color{green}1 \cdot \color{blue}2 \\ \color{blue}0 \cdot \color{red}4 + \color{blue}1 \cdot \color{red}2 + \color{blue}0 \cdot \color{red}9 & \color{blue}0 \cdot \color{green}2 + \color{blue}1 \cdot \color{green}0 + \color{blue}0 \cdot \color{green}4 & \color{blue}0 \cdot \color{blue}1 + \color{blue}1 \cdot \color{blue}4 + \color{blue}0 \cdot \color{blue}2 \end{bmatrix} \begin{bmatrix} \color{red}4 & \color{red}2 & \color{red}0 \\ \color{green}0 & \color{green}8 & \color{green}1 \\ \color{blue}0 & \color{blue}1 & \color{blue}0 \end{bmatrix} \cdot \begin{bmatrix} \color{red}4 & \color{green}2 & \color{blue}1 \\ \color{red}2 & \color{green}0 & \color{blue}4 \\ \color{red}9 & \color{green}4 & \color{blue}2 \end{bmatrix} = \begin{bmatrix} \color{red}4 \cdot \color{red}4 + \color{red}2 \cdot \color{red}2 + \color{red}0 \cdot \color{red}9 & \color{red}4 \cdot \color{green}2 + \color{red}2 \cdot \color{green}0 + \color{red}0 \cdot \color{green}4 & \color{red}4 \cdot \color{blue}1 + \color{red}2 \cdot \color{blue}4 + \color{red}0 \cdot \color{blue}2 \\ \color{green}0 \cdot \color{red}4 + \color{green}8 \cdot \color{red}2 + \color{green}1 \cdot \color{red}9 & \color{green}0 \cdot \color{green}2 + \color{green}8 \cdot \color{green}0 + \color{green}1 \cdot \color{green}4 & \color{green}0 \cdot \color{blue}1 + \color{green}8 \cdot \color{blue}4 + \color{green}1 \cdot \color{blue}2 \\ \color{blue}0 \cdot \color{red}4 + \color{blue}1 \cdot \color{red}2 + \color{blue}0 \cdot \color{red}9 & \color{blue}0 \cdot \color{green}2 + \color{blue}1 \cdot \color{green}0 + \color{blue}0 \cdot \color{green}4 & \color{blue}0 \cdot \color{blue}1 + \color{blue}1 \cdot \color{blue}4 + \color{blue}0 \cdot \color{blue}2 \end{bmatrix}
\\ = \begin{bmatrix} 20 & 8 & 12 \\ 25 & 4 & 34 \\ 2 & 0 & 4 \end{bmatrix} \\ = \begin{bmatrix} 20 & 8 & 12 \\ 25 & 4 & 34 \\ 2 & 0 & 4 \end{bmatrix}
$$ $$
就像你所看到的那样,矩阵与矩阵相乘复杂而容易犯错(这就是我们通常让计算机做这件事的原因),而且当矩阵变大以后很快就会出现问题。如果你仍然希望了解更多,对矩阵的数学性感到好奇,我强烈推荐你看看[可汗学院](https://www.khanacademy.org/math/algebra2/algebra-matrices)的矩阵内容视频 可以看到,矩阵相乘非常繁琐而容易出错(这也是我们通常让计算机做这件事的原因,而且当矩阵变大以后很快就会出现问题。如果你仍然希望了解更多,对矩阵的数学性感到好奇,我强烈推荐你看看[可汗学院](https://www.khanacademy.org/math/algebra2/algebra-matrices)的矩阵教程
不管怎样,反正现在我们知道如何进行矩阵相乘了,我们可以开始了解好东西了。 不管怎样,现在我们知道如何进行矩阵相乘了,我们可以开始学习好东西了。
# 矩阵与向量相乘 # 矩阵与向量相乘
目前,通过这些教程我们已经相当了解向量了。我们用向量来表示位置、颜色和纹理坐标。让我们进到兔子洞更深处:向量基本上就是一个**N×1**矩阵N向量分量的个数(也叫**N维(N-dimensional)**向量)。如果你仔细思考这个问题,会很有意思。向量和矩阵一样都是一个数字序列,但它只有1列。所以,这个新信息能如何帮助我们如果我们有一个M×N矩阵我们可以用这个矩阵乘以我们的N×1向量因为我们的矩阵的列数等于向量的行数,所以它们就能相乘。 目前为止,通过这些教程我们已经相当了解向量了。我们用向量来表示位置,表示颜色,甚至是纹理坐标。让我们更深入了解一下向量,它其实就是一个**N×1**矩阵N表示向量分量的个数也叫<def>N维</def>(N-dimensional)向量。如果你仔细思考一下就会明白。向量和矩阵一样都是一个数字序列但它只有1列。那么,这个新的定义对我们有什么帮助呢?如果我们有一个**M×N**矩阵,我们可以用这个矩阵乘以我们的**N×1**向量,因为这个矩阵的列数等于向量的行数,所以它们就能相乘。
但是为什么我们关心矩阵是否能够乘以一个向量?很多有意思的2D/3D变换本质上都是矩阵,而矩阵与我们的向量相乘会变换我们的向量。如你仍然有些困惑,我们看一些例子,你很快就能明白了。 但是为什么我们关心矩阵能否乘以一个向量?好吧,正巧,很多有的2D/3D变换都可以放在一个矩阵中,用这个矩阵乘以我们的向量将**变换**(Transform)这个向量。如你仍然有些困惑,我们看一些例子,你很快就能明白了。
## 单位矩阵 ## 单位矩阵
在OpenGL中因为有一些原因我们通常使用4×4的变换矩阵而其中最重要的原因就是因为每一个向量都有4个分量的。我们能想到的最简单的变换矩阵就是**单位矩阵(Identity Matrix)**。单位矩阵是一个除了对角线以外都是0的N × N矩阵。就像你看到,这变换矩阵使一个向量完全不变: 在OpenGL中由于某些原因我们通常使用**4×4**的变换矩阵,而其中最重要的原因就是大部分的向量都是4分量的。我们能想到的最简单的变换矩阵就是<def>单位矩阵</def>(Identity Matrix)。单位矩阵是一个除了对角线以外都是0的**N×N**矩阵。在下式中可以看到,这变换矩阵使一个向量完全不变:
$$ $$
\begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot 1 \\ \color{green}1 \cdot 2 \\ \color{blue}1 \cdot 3 \\ \color{purple}1 \cdot 4 \end{bmatrix} = \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot 1 \\ \color{green}1 \cdot 2 \\ \color{blue}1 \cdot 3 \\ \color{purple}1 \cdot 4 \end{bmatrix} = \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix}
$$ $$
向量看起来完全没。从乘法法则来看很明显:第一个结果分量是矩阵的第一行的每个对应分量乘以向量的每一个分量。因为每行的分量除了第一个都是0可得: \(\color{red}1\cdot1 + \color{red}0\cdot2 + \color{red}0\cdot3 + \color{red}0\cdot4 = 1\)这对向量的其他3个分量同样适用 向量看起来完全没。从乘法法则来看就很容易理解来:第一个结果元素是矩阵的第一行的每个元素乘以向量的每个对应元素。因为每行的元素除了第一个都是0可得\(\color{red}1\cdot1 + \color{red}0\cdot2 + \color{red}0\cdot3 + \color{red}0\cdot4 = 1\)向量的其他3个元素同理
!!! Important !!! Important
你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这是一个对证明定理、解线性方程非常有用的矩阵。 你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这是一个对证明定理、解线性方程非常有用的矩阵。
## 缩放 ## 缩放
当我们对一个向量进行缩放(Scaling)的时候就是对向量的长度进行缩放,而它的方向保持不变。如果我们进行2或3维操作那么我们可以分别定义一个有2或3个缩放变量的向量每个变量缩放一个轴(x、y或z)。 对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变。由于我们进行的是2维或3维操作我们可以分别定义一个有2或3个缩放变量的向量每个变量缩放一个轴(x、y或z)。
我们可以尝试缩放向量\(\color{red}{\bar{v}} = (3,2)\)。我们可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;我们可以沿着y轴把向量的高度缩放为原来的两倍。我们看看把向量缩放(0.5, 2)所获得的\(\color{blue}{\bar{s}}\)是什么样的: 我们先来尝试缩放向量\(\color{red}{\bar{v}} = (3,2)\)。我们可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;我们沿着y轴把向量的高度缩放为原来的两倍。我们看看把向量缩放(0.5, 2)所获得的\(\color{blue}{\bar{s}}\)是什么样的:
![](http://learnopengl.com/img/getting-started/vectors_scale.png) ![](../img/01/07/vectors_scale.png)
记住OpenGL通常是在3D空间操作的对于2D的情况我们可以把z轴缩放1这样z轴的值就不变了。我们刚刚的缩放操作是**不均匀(Non-uniform)**缩放,因为每个轴的缩放因子(Scaling Factor)都不一样。如果每个轴的缩放都一样那么就叫**均匀缩放(Uniform Scale)** 记住OpenGL通常是在3D空间进行操作的对于2D的情况我们可以把z轴缩放1倍,这样z轴的值就不变了。我们刚刚的缩放操作是<def>不均匀(Non-uniform)<def>缩放,因为每个轴的缩放因子(Scaling Factor)都不一样。如果每个轴的缩放因子都一样那么就叫<def>均匀缩放</def>(Uniform Scale)。
我们下面设置一个变换矩阵来为我们提供缩放功能。我们从单位矩阵了解到,每个对角线元素乘以对应的向量分量。如果我们把1变为3会怎样种情况,我们就把向量的每个分量乘以3了这事实上就把向量缩放3。如果我们把缩放变量表示为\((\color{red}{S_1}, \color{green}{S_2}, \color{blue}{S_3})\)我们可以为任意向量\((x,y,z)\)定义一个缩放矩阵: 我们下面会构造一个变换矩阵来为我们提供缩放功能。我们从单位矩阵了解到,每个对角线元素会分别与向量的对应元素相乘。如果我们把1变为3会怎样样子的话,我们就把向量的每个元素乘以3了这事实上就把向量缩放3。如果我们把缩放变量表示为\((\color{red}{S_1}, \color{green}{S_2}, \color{blue}{S_3})\)我们可以为任意向量\((x,y,z)\)定义一个缩放矩阵:
$$ $$
\begin{bmatrix} \color{red}{S_1} & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}{S_2} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}{S_3} & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{S_1} \cdot x \\ \color{green}{S_2} \cdot y \\ \color{blue}{S_3} \cdot z \\ 1 \end{pmatrix} \begin{bmatrix} \color{red}{S_1} & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}{S_2} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}{S_3} & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{S_1} \cdot x \\ \color{green}{S_2} \cdot y \\ \color{blue}{S_3} \cdot z \\ 1 \end{pmatrix}
$$ $$
注意,第四个缩放向量仍然是1因为不会缩放3D空间中w分量。w分量另有其他用途在后面我们会看到。 注意第四个缩放向量仍然是1因为3D空间中缩放w分量是无意义的。w分量另有其他用途在后面我们会看到。
## ##
**平移(Translation)**是在原向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,这样就基于平移向量**移动(Move)**了向量。我们已经讨论了向量加法,所以应该不会陌生。 <def>位移</def>(Translation)是在原向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,从而在位移向量基础上**移动**了原始向量。我们已经讨论了向量加法,所以应该不会陌生。
和缩放矩阵一样在4×4矩阵上有几个特别的位置用来执行特定的操作对于移来说它们是第四列最上面的3个值。如果我们把缩放向量表示为\((\color{red}{T_x},\color{green}{T_y},\color{blue}{T_z})\)我们就能把移矩阵定义为: 和缩放矩阵一样在4×4矩阵上有几个特别的位置用来执行特定的操作对于移来说它们是第四列最上面的3个值。如果我们把缩放向量表示为\((\color{red}{T_x},\color{green}{T_y},\color{blue}{T_z})\)我们就能把移矩阵定义为
$$ $$
\begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}{T_x} \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}{T_y} \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}{T_z} \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x + \color{red}{T_x} \\ y + \color{green}{T_y} \\ z + \color{blue}{T_z} \\ 1 \end{pmatrix} \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}{T_x} \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}{T_y} \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}{T_z} \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x + \color{red}{T_x} \\ y + \color{green}{T_y} \\ z + \color{blue}{T_z} \\ 1 \end{pmatrix}
$$ $$
这样是能工作的,因为所有的移值都要乘以向量的w列,所以移值会加到向量的原始坐标上(想想矩阵乘法法则)。而如果你用3x3矩阵我们的移值就没地方放也没地方乘了,所以是不行的。 这样是能工作的,因为所有的移值都要乘以向量的**w**列,所以移值会加到向量的原始值上(想想矩阵乘法法则。而如果你用3x3矩阵我们的移值就没地方放也没地方乘了,所以是不行的。
!!! Important !!! Important
**齐次坐标(Homogeneous coordinates)** **齐次坐标(Homogeneous Coordinates)**
向量的w分量也叫**齐次坐标**。想要从齐次坐标得到3D坐标我们可以把x、y和z坐标除以w坐标。我们通常不会注意这个问题因为w分量通常是1.0。使用齐次坐标有几点好处它允许我们在3D向量上进行平移(如果没有w分量我们是不能移向量的)下一章我们会用w值创建3D图像 向量的w分量也叫<def>齐次坐标</def>。想要从齐次向量得到3D向量我们可以把x、y和z坐标分别除以w坐标。我们通常不会注意这个问题因为w分量通常是1.0。使用齐次坐标有几点好处它允许我们在3D向量上进行位移(如果没有w分量我们是不能移向量的),而且下一章我们会用w值创建3D视觉效果
如果一个向量的齐次坐标是0这个坐标就是**方向向量(Direction Vector)**因为w坐标是0这个向量就不能平移(译注:这也就是我们说的不能移一个方向) 如果一个向量的齐次坐标是0这个坐标就是<def>方向向量</def>(Direction Vector)因为w坐标是0这个向量就不能位移(译注这也就是我们说的不能移一个方向
有了移矩阵我们就可以在3个方向(x、y、z)上移动物体,它是我们的变换工具箱中非常有用的一个变换矩阵。 有了移矩阵我们就可以在3个方向(x、y、z)上移动物体,它是我们的变换工具箱中非常有用的一个变换矩阵。
### 旋转 ## 旋转
上面几个的变换内容相对容易理解在2D或3D空间中也容易表示出来但旋转(Rotation)稍复杂些。如果你想知道旋转矩阵是如何构造出来的,我推荐你去看可汗学院[线性代数](https://www.khanacademy.org/math/linear-algebra/matrix_transformations)视频。 上面几个的变换内容相对容易理解在2D或3D空间中也容易表示出来但旋转(Rotation)稍复杂些。如果你想知道旋转矩阵是如何构造出来的,我推荐你去看可汗学院[线性代数](https://www.khanacademy.org/math/linear-algebra/matrix_transformations)视频。
首先我们来定义一个向量的旋转到底是什么。2D或3D空间中的旋转用**角(Angle)**来表示。角可以是角度制或弧度制的周角是360度或2 [PI](https://en.wikipedia.org/wiki/Pi)弧度。我个人更喜欢用角度,因为它们看起来更直观。 首先我们来定义一个向量的旋转到底是什么。2D或3D空间中的旋转用<def></def>(Angle)来表示。角可以是角度制或弧度制的周角是360度或2 [PI](https://en.wikipedia.org/wiki/Pi)弧度。我个人更喜欢用角度,因为它们看起来更直观。
!!! Important !!! Important
大多数旋转函数需要用弧度制的角,但是角度制的角也可以很容易地转化为弧度制: 大多数旋转函数需要用弧度制的角,但幸运的是角度制的角也可以很容易地转化为弧度制
- 弧度转角度:角度 = 弧度 * (180.0f / PI) - 弧度转角度:`角度 = 弧度 * (180.0f / PI)`
- 角度转弧度:弧度 = 角度 * (PI / 180.0f) - 角度转弧度:`弧度 = 角度 * (PI / 180.0f)`
PI约等于3.14159265359。 `PI`约等于3.14159265359。
转半圈会向右旋转360/2 = 180度向右旋转1/5圈表示向右旋转360/5 = 72度。这表明2D空间的向量\(\color{red}{\bar{v}}\)是由\(\color{green}{\bar{k}}\)向右旋转72度得的: 转半圈会旋转360/2 = 180度向右旋转1/5圈表示向右旋转360/5 = 72度。下图中展示的2D向量\(\color{red}{\bar{v}}\)是由\(\color{green}{\bar{k}}\)向右旋转72度得的:
![](http://learnopengl.com/img/getting-started/vectors_angle.png) ![](../img/01/07/vectors_angle.png)
在3D空间中旋转需要一个角**和**一个**旋转轴(Rotation Axis)**。物体会沿着给定的旋转轴旋转特定角度。如果你想要更形象化的描述,可以试试向下看着一个特定的旋转轴,同时将你的头部旋转一定角度。比如2D向量在3D空间中旋转时我们把旋转轴设为z轴(尝试想象这种情况) 在3D空间中旋转需要定义一个角**和**一个<def>旋转轴</def>(Rotation Axis)。物体会沿着给定的旋转轴旋转特定角度。如果你想要更形象化的感受,可以试试向下看着一个特定的旋转轴,同时将你的头部旋转一定角度。2D向量在3D空间中旋转时我们把旋转轴设为z轴尝试想象这种情况
使用三角学就能把一个向量变换为一个经过旋转特定角度的新向量。这通常是使用一系列正弦和余弦各种巧妙的组合得到的(一般简称sin和cos)。当然,讨论如何生成变换矩阵超出了这个教程的范围。 使用三角学,给定一个角度,可以把一个向量变换为一个经过旋转的新向量。这通常是使用一系列正弦和余弦函数一般简称sin和cos各种巧妙的组合得到的。当然,讨论如何生成变换矩阵超出了这个教程的范围。
旋转矩阵在3D空间中每个单位轴都有不同定义这个角度表示为\(\theta\) 旋转矩阵在3D空间中每个单位轴都有不同定义旋转角度用\(\theta\)表示
沿x轴旋转 沿x轴旋转
@@ -341,42 +341,41 @@ $$
\begin{bmatrix} \color{red}{\cos \theta} & - \color{red}{\sin \theta} & \color{red}0 & \color{red}0 \\ \color{green}{\sin \theta} & \color{green}{\cos \theta} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{\cos \theta} \cdot x - \color{red}{\sin \theta} \cdot y \\ \color{green}{\sin \theta} \cdot x + \color{green}{\cos \theta} \cdot y \\ z \\ 1 \end{pmatrix} \begin{bmatrix} \color{red}{\cos \theta} & - \color{red}{\sin \theta} & \color{red}0 & \color{red}0 \\ \color{green}{\sin \theta} & \color{green}{\cos \theta} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{\cos \theta} \cdot x - \color{red}{\sin \theta} \cdot y \\ \color{green}{\sin \theta} \cdot x + \color{green}{\cos \theta} \cdot y \\ z \\ 1 \end{pmatrix}
$$ $$
利用旋转矩阵我们可以把我们的位置向量(Position Vectors)沿一个或多个轴进行旋转。也可以把多个矩阵结合起来,比如先沿着X轴旋转再沿着Y轴旋转。但是这会很快导致一个问题——**万向节死锁(Gimbal Lock可以看看[这个视频](https://www.youtube.com/watch?v=zc8b2Jo7mno)[(优酷)](http://v.youku.com/v_show/id_XNzkyOTIyMTI=.html)来了解)**。我们不会讨论它的细节,但是一个更好的解决方案是沿着任意轴比如(0.662, 0.2, 0.7222)(注意这是个单位向量)旋转,而不是使用一系列旋转矩阵的组合。这样一个(超级麻烦)的矩阵是存在的,下面\((\color{red}{R_x}, \color{green}{R_y}, \color{blue}{R_z})\)代表任意旋转轴: 利用旋转矩阵我们可以把我们的位置向量沿一个单位轴进行旋转。也可以把多个矩阵结合起来,比如先沿着x轴旋转再沿着y轴旋转。但是这会很快导致一个问题——<def>万向节死锁</def>Gimbal Lock可以看看[这个视频](https://www.youtube.com/watch?v=zc8b2Jo7mno)[(优酷)](http://v.youku.com/v_show/id_XNzkyOTIyMTI=.html)来了解。我们不会讨论它的细节,但是一个更好的解决方案是沿着任意轴比如(0.662, 0.2, 0.7222)注意这是个单位向量旋转,而不是使用一系列旋转矩阵的组合。这样一个超级麻烦的矩阵是存在的,下面这个公式,\((\color{red}{R_x}, \color{green}{R_y}, \color{blue}{R_z})\)代表任意旋转轴:
$$ $$
\begin{bmatrix} \cos \theta + \color{red}{R_x}^2(1 - \cos \theta) & \color{red}{R_x}\color{green}{R_y}(1 - \cos \theta) - \color{blue}{R_z} \sin \theta & \color{red}{R_x}\color{blue}{R_z}(1 - \cos \theta) + \color{green}{R_y} \sin \theta & 0 \\ \color{green}{R_y}\color{red}{R_x} (1 - \cos \theta) + \color{blue}{R_z} \sin \theta & \cos \theta + \color{green}{R_y}^2(1 - \cos \theta) & \color{green}{R_y}\color{blue}{R_z}(1 - \cos \theta) - \color{red}{R_x} \sin \theta & 0 \\ \color{blue}{R_z}\color{red}{R_x}(1 - \cos \theta) - \color{green}{R_y} \sin \theta & \color{blue}{R_z}\color{green}{R_y}(1 - \cos \theta) + \color{red}{R_x} \sin \theta & \cos \theta + \color{blue}{R_z}^2(1 - \cos \theta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \cos \theta + \color{red}{R_x}^2(1 - \cos \theta) & \color{red}{R_x}\color{green}{R_y}(1 - \cos \theta) - \color{blue}{R_z} \sin \theta & \color{red}{R_x}\color{blue}{R_z}(1 - \cos \theta) + \color{green}{R_y} \sin \theta & 0 \\ \color{green}{R_y}\color{red}{R_x} (1 - \cos \theta) + \color{blue}{R_z} \sin \theta & \cos \theta + \color{green}{R_y}^2(1 - \cos \theta) & \color{green}{R_y}\color{blue}{R_z}(1 - \cos \theta) - \color{red}{R_x} \sin \theta & 0 \\ \color{blue}{R_z}\color{red}{R_x}(1 - \cos \theta) - \color{green}{R_y} \sin \theta & \color{blue}{R_z}\color{green}{R_y}(1 - \cos \theta) + \color{red}{R_x} \sin \theta & \cos \theta + \color{blue}{R_z}^2(1 - \cos \theta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}
$$ $$
在数学上讨论如何生成这样的矩阵仍然超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题(尽管会极大地避免)。避免万向节死锁的真正解决方案是使用**四元数(Quaternion)**,它不仅安全,而且计算更加友好。有关四元数会在后面的教程中讨论。 在数学上讨论如何生成这样的矩阵仍然超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题尽管会极大地避免。避免万向节死锁的真正解决方案是使用<def>四元数</def>(Quaternion),它不仅安全,而且计算更加友好。有关四元数会在后面的教程中讨论。
### 矩阵的组合 ## 矩阵的组合
使用矩阵变换的真正力量在于,根据矩阵之的乘法,我们可以把多个变换组合到一个矩阵中。让我们看看我们是否能生成一个多个变换相结合而成的变换矩阵。我们有一个顶点(x, y, z)我们希望将其缩放2倍然后位移(1, 2, 3)来平移它。我们需要一个移和缩放矩阵来完成这些变换。结果的变换矩阵看起来像这样: 使用矩阵进行变换的真正力量在于,根据矩阵之的乘法,我们可以把多个变换组合到一个矩阵中。让我们看看我们是否能生成一个变换矩阵,让它组合多个变换。假设我们有一个顶点(x, y, z)我们希望将其缩放2倍然后位移(1, 2, 3)个单位。我们需要一个移和缩放矩阵来完成这些变换。结果的变换矩阵看起来像这样:
$$ $$
Trans . Scale = \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} = \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} Trans . Scale = \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} = \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix}
$$ $$
注意,当矩阵相乘时我们先写移再写缩放变换的。矩阵乘法是不交换的,这意味着它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个乘以向量的,所以你应该从右向左读这个乘法。我们建议您在组合矩阵时,先进行缩放操作,然后是旋转,最后才是移,否则它们会(消极地)互相影响。比如,如果你先平移然后缩放,移的向量也会同样被缩放(译注比如向某方向移动2米2米也许会被缩放成1米) 注意,当矩阵相乘时我们先写移再写缩放变换的。矩阵乘法是不遵守交换的,这意味着它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个与向量相乘的,所以你应该从右向左读这个乘法。建议您在组合矩阵时,先进行缩放操作,然后是旋转,最后才是移,否则它们会消极地互相影响。比如,如果你先位移再缩放,移的向量也会同样被缩放译注比如向某方向移动2米2米也许会被缩放成1米
将我们的矢量左乘最终的变换矩阵会得到以下结果: 用最终的变换矩阵左乘我们的向量会得到以下结果:
$$ $$
\begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} \color{red}2x + \color{red}1 \\ \color{green}2y + \color{green}2 \\ \color{blue}2z + \color{blue}3 \\ 1 \end{bmatrix} \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} \color{red}2x + \color{red}1 \\ \color{green}2y + \color{green}2 \\ \color{blue}2z + \color{blue}3 \\ 1 \end{bmatrix}
$$ $$
不错向量先缩放2倍然后移了(1, 2, 3)个单位。 不错向量先缩放2倍然后移了(1, 2, 3)个单位。
# 实践 # 实践
现在我们已经解释了所有变换背后的理论是时候将这些知识利用起来了。OpenGL没有任何自带的矩阵和向量形式,所以我们必须自己定义数学类和方法。在这个教程中我们更愿意抽象所有的数学细节,使用已经做好了的数学库。幸运的是有个使用简单的专门为OpenGL量身定做的数学库那就是GLM。 现在我们已经解释了变换背后的所有理论是时候将这些知识利用起来了。OpenGL没有自带任何的矩阵和向量知识,所以我们必须定义自己的数学类和函数。在教程中我们更希望抽象所有的数学细节,使用已经做好了的数学库。幸运的是有个易于使用,专门为OpenGL量身定做的数学库那就是GLM。
## GLM ## GLM
<img alt="GLM Logo" src="../../img/01/07/glm.png" class="right" /> <img alt="GLM Logo" src="../../img/01/07/glm.png" class="right" />
GLM是Open**GL** **M**athematics的缩写它是一个只有头文件的库也就是说我们只需包含合适的头文件就行了不用链接和编译。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个头文件中找到
@@ -386,7 +385,7 @@ GLM是Open**GL** **M**athematics的缩写它是一个只有头文件的库
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
``` ```
我们来看看是否可以利用我们刚学的变换知识把一个向量(1, 0, 0)移(1, 1, 0)个单位(注意我们把它定义为一个glm::vec4类型的值其中齐次坐标我们设定为1.0) 我们来看看是否可以利用我们刚学的变换知识把一个向量(1, 0, 0)移(1, 1, 0)个单位注意,我们把它定义为一个`glm::vec4`类型的值齐次坐标设定为1.0
```c++ ```c++
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
@@ -396,11 +395,10 @@ vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl; std::cout << vec.x << vec.y << vec.z << std::endl;
``` ```
我们先用GLM内建的向量类定义一个叫做`vec`的向量。接下来我们定义一个`mat4`类型的`trans`默认是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++ ```c++
glm::mat4 trans; glm::mat4 trans;
@@ -408,13 +406,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)); trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
``` ```
首先我们把箱子在每个轴缩放到0.5倍,然后沿Z轴旋转90度。注意有纹理的那面矩形是在XY平面上的我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数GLM会自动将矩阵相乘返回的结果是一个包括了多个变换的变换矩阵。 首先,我们把箱子在每个轴缩放到0.5倍,然后沿z轴旋转90度。注意有纹理的那面矩形是在XY平面上的所以我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数GLM会自动将矩阵相乘返回的结果是一个包括了多个变换的变换矩阵。
!!! Attention !!! Attention
有些GLM版本接收的是弧度而不是角度这种情况下你可以用`glm::radians(90.0f)`将角度转换为弧度。 有些GLM版本接收的是弧度而不是角度这种情况下你可以用`glm::radians(90.0f)`将角度转换为弧度。
下一个大问题是如何把矩阵传递给着色器我们在前面简单提到过GLSL里`mat4`类型。所以我们改顶点着色器接收一个`mat4`的uniform变量然后再用矩阵uniform乘以位置向量 下一个大问题是如何把矩阵传递给着色器我们在前面简单提到过GLSL里也有一个`mat4`类型。所以我们将修改顶点着色器让其接收一个`mat4`的uniform变量然后再用矩阵uniform乘以位置向量
```c++ ```c++
#version 330 core #version 330 core
@@ -437,22 +435,22 @@ void main()
!!! Attention !!! Attention
GLSL也有`mat2`和`mat3`类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(比如标量-矩阵乘,矩阵-向量乘和矩阵-矩阵乘法)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明发生了什么的 GLSL也有`mat2`和`mat3`类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(像是标量-矩阵乘,矩阵-向量乘和矩阵-矩阵相乘)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明。
在把位置向量传给`gl_Position`之前我们添加一个uniform并且变换矩阵乘以它。我们的箱子现在应该是原来的二分之一大小并旋转了90度(向左倾斜)。当然,我们仍需要把变换矩阵传递给着色器: 在把位置向量传给<var>gl_Position</var>之前,我们添加一个uniform并且将其与变换矩阵乘。我们的箱子现在应该是原来的二分之一大小并(向左)旋转了90度。当然我们仍需要把变换矩阵传递给着色器
```c++ ```c++
GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform"); GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans)); glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
``` ```
我们首先请求uniform变量的地址然后用有`Matrix4fv`后缀的`glUniform`函数把矩阵数据发送给着色器。第一个参数你现在应该很熟悉了它是uniform的地址(Location)。第二个参数告诉OpenGL我们将要发送多少个矩阵目前是1。第三个参数询问我们我们是否希望对我们的矩阵进行置换(Transpose)也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局叫做**以列为主顺序的(Column-major Ordering)**布局。GLM已经是用以列为主顺序定义了它的矩阵,所以并不需要置换矩阵,我们填`GL_FALSE`最后一个参数是实际的矩阵数据但是GLM并不是把它们的矩阵储存为OpenGL所希望的那种因此我们要先用GLM的自带的函数`value_ptr`来变换这些数据。 我们首先查询uniform变量的地址然后用有`Matrix4fv`后缀的<fun>glUniform</fun>函数把矩阵数据发送给着色器。第一个参数你现在应该很熟悉了它是uniform的位置值。第二个参数告诉OpenGL我们将要发送多少个矩阵这里是1。第三个参数询问我们我们是否希望对我们的矩阵进行置换(Transpose)也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局叫做<def>列主序</def>(Column-major Ordering)布局。GLM的默认布局就是列主序,所以并不需要置换矩阵,我们填`GL_FALSE`最后一个参数是真正的矩阵数据但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种因此我们要先用GLM的自带的函数<fun>value_ptr</fun>来变换这些数据。
我们创建了一个变换矩阵在顶点着色器中声明了一个uniform并把矩阵发送给了着色器着色器会变换我们的顶点坐标。最后的结果应该看起来像这样 我们创建了一个变换矩阵在顶点着色器中声明了一个uniform并把矩阵发送给了着色器着色器会变换我们的顶点坐标。最后的结果应该看起来像这样
![](http://learnopengl.com/img/getting-started/transformations.png) ![](../img/01/07/transformations.png)
完美!我们的箱子向左侧倾斜,是原来的二分之一大小,看来变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它需要在每一次渲染迭代中更新。我们使用GLFW的时间函数来获取不同时间的角度 完美!我们的箱子向左侧旋转,并是原来的一半大小,所以变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它在每一次渲染迭代中都要更新。我们使用GLFW的时间函数来获取不同时间的角度
```c++ ```c++
glm::mat4 trans; glm::mat4 trans;
@@ -460,22 +458,21 @@ 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)); 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 src="../../img/01/07/transformations.mp4" controls="controls"></video>
</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)着色器。 如果你没有得到正确的结果,或者你有哪儿不清楚的地方。可以看[源码](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图像的第一步 一节我们会讨论怎样使用矩阵为顶点定义不同的坐标空间。这将是我们进入实时3D图像的第一步
## 练习 ## 练习
- 使用应用在箱子上的最后变换,尝试将其改变先旋转,后移。看看发生了什么,试着想想为什么会发生这样的事情: [参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise1) - 使用应用在箱子上的最后一个变换,尝试将其改变先旋转,后移。看看发生了什么,试着想想为什么会发生这样的事情[参考解答](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) - 尝试再次调用<fun>glDrawElements</fun>画出第二个箱子,**只**使用变换将其摆放在不同的位置。这个箱子被摆放在窗口的左上角,并且会不断的缩放而不是旋转)。`sin`函数在这里会很有用,不过注意使用`sin`函数时应用负值会导致物体被翻转[参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise2)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/img/01/07/vectors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -49,7 +49,7 @@
## 01-04 ## 01-04
- Graphics Pipeline图形渲染管线 - Graphics Pipeline图形渲染管线
- Resolution分辨率 - Resolution(图像,屏幕):分辨率
- Shader着色器 - Shader着色器
- GLSLOpenGL Shading LanguageOpenGL着色器语言 - GLSLOpenGL Shading LanguageOpenGL着色器语言
- Vertex Data顶点数据 - Vertex Data顶点数据
@@ -78,6 +78,7 @@
- Perspective Division透视划分 - Perspective Division透视划分
- Fragment Shader片段着色器 - Fragment Shader片段着色器
- Component分量 - Component分量
- Element元素
- Normalize标准化 - Normalize标准化
- Stride步长 - Stride步长
- Offset偏移量 - Offset偏移量
@@ -101,7 +102,7 @@
- Preprocessor Directives预处理指令 - Preprocessor Directives预处理指令
- Debug调试 - Debug调试
## 01-05 ## 01-06
- Texture Coordinate纹理坐标 - Texture Coordinate纹理坐标
- Sampling采样 - Sampling采样
@@ -116,3 +117,23 @@
- Channel通道 - Channel通道
- Sampler采样器 - Sampler采样器
- Texture Unit纹理单元 - Texture Unit纹理单元
## 01-07
- Direction方向
- Magnitude大小
- Dimension数学维度
- Scalar标量
- Position Vector位置向量
- Unit Vector单位向量
- Pythagoras Theorem勾股定理
- Dot Product点乘点积
- Cross Product叉乘叉积
- Identity Matrix单位矩阵
- Scale缩放
- Translation位移
- Homogeneous Coordinate齐次坐标
- Direction Vector方向向量
- Rotation旋转
- Rotation Axis旋转轴
- Column-major Ordering列主序