1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 04:35:28 +08:00

Irregular update

This commit is contained in:
Meow J
2016-08-14 12:50:04 +08:00
parent 628478b528
commit 07cba71890
19 changed files with 123 additions and 128 deletions

View File

@@ -77,11 +77,11 @@ $ mkdocs serve
## 建议
如果您发现文档有任何错误的话欢迎Fork这个工程并发送Pull Request到 `master` 分支。如果您不想修改的话,可以点击页面上方的 `Issues` 按钮提交一个Issue我们看到后会及时更正。
如果您发现教程有任何错误的话欢迎Fork这个工程并发送Pull Request到 `new-theme` 分支。如果您不想修改的话,可以点击页面上方的 `Issues` 按钮提交一个Issue我们看到后会及时更正。如果是对教程的内容有问题请先查看原文如果不是翻译错误的话请直接在原网站评论区向作者JoeyDeVries反馈。
## 样式指南
在文档的写作过程中,请遵守我们的[样式指南](https://github.com/LearnOpenGL-CN/LearnOpenGL-CN/blob/master/styleguide.md)方便之后的校对以及修改工作。
在文档的写作过程中,请遵守我们的[样式指南](https://github.com/LearnOpenGL-CN/LearnOpenGL-CN/blob/new-theme/styleguide.md)方便之后的校对以及修改工作。
## 联系方式

View File

@@ -79,7 +79,7 @@ GLfloat vertices[] = {
一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是**标准化设备坐标**了标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴)
![](../img/01/04/ndc.png)
<img alt="NDC" src="../../img/01/04/ndc.png" class="noborder" />
与通常的屏幕坐标不同y轴正方向为向上(0, 0)坐标是这个图像的中心,而不是左上角。最终你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。

View File

@@ -6,19 +6,19 @@
翻译 | Django
校对 | 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}\)。当用在公式中时它们通常是这样的:
@@ -26,23 +26,23 @@ $$
\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}
$$
其中的+可以是+-,·或÷,其中·是乘号。注意-和÷运算时不能颠倒,因为颠倒的运算是没有定义的(标量-/÷矢量)
其中的+可以是+-,·或÷,其中·是乘号。注意-和÷运算时不能颠倒(标量-/÷向量),因为颠倒的运算是没有定义的
## 向量取反
对一个向量取反(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}
@@ -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}
$$
向量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}
$$
两个向量的相减会得到这两个向量指向位置的差. 这在我们想要获取两点的差会非常有用.
两个向量的相减会得到这两个向量指向位置的差这在我们想要获取两点的差会非常有用
![](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}}||\)表示向量\(\color{red}{\bar{v}}\)的大小,我们也可以很容易加上\(z^2\)把这个公式拓展到三维空间
\(||\color{red}{\bar{v}}||\)表示向量\(\color{red}{\bar{v}}\)的长度,我们也可以加上\(z^2\)把这个公式拓展到三维空间
例子中向量(4, 2)的长度等于:
@@ -92,37 +92,37 @@ $$
结果是4.47。
有一个特殊类型向量叫做**单位向量(Unit Vector)**。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个分量除以向量的长度得到它的单位向量\(\hat{n}\)
有一个特殊类型向量叫做<def>单位向量</def>(Unit Vector)。单位向量有一个特别的性质——它的长度是1。我们可以用任意向量的每个分量除以向量的长度得到它的单位向量\(\hat{n}\)
$$
\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
$$
它们之间的夹角我们记作\(\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
$$
现在点**只**两个向量的角度有关。你也许记得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
你可以通过点乘的结果计算两个非单位向量的夹角,点乘的结果除以两个向量的大小之积,得到的结果就是夹角的余弦值,即\(cos \theta\)。
可以通过点乘的结果计算两个非单位向量的夹角,点乘的结果除以两个向量的长度之积,得到的结果就是夹角的余弦值,即\(cos \theta\)。
译注:通过上面点乘定义式可推出:
@@ -130,55 +130,55 @@ $$
\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
$$
计算两个单位余弦的角度,我们使用反余弦\(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}
$$
就像你所看到的,看起来毫无头绪。可如果你这么做了,你会得到第三个向量,它正交于你的输入向量。
是不是看起来毫无头绪?不过只要你按照步骤来了,你就能得到一个正交于两个输入向量的第三个向量。
# 矩阵
现在我们已经讨论了向量的全部内容,是时候看看矩阵(Matrix)了!矩阵简单说是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的**元素(Element)**。下面是一个2×3矩阵的例子
现在我们已经讨论了向量的全部内容,是时候看看矩阵了!简单来说矩阵就是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的<def>元素</def>(Element)。下面是一个2×3矩阵的例子
$$
\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} -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}
@@ -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}
$$
现在我们也就能明白为什么一个单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值缩放(Scale)矩阵的所有元素(译注注意Scalar是由Scale + -ar演变过来的)。前面例子所有的元素都被放大了2倍。
现在我们也就能明白为什么这些单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值**缩放**(Scale)矩阵的所有元素译注注意Scalar是由Scale + -ar演变过来的。前面那个例子所有的元素都被放大了2倍。
到目前为止都还好,我们的例子都不复杂。不过矩阵与矩阵的乘法就不一样了。
### 矩阵相乘
## 矩阵相乘
矩阵之间的乘法不见得有多复杂,但的确很难让人适应。矩阵乘法基本上意味着遵照规定好的法则进行相乘。当然,相乘还有一些限制:
1. 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
2. 矩阵相乘不遵守**交换律(Commutative)**\(A \cdot B \neq B \cdot A\)。
2. 矩阵相乘不遵守<def>交换律</def>(Commutative)也就是说\(A \cdot B \neq B \cdot A\)。
我们先看一个两个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}
$$
现在你可能会在想了:我勒个去,刚刚到底发生了什么? 矩阵的乘法是一系列乘法和加法组合的结果,它使用到了左侧矩阵的行和右侧矩阵的列。我们可以看下面的图片:
现在你可能会在想了:天哪,刚刚到底发生了什么? 矩阵的乘法是一系列乘法和加法组合的结果,它使用到了左侧矩阵的行和右侧矩阵的列。我们可以看下面的图片:
![](http://learnopengl.com/img/getting-started/matrix_multiplication.png)
<img alt="Matrix Multiplication" src="../../img/01/07/matrix_multiplication.png" class="noborder" />
我们先把左侧矩阵的行和右侧矩阵的列拿出来。这些我们挑出来行和列决定着作为结果的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} 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}
$$
向量看起来完全没。从乘法法则来看很明显:第一个结果分量是矩阵的第一行的每个对应分量乘以向量的每一个分量。因为每行的分量除了第一个都是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
你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这是一个对证明定理、解线性方程非常有用的矩阵。
你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这是一个对证明定理、解线性方程非常有用的矩阵。
## 缩放
当我们对一个向量进行缩放(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}
$$
注意,第四个缩放向量仍然是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}
$$
这样是能工作的,因为所有的移值都要乘以向量的w列,所以移值会加到向量的原始坐标上(想想矩阵乘法法则)。而如果你用3x3矩阵我们的移值就没地方放也没地方乘了,所以是不行的。
这样是能工作的,因为所有的移值都要乘以向量的**w**列,所以移值会加到向量的原始值上(想想矩阵乘法法则。而如果你用3x3矩阵我们的移值就没地方放也没地方乘了,所以是不行的。
!!! 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
大多数旋转函数需要用弧度制的角,但是角度制的角也可以很容易地转化为弧度制:
大多数旋转函数需要用弧度制的角,但幸运的是角度制的角也可以很容易地转化为弧度制
- 弧度转角度:角度 = 弧度 * (180.0f / PI)
- 角度转弧度:弧度 = 角度 * (PI / 180.0f)
- 弧度转角度:`角度 = 弧度 * (180.0f / PI)`
- 角度转弧度:`弧度 = 角度 * (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轴旋转
@@ -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}
$$
利用旋转矩阵我们可以把我们的位置向量(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}
$$
在数学上讨论如何生成这样的矩阵仍然超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题(尽管会极大地避免)。避免万向节死锁的真正解决方案是使用**四元数(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}
$$
注意,当矩阵相乘时我们先写移再写缩放变换的。矩阵乘法是不交换的,这意味着它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个乘以向量的,所以你应该从右向左读这个乘法。我们建议您在组合矩阵时,先进行缩放操作,然后是旋转,最后才是移,否则它们会(消极地)互相影响。比如,如果你先平移然后缩放,移的向量也会同样被缩放(译注比如向某方向移动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}
$$
不错向量先缩放2倍然后移了(1, 2, 3)个单位。
不错向量先缩放2倍然后移了(1, 2, 3)个单位。
# 实践
现在我们已经解释了所有变换背后的理论是时候将这些知识利用起来了。OpenGL没有任何自带的矩阵和向量形式,所以我们必须自己定义数学类和方法。在这个教程中我们更愿意抽象所有的数学细节,使用已经做好了的数学库。幸运的是有个使用简单的专门为OpenGL量身定做的数学库那就是GLM。
现在我们已经解释了变换背后的所有理论是时候将这些知识利用起来了。OpenGL没有自带任何的矩阵和向量知识,所以我们必须定义自己的数学类和函数。在教程中我们更希望抽象所有的数学细节,使用已经做好了的数学库。幸运的是有个易于使用,专门为OpenGL量身定做的数学库那就是GLM。
## GLM
<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个头文件中找到
@@ -386,7 +385,7 @@ GLM是Open**GL** **M**athematics的缩写它是一个只有头文件的库
#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++
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;
```
我们先用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++
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));
```
首先我们把箱子在每个轴缩放到0.5倍,然后沿Z轴旋转90度。注意有纹理的那面矩形是在XY平面上的我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数GLM会自动将矩阵相乘返回的结果是一个包括了多个变换的变换矩阵。
首先,我们把箱子在每个轴缩放到0.5倍,然后沿z轴旋转90度。注意有纹理的那面矩形是在XY平面上的所以我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数GLM会自动将矩阵相乘返回的结果是一个包括了多个变换的变换矩阵。
!!! Attention
有些GLM版本接收的是弧度而不是角度这种情况下你可以用`glm::radians(90.0f)`将角度转换为弧度。
下一个大问题是如何把矩阵传递给着色器我们在前面简单提到过GLSL里`mat4`类型。所以我们改顶点着色器接收一个`mat4`的uniform变量然后再用矩阵uniform乘以位置向量
下一个大问题是如何把矩阵传递给着色器我们在前面简单提到过GLSL里也有一个`mat4`类型。所以我们将修改顶点着色器让其接收一个`mat4`的uniform变量然后再用矩阵uniform乘以位置向量
```c++
#version 330 core
@@ -432,27 +430,27 @@ void main()
gl_Position = transform * vec4(position, 1.0f);
ourColor = color;
TexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
}
}
```
!!! Attention
GLSL也有`mat2`和`mat3`类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(比如标量-矩阵乘,矩阵-向量乘和矩阵-矩阵乘法)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明发生了什么的
GLSL也有`mat2`和`mat3`类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(像是标量-矩阵乘,矩阵-向量乘和矩阵-矩阵相乘)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明。
在把位置向量传给`gl_Position`之前我们添加一个uniform并且变换矩阵乘以它。我们的箱子现在应该是原来的二分之一大小并旋转了90度(向左倾斜)。当然,我们仍需要把变换矩阵传递给着色器:
在把位置向量传给<var>gl_Position</var>之前,我们添加一个uniform并且将其与变换矩阵乘。我们的箱子现在应该是原来的二分之一大小并(向左)旋转了90度。当然我们仍需要把变换矩阵传递给着色器
```c++
GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform");
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并把矩阵发送给了着色器着色器会变换我们的顶点坐标。最后的结果应该看起来像这样
![](http://learnopengl.com/img/getting-started/transformations.png)
![](../img/01/07/transformations.png)
完美!我们的箱子向左侧倾斜,是原来的二分之一大小,看来变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它需要在每一次渲染迭代中更新。我们使用GLFW的时间函数来获取不同时间的角度
完美!我们的箱子向左侧旋转,并是原来的一半大小,所以变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它在每一次渲染迭代中都要更新。我们使用GLFW的时间函数来获取不同时间的角度
```c++
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));
```
要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一次迭代中创建它,从而保证我们能够更新旋转矩阵。这也就意味着我们不得不在每次迭代中重新创建变换矩阵。通常在渲染场景的时候,我们也会有多个在每次渲染迭代中都用新值重新创建的变换矩阵
要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一次迭代中创建它,从而保证我们能够不断更新旋转角度。这也就意味着我们不得不在每次游戏循环的迭代中重新创建变换矩阵。通常在渲染场景的时候,我们也会有多个需要在每次渲染迭代中都用新值重新创建的变换矩阵
在这里我们先把箱子围绕原点(0, 0, 0)旋转,之后,我们把旋转过后的箱子移到屏幕的右下角。记住,实际的变换顺序应该从下向上阅读:尽管在代码中我们先移再旋转,实际的变换却是先应用旋转然后平移的。明白所有这些变换的组合,并且知道它们是如何应用到物体上的并不简单。只有尝试和实验这些变换你才能快速地掌握它们。
在这里我们先把箱子围绕原点(0, 0, 0)旋转,之后,我们把旋转过后的箱子移到屏幕的右下角。记住,实际的变换顺序应该与阅读顺序相反:尽管在代码中我们先移再旋转,实际的变换却是先应用旋转再是位移的。明白所有这些变换的组合,并且知道它们是如何应用到物体上是一件非常困难的事情。只有不断地尝试和实验这些变换你才能快速地掌握它们。
如果你做对了,你将看到下面的结果:
<video src="http://learnopengl.com/video/getting-started/transformations.mp4" controls="controls">
</video>
<video src="../../img/01/07/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图像的第一步
一节我们会讨论怎样使用矩阵为顶点定义不同的坐标空间。这将是我们进入实时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)
- 使用应用在箱子上的最后一个变换,尝试将其改变先旋转,后移。看看发生了什么,试着想想为什么会发生这样的事情[参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise1)
- 尝试再次调用<fun>glDrawElements</fun>画出第二个箱子,**只**使用变换将其摆放在不同的位置。这个箱子被摆放在窗口的左上角,并且会不断的缩放而不是旋转)。`sin`函数在这里会很有用,不过注意使用`sin`函数时应用负值会导致物体被翻转[参考解答](http://learnopengl.com/code_viewer.php?code=getting-started/transformations-exercise2)

View File

@@ -86,8 +86,6 @@ glUniform3f(lightDirPos, -0.2f, -1.0f, -0.3f);
你可以在这里获得[应用的所有代码](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_directional),这里是[顶点](http://learnopengl.com/code_viewer.php?code=lighting/lighting_maps&type=vertex)和[片段](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_directional&type=fragment)着色器代码。
# 点光源
定向光作为全局光可以照亮整个场景,这非常棒,但是另一方面除了定向光,我们通常也需要几个点光源(Point Light),在场景里发亮。点光是一个在时间里有位置的光源,它向所有方向发光,光线随距离增加逐渐变暗。想象灯泡和火炬作为投光物,它们可以扮演点光的角色。
@@ -110,8 +108,8 @@ $$
在这里\(d\)代表片段到光源的距离。为了计算衰减值我们定义3个可配置**常数**项\(K_c\)**一次**项\(K_l\)和**二次**项\(K_q\)。
- 常数项通常是1.0,它的作用是保证坟墓永远不会比1小因为它可以利用一定的距离增加亮度这个结果不会影响到我们所寻找的。
- 一次项用于与距离值相,这以线性的方式减少亮度。
- 常数项通常是1.0,它的作用是保证分母永远不会比1小因为它可以利用一定的距离增加亮度这个结果不会影响到我们所寻找的。
- 一次项用于与距离值相,这以线性的方式减少亮度。
- 二次项用于与距离的平方相乘,为光源设置一个亮度的二次递减。二次项在距离比较近的时候相比一次项会比一次项更小,但是当距离更远的时候比一次项更大。
由于二次项的光会以线性方式减少指导距离足够大的时候就会超过一次项之后光的亮度会减少的更快。最后的效果就是光在近距离时非常量但是距离变远亮度迅速降低最后亮度降低速度再次变慢。下面的图展示了在100以内的范围这样的衰减效果。
@@ -171,7 +169,7 @@ glUniform1f(glGetUniformLocation(lightingShader.Program, "light.quadratic"), 0.0
我们需要将光源的距离提供给公式还记得我们是怎样计算向量的长度吗我们可以通过获取片段和光源之间的不同向量把向量的长度结果作为距离项。我们可以使用GLSL的内建`length`函数做这件事:
```c++
float distance = length(light.position - Position);
float distance = length(light.position - FragPos);
float attenuation = 1.0f / (light.constant + light.linear*distance +light.quadratic*(distance*distance));
```

View File

@@ -274,7 +274,7 @@ vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string
注意,我们假设纹理文件与模型是在相同的目录里。我们可以简单的链接纹理位置字符串和之前获取的目录字符串(在`loadModel`函数中得到的)来获得完整的纹理路径(这就是为什么`GetTexture`函数同样需要目录字符串)。
有些在互联网上找到的模型使用绝对路径,它们的纹理位置就不会在每机器上都有效了。例子里,你可能希望手工编辑这个文件来使用本地路径为纹理所使用(如果可能的话)。
有些在互联网上找到的模型使用绝对路径,它们的纹理位置就不会在每机器上都有效了。例子里,你可能希望手工编辑这个文件来使用本地路径为纹理所使用(如果可能的话)。
这就是使用Assimp来导入一个模型的全部了。你可以在这里找到[Model类的代码](http://learnopengl.com/code_viewer.php?code=model_loading/model_unoptimized)。

View File

@@ -16,7 +16,7 @@
![](http://learnopengl.com/img/advanced-lighting/gamma_correction_brightness.png)
第一行是人眼所感知到的正常的灰阶亮度要增加一倍比如从0.1到0.2你才会感觉比原来变亮了一倍译注这里的意思是说比如一个东西的亮度0.3让人感觉它比原来变亮一倍那么现在这个亮度应该成为0.6而不是0.4也就是说人眼感知到的亮度的变化并非线性均匀分布的。问题的关键在于这样的一倍相当于一个亮度级例如假设0.1、0.2、0.4、0.8是我们定义的四个亮度级别在0.1和0.2之间人眼只能识别出0.15这个中间级而虽然0.4到0.8之间的差距更大,这个区间人眼也只能识别出一个颜色)。然而,当我们谈论光的物理亮度,也就是光子的数量的多少的时候,底部的灰阶显示出的才是这时讨论的亮度。底部的灰阶显示出的是双倍的亮度所返回的物理亮度译注这里亮度是指光子数量和正相关的亮度即物理亮度前面讨论的是人的感知亮度物理亮度和感知亮度的区别在于物理亮度基于光子数量感知亮度基于人的感觉比如第二个灰阶里亮度0.1的光子数量是0.2的二分之一),但是由于这与我们的眼睛感知亮度不完全一致(对比较暗的颜色变化更敏感),所以它看起来很奇怪
第一行是人眼所感知到的正常的灰阶亮度要增加一倍比如从0.1到0.2你才会感觉比原来变亮了一倍译注这里的意思是说比如一个东西的亮度0.3让人感觉它比原来变亮一倍那么现在这个亮度应该成为0.6而不是0.4也就是说人眼感知到的亮度的变化并非线性均匀分布的。问题的关键在于这样的一倍相当于一个亮度级例如假设0.1、0.2、0.4、0.8是我们定义的四个亮度级别在0.1和0.2之间人眼只能识别出0.15这个中间级而虽然0.4到0.8之间的差距更大,这个区间人眼也只能识别出一个颜色)。然而,当我们谈论光的物理亮度,比如光源发射光子的数量的时候,底部(第二行)的灰阶显示出的才是物理世界真实的亮度。底部的灰阶显示,亮度加倍时返回的也是真实的物理亮度译注这里亮度是指光子数量和正相关的亮度即物理亮度前面讨论的是人的感知亮度物理亮度和感知亮度的区别在于物理亮度基于光子数量感知亮度基于人的感觉比如第二个灰阶里亮度0.1的光子数量是0.2的二分之一),但是由于这与我们的眼睛感知亮度不完全一致(对比较暗的颜色变化更敏感),所以它看起来有差异
因为人眼看到颜色的亮度更倾向于顶部的灰阶监视器使用的也是一种指数关系电压的2.2次幂所以物理亮度通过监视器能够被映射到顶部的非线性亮度因此看起来效果不错译注CRT亮度是是电压的2.2次幂而人眼相当于2次幂因此CRT这个缺陷正好能满足人的需要
@@ -141,4 +141,4 @@ float attenuation = 1.0 / distance;
- [cambridgeincolour.com](http://www.cambridgeincolour.com/tutorials/gamma-correction.htm):更多关于gamma和gamma校正的内容。
- [wolfire.com](http://blog.wolfire.com/2010/02/Gamma-correct-lighting): David Rosen关于在渲染领域使用gamma校正的好处。
- [renderwonk.com](http://renderwonk.com/blog/index.php/archive/adventures-with-gamma-correct-rendering/): 一些额外的实践上的思考。
- [renderwonk.com](http://renderwonk.com/blog/index.php/archive/adventures-with-gamma-correct-rendering/): 一些额外的实践上的思考。

View File

@@ -14,7 +14,7 @@
光照并没有呈现出任何裂痕和孔洞完全忽略了砖块之间凹进去的线条表面看起来完全就是平的。我们可以使用specular贴图根据深度或其他细节阻止部分表面被照的更亮以此部分地解决问题但这并不是一个好方案。我们需要的是某种可以告知光照系统给所有有关物体表面类似深度这样的细节的方式。
如果我们光的视角来看这个问题是什么使表面被视为完全平坦的表面来照亮答案会是表面的法线向量。以光照算法的视角考虑的话只有一件事决定物体的形状这就是垂直于它的法线向量。砖块表面只有一个法线向量表面完全根据这个法线向量被以一致的方式照亮。如果每个fragment都是用自己的不同的法线会怎样这样我们就可以根据表面细微的细节对法线向量进行改变这样就会获得一种表面看起来要复杂得多的幻觉
如果我们光的视角来看这个问题是什么使表面被视为完全平坦的表面来照亮答案会是表面的法线向量。以光照算法的视角考虑的话只有一件事决定物体的形状这就是垂直于它的法线向量。砖块表面只有一个法线向量表面完全根据这个法线向量被以一致的方式照亮。如果每个fragment都是用自己的不同的法线会怎样这样我们就可以根据表面细微的细节对法线向量进行改变这样就会获得一种表面看起来要复杂得多的幻觉
![](http://learnopengl.com/img/advanced-lighting/normal_mapping_surfaces.png)
@@ -409,4 +409,4 @@ mat3 TBN = mat3(T, B, N)
- [Tutorial 26: Normal Mapping](http://ogldev.atspace.co.uk/www/tutorial26/tutorial26.html)ogldev的法线贴图教程。
- [How Normal Mapping Works](https://www.youtube.com/watch?v=LIOPYmknj5Q)TheBennyBox的讲述法线贴图如何工作的视频。
- [Normal Mapping Mathematics](https://www.youtube.com/watch?v=4FaWLgsctqY)TheBennyBox关于法线贴图的数学原理的教程。
- [Tutorial 13: Normal Mapping](http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/)opengl-tutorial.org提供的法线贴图教程。
- [Tutorial 13: Normal Mapping](http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/)opengl-tutorial.org提供的法线贴图教程。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 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: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 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: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -46,4 +46,4 @@ LearnOpenGL被分解成了许多大的主题。每个主题包括一些小节
- <fun>程序逻辑</fun>:红色的字是函数的名称或者是类名。
- <var>变量</var>蓝色的字是变量包括所有的OpenGL常量。
现在你应该对这个网站的结构有一些了解了,你现在可以进入入门这一主题开始你的OpenGL学习生涯吧
现在你应该对这个网站的结构有一些了解了,你现在可以进入入门这一开始你的OpenGL学习生涯吧