mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
Replace html with tags
This commit is contained in:
@@ -9,9 +9,10 @@ OpenGL规范严格规定了每个函数该如何执行,以及它们该如何
|
||||
实际的OpenGL库的开发者通常是显卡的生产商。每个你购买的显卡都会支持特定版本的OpenGL,通常是为一个系列的显卡专门开发的。当你使用苹果系统的时候,OpenGL库是由苹果自身维护的。在Linux下,有显卡生产商提供的OpenGL库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug。(快甩锅)
|
||||
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
由于大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这也是为什么总是建议你偶尔更新一下显卡驱动。
|
||||
</div>
|
||||
!!! Important
|
||||
|
||||
由于大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这也是为什么总是建议你偶尔更新一下显卡驱动。
|
||||
|
||||
|
||||
|
||||
Khronos公开主持所有版本的OpenGL规范书的制定。有兴趣的读者可以找到OpenGL3.3(我们将要提到的版本)的[规范书](https://www.opengl.org/registry/doc/glspec33.core.20100311.withchanges.pdf)。如果你想深入到OpenGL的细节(注意只描述函数的功能而不管实现),这是个很好的选择。该规范还提供一个强大的可以寻找到每个函数具体功能的参考。
|
||||
@@ -27,9 +28,8 @@ Khronos公开主持所有版本的OpenGL规范书的制定。有兴趣的读者
|
||||
现今更高版本的OpenGL已经发布(目前最新是4.5),你可能会问:为什么我们还要学习3.3?答案很简单,所有OpenGL的更高的版本都是在3.3的基础上,添加了额外的功能,并不更改核心架构。新版本只是引入了一些更有效率或更有用的方式去完成同样的功能。因此所有的概念和技术在现代OpenGL版本里都保持一致。当你的经验足够,你可以轻松使用来自更高版本OpenGL的新特性。
|
||||
|
||||
|
||||
<div style="border:solid #E1B3B3;border-radius:10px;background-color:#FFD2D2;margin:10px 10px 10px 0px;padding:10px">
|
||||
当使用新版本OpenGL的新特性时,只有新一代的显卡能够支持你的应用程序。这也是为什么大多数开发者基于较低版本的OpenGL编写程序,并有选择的启用新特性。
|
||||
</div>
|
||||
!!! Attention
|
||||
当使用新版本OpenGL的新特性时,只有新一代的显卡能够支持你的应用程序。这也是为什么大多数开发者基于较低版本的OpenGL编写程序,并有选择的启用新特性。
|
||||
|
||||
|
||||
在有些教程里你会发现像如下方式注明的新特性。
|
||||
@@ -75,11 +75,11 @@ OpenGL库是用C写的,同时也支持多种语言的派生,但是核心是
|
||||
};
|
||||
```
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
<span style="font-size:20px"><b>原始类型</b></span>
|
||||
<br/>
|
||||
使用OpenGL时,建议使用OpenGL定义的原始类型。比如使用float时我们加上前缀GL(因此写作GLfloat)。int,uint,char,bool等等类似。OpenGL定义了这些GL原始类型的平台无关的内存排列方式。而int等原始类型在不同平台上可能有不同的内存排列方式。使用GL原始类型可以保证你的程序在不同的平台上工作一致。
|
||||
</div>
|
||||
|
||||
!!! Important
|
||||
|
||||
**原始类型**
|
||||
使用OpenGL时,建议使用OpenGL定义的原始类型。比如使用float时我们加上前缀GL(因此写作GLfloat)。int,uint,char,bool等等类似。OpenGL定义了这些GL原始类型的平台无关的内存排列方式。而int等原始类型在不同平台上可能有不同的内存排列方式。使用GL原始类型可以保证你的程序在不同的平台上工作一致。
|
||||
|
||||
|
||||
当我们使用一个对象时,通常看起来像如下一样(把OpenGL上下文比作一个大的结构体):
|
||||
@@ -109,9 +109,9 @@ OpenGL库是用C写的,同时也支持多种语言的派生,但是核心是
|
||||
这一小片代码将会是以后使用OpenGl时常见的工作流。我们首先创建一个对象,然后用一个id保存它的引用(实际数据被储存在后台)。然后我们将对象绑定至上下文的目标位置(例子里窗口对象的目标位置被定义成GL\_WINDOW\_TARGET)。接下来我们设置窗口的选项,最后我们解绑这个对象,通过将目标位置的对象id设回0。我们设置的选项被保存在objectId代表的对象中,一旦我们重新绑定这个对象到GL\_WINDOW\_TARGET位置,这些选项就会重新生效。
|
||||
|
||||
|
||||
<div style="border:solid #E1B3B3;border-radius:10px;background-color:#FFD2D2;margin:10px 10px 10px 0px;padding:10px">
|
||||
目前提供的示例代码只是OpenGL如何操作的一个大致描述,通过教程你会遇到很多实际的例子。
|
||||
</div>
|
||||
!!! Attention
|
||||
|
||||
目前提供的示例代码只是OpenGL如何操作的一个大致描述,通过教程你会遇到很多实际的例子。
|
||||
|
||||
|
||||
使用对象的一个好事是我们在程序中可以定义不止一个对象,设置他们的选项,当我们需要进行一个操作的时候,只需要绑定预设了需要的状态的对象。比如,有作为3D模型数据(一栋房子或一个人物)容器对象的对象,任何时候我们想绘制其中一个3D模型的时候,只需绑定相应的含模型数据的对象(我们预先创建并设置好了它们的选项)。拥有数个这样的对象允许我们指定多个模型,在想画其中任何一个的时候,简单的将相应的对象绑定上去,不需要进行重复的设置选项的操作。
|
||||
|
@@ -17,15 +17,16 @@ GLFW是一个C库,专门针对OpenGL,提供了一些渲染物件所需的最
|
||||
GLFW可以从它们网站的[下载页](http://www.glfw.org/download.html)上获取。GLFW已经有针对Visual Studio 2012/2013的预编译的二进制版本和相应的头文件,但是为了完整性我们将从编译源代码开始,所以需要下载**源代码包**。
|
||||
|
||||
|
||||
<div style="border:solid #E1B3B3;border-radius:10px;background-color:#FFD2D2;margin:10px 10px 10px 0px;padding:10px">
|
||||
当你下载二进制版本时,请下载32位的版本而不是64位的除非你清楚你在做什么。64位版本被报告出现很多奇怪的问题。
|
||||
</div>
|
||||
!!! Attention
|
||||
|
||||
当你下载二进制版本时,请下载32位的版本而不是64位的除非你清楚你在做什么。64位版本被报告出现很多奇怪的问题。
|
||||
|
||||
|
||||
|
||||
一旦下载完了源码包,解压到某处。我们只关心里面的这些内容:
|
||||
|
||||
-编译生成的库
|
||||
-**include**文件夹
|
||||
- 编译生成的库
|
||||
- **include**文件夹
|
||||
|
||||
从源代码编译库可以保证生成的目标代码是针对你的操作系统和CPU的,而一个预编译的二进制代码并不保证总是适合。提供源代码的一个问题是不是每个人都用相同的IDE来编译,因而提供的工程文件可能和一些人的IDE不兼容。所以人们只能从.cpp和.h文件来自己建立工程,这是一项笨重的工作。因此诞生了一个叫做CMake的工具。
|
||||
|
||||
@@ -113,11 +114,10 @@ GLEW代表OpenGL Extension Wrangler Library,管理我们上面提到的繁琐
|
||||
我们使用GLEW的静态版本glew32s.lib(注意这里的's'),用如上的方式添加其库文件和头文件,最后在链接器的选项里加上glew32s.lib。注意GLFW3也是编译成了一个静态库。
|
||||
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
静态链接是指编译时就将库代码里的内容合并进二进制文件。优点就是你不需要再放额外的文件,只需要发布你最终的二进制代码文件。缺点就是你的程序会变得更大,另外当库有升级版本时,你必须重新进行编译。
|
||||
<br/>
|
||||
动态链接是指一个库通过.dll或.so的方式存在,它的代码与你的二进制文件的代码是分离的。优点是使你的程序大小变小并且更容易升级,缺点是你发布时必须带上这些dll。
|
||||
</div>
|
||||
!!! Important
|
||||
|
||||
静态链接是指编译时就将库代码里的内容合并进二进制文件。优点就是你不需要再放额外的文件,只需要发布你最终的二进制代码文件。缺点就是你的程序会变得更大,另外当库有升级版本时,你必须重新进行编译。
|
||||
动态链接是指一个库通过.dll或.so的方式存在,它的代码与你的二进制文件的代码是分离的。优点是使你的程序大小变小并且更容易升级,缺点是你发布时必须带上这些dll。
|
||||
|
||||
|
||||
如果你希望静态链接GLEW,必须在包含GLEW头文件之前定义预编译宏GLEW\_STATIC:
|
||||
@@ -130,9 +130,10 @@ GLEW代表OpenGL Extension Wrangler Library,管理我们上面提到的繁琐
|
||||
如果你希望动态链接那么不要定义这个宏。几乎动态链接的话你需要拷贝一份dll文件到你的应用程序同目录。
|
||||
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
对于Linux用户建议使用这个命令行-lGLEW -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi。没有正确链接相应的库会产生<i>undefined reference</i> errors.(未定义的引用)
|
||||
</div>
|
||||
|
||||
!!! Important
|
||||
|
||||
对于Linux用户建议使用这个命令行`-lGLEW -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi`。没有正确链接相应的库会产生*undefined reference* errors.(未定义的引用)
|
||||
|
||||
|
||||
我们现在成功编译了GLFW和GLEW库,我们将进入[下一节](http://www.learnopengl.com/#!Getting-Started/Hello-Window)(第一章第三节)去使用GLFW和GLEW来设置OpenGL上下文并创建窗口。记住确保你的头文件和库文件的目录设置正确,以及链接器里引用的库文件名正确。如果仍然遇到错误,请参考额外资源中的例子。
|
||||
|
@@ -11,8 +11,9 @@
|
||||
// GLFW
|
||||
#include <GLFW/glfw3.h>
|
||||
```
|
||||
!!! Attention
|
||||
|
||||
> 请确认在包含GLFW的头文件之前包含了GLEW的头文件。在包含glew.h头文件时会引入许多OpenGL必要的头文件(例如GL/gl.h),所以#include < GL/glew.h\>应放在引入其他头文件的代码之前。
|
||||
请确认在包含GLFW的头文件之前包含了GLEW的头文件。在包含glew.h头文件时会引入许多OpenGL必要的头文件(例如GL/gl.h),所以#include <GL/glew.h>应放在引入其他头文件的代码之前。
|
||||
|
||||
接下来我们创建main函数,并做一些初始化GLFW的操作:
|
||||
|
||||
@@ -36,7 +37,10 @@ int main()
|
||||
```c++
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
```
|
||||
>请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会崩溃或者出现不可预知的错误。可以通过运行glew附带的glxinfo程序或者其他的工具(例如[OpenGL Extension Viewer ](http://download.cnet.com/OpenGL-Extensions-Viewer/3000-18487_4-34442.html)来查看的OpenGL版本。如果您的OpenGL版本低于3.3请更新您的驱动程序或者有必要的话更新您的设备。
|
||||
|
||||
!!! Important
|
||||
|
||||
请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会崩溃或者出现不可预知的错误。可以通过运行glew附带的glxinfo程序或者其他的工具(例如[OpenGL Extension Viewer ](http://download.cnet.com/OpenGL-Extensions-Viewer/3000-18487_4-34442.html)来查看的OpenGL版本。如果您的OpenGL版本低于3.3请更新您的驱动程序或者有必要的话更新您的设备。
|
||||
|
||||
接下来我们创建一个窗口对象,这个窗口对象中具有关和窗口相关的许多数据,而且会被GLFW的其他函数频繁地用到。
|
||||
|
||||
@@ -77,7 +81,9 @@ glViewport(0, 0, 800, 600);
|
||||
```
|
||||
前两个参数设置窗口左下角的位置。第三个和第四个参数设置渲染窗口的宽度和高度,我们设置成与GLFW的窗口的宽高大小,我们也可以将这个值设置成比窗口小的数值,然后所有的OpenGL渲染将会显示在一个较小的区域。
|
||||
|
||||
>OpenGL 使用glViewport定义的位置和宽高进行位置坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(0.5,0.5)有可能被转换为屏幕中的坐标(200,450)。注意,OpenGL只会把-1到1之间的坐标转换为屏幕坐标,因此在此例中(-1,1)转换为屏幕坐标是(0,600)。
|
||||
!!!Important
|
||||
|
||||
OpenGL 使用glViewport定义的位置和宽高进行位置坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(0.5,0.5)有可能被转换为屏幕中的坐标(200,450)。注意,OpenGL只会把-1到1之间的坐标转换为屏幕坐标,因此在此例中(-1,1)转换为屏幕坐标是(0,600)。
|
||||
|
||||
###准备好你的引擎
|
||||
|
||||
@@ -95,9 +101,12 @@ while(!glfwWindowShouldClose(window))
|
||||
* glfwPollEvents函数检查有没有触发什么事件(比如键盘有按钮按下、鼠标移动等)然后调用对应的回调函数(我们可以手动设置这些回调函数)。我们一般在游戏循环的一开始就检查事件。
|
||||
* 调用glfwSwapBuffers会交换缓冲区(储存着GLFW窗口每一个像素颜色的缓冲区)
|
||||
|
||||
>双缓冲区
|
||||
|
||||
>应用程序使用单缓冲区绘图可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。因为这些不是在瞬间显示给用户,而是通过一步一步地计算结果绘制的,这可能会花费一些时间。为了规避这些问题,我们应用双缓冲区渲染窗口应用程序。前面的缓冲区保存着计算后可显示给用户的图像,被显示到屏幕上;所有的的渲染命令被传递到后台的缓冲区进行计算。当所有的渲染命令执行结束后,我们交换前台缓冲和后台缓冲,这样图像就立即呈显出来,之后清空缓冲区。
|
||||
!!! Important
|
||||
|
||||
**双缓冲区**
|
||||
|
||||
应用程序使用单缓冲区绘图可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。因为这些不是在瞬间显示给用户,而是通过一步一步地计算结果绘制的,这可能会花费一些时间。为了规避这些问题,我们应用双缓冲区渲染窗口应用程序。前面的缓冲区保存着计算后可显示给用户的图像,被显示到屏幕上;所有的的渲染命令被传递到后台的缓冲区进行计算。当所有的渲染命令执行结束后,我们交换前台缓冲和后台缓冲,这样图像就立即呈显出来,之后清空缓冲区。
|
||||
|
||||
###最后一件事
|
||||
|
||||
@@ -167,7 +176,9 @@ glClear(GL_COLOR_BUFFER_BIT);
|
||||
```
|
||||
注意,除了glClear之外,我们还要调用glClearColor来设置要清空缓冲的颜色。当调用glClear函数之后,整个指定清空的缓冲区都被填充为glClearColor所设置的颜色。在这里,我们将屏幕设置为了类似黑板的深蓝绿色。
|
||||
|
||||
>你应该能够想起来我们在 [OpenGL]() 教程的内容, glClearColor函数是一个状态设置函数,而glClear函数则是一个状态应用的函数。
|
||||
!!! Important
|
||||
|
||||
你应该能够想起来我们在 [OpenGL]() 教程的内容, glClearColor函数是一个状态设置函数,而glClear函数则是一个状态应用的函数。
|
||||
|
||||

|
||||
|
||||
|
@@ -4,9 +4,9 @@
|
||||
|
||||
在OpenGL中任何事物都在3D空间中,但是屏幕和窗口是一个2D像素阵列,所以OpenGL的大部分工作都是关于如何把3D坐标转变为适应你的屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的**图形输送管道**(pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形输送管道可以被划分为两个主要部分:第一个部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。这个教程里,我们会简单地讨论一下图形输送管道,以及如何使用它创建一些像素,这对我们来说是有好处的。
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
2D坐标和像素也是不同的,2D坐标是在2D空间中的一个点的非常精确的表达,2D像素是这个点的近似,它受到你的屏幕/窗口解析度的限制。
|
||||
</div>
|
||||
!!! Important
|
||||
|
||||
2D坐标和像素也是不同的,2D坐标是在2D空间中的一个点的非常精确的表达,2D像素是这个点的近似,它受到你的屏幕/窗口解析度的限制。
|
||||
|
||||
图形输送管道接收一组3D坐标,然后把它们转变为你屏幕上的有色2D像素。图形输送管道可以被划分为几个阶段,每个阶段需要把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们有一个特定的函数),它们能简单地并行执行。由于它们的并行执行的特征,当今大多数显卡都有成千上万的小处理核心,在GPU上为每一个(输送管道的)阶段运行各自的小程序,从而在图形输送管道中快速处理你的数据。这些小程序叫做**着色器**(Shader)。
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
①译注:当我们谈论一个“位置”的时候,它代表在一个“空间”中所处地点的这个特殊属性;同时“空间”代表着任何一种坐标系,比如x、y、z三维坐标系,x、y二维坐标系,或者一条直线上的x和y的线性关系,只不过二维坐标系是一个扁扁的平面空间,而一条直线是一个很瘦的长长的空间。
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去提示你希望这些数据所表示的是什么类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做**基本图形**(Primitives),任何一个绘制命令的调用都必须把基本图形类型传递给OpenGL。这是其中的几个:`GL_POINTS`、`GL_TRIANGLES`、`GL_LINE_STRIP`。
|
||||
</div>
|
||||
!!! Important
|
||||
|
||||
为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去提示你希望这些数据所表示的是什么类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做**基本图形**(Primitives),任何一个绘制命令的调用都必须把基本图形类型传递给OpenGL。这是其中的几个:`GL_POINTS`、`GL_TRIANGLES`、`GL_LINE_STRIP`。
|
||||
|
||||
输送管道的第一个部分是**顶点着色器**(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
|
||||
②译注:Fragment通常译为片段,但从根本上来说它就是带有一些额外信息的像素,由于带有额外信息,OpenGL就没有给它取名叫“像素”,所以“片段”的中文译法比较不准确听起来就像一个fragment可能包含多个像素一样,实际上一个fragment只包含一个像素,现在你只要简单记住它是像素就行了。后面你会知道除了像素信息以外它还包含了什么。
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
OpenGL中的一个fragment是OpenGL渲染一个独立像素所需的所有数据。
|
||||
</div>
|
||||
!!! Important
|
||||
|
||||
OpenGL中的一个fragment是OpenGL渲染一个独立像素所需的所有数据。
|
||||
|
||||
**片段着色器**的主要目的是计算一个像素的最终颜色,这也是OpenGL高级效果产生的地方。通常,片段着色器包含用来计算像素最终颜色的3D场景的一些数据(比如光照、阴影、光的颜色等等)。
|
||||
|
||||
@@ -68,17 +68,17 @@ GLfloat vertices[] = {
|
||||
|
||||
③译注:通常深度可以理解为z坐标,它代表一个像素在空间中和你的距离,如果离你远就可能被别的像素遮挡,你就看不到它了,它会被丢弃,以节省资源。
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:10px;background-color:#D8F5D8;margin:10px 10px 10px 0px;padding:10px">
|
||||
**标准化设备坐标**
|
||||
!!! Important
|
||||
|
||||
一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是**标准化设备坐标**了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):
|
||||
**标准化设备坐标**
|
||||
|
||||

|
||||
一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是**标准化设备坐标**了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):
|
||||
|
||||
与通常的屏幕坐标不同,y轴正方向上的点和(0, 0)坐标是这个图像的中心,而不是左上角。最后你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。
|
||||

|
||||
|
||||
你的标准化设备坐标接着会变换为**屏幕空间坐标**(screen-space coordinates),这是使用你通过`glViewport`函数提供的数据,进行**视口变换**(viewport transform)完成的。最后的屏幕空间坐标被变换为像素输入到片段着色器。
|
||||
</div>
|
||||
与通常的屏幕坐标不同,y轴正方向上的点和(0, 0)坐标是这个图像的中心,而不是左上角。最后你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。
|
||||
|
||||
你的标准化设备坐标接着会变换为**屏幕空间坐标**(Screen-space Coordinates),这是使用你通过`glViewport`函数提供的数据,进行**视口变换**(Viewport Transform)完成的。最后的屏幕空间坐标被变换为像素输入到片段着色器。
|
||||
|
||||
有了这样的顶点数据,我们会把它作为输入发送给图形输送管道的第一个处理阶段:顶点着色器。它会在GPU上创建储存空间用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定如何发送给显卡。顶点着色器接着会处理我们告诉它要处理内存中的顶点的数量。
|
||||
|
||||
|
@@ -52,6 +52,8 @@
|
||||
因为将所有可见的坐标都放置在-1.0到1.0的范围内不是很直观,我们会指定自己的坐标集的方式并将它转换回规则化设备坐标系就像OpenGL期望它做的那样。
|
||||
为了将顶点坐标从观察空间转换到裁剪空间,我们定义一个投影矩阵,它指定了坐标的范围,例如,每个维度都是从-1000到1000。投影矩阵接着会将在它指定的范围内的坐标转换到规则化设备坐标系中(-1.0,1.0)。所有在范围外的坐标在-1.0到1.0之间都不会被绘制出来且会被裁剪。在投影矩阵所指定的范围内,坐标(1250,500,750)将是不可见的,这是由于它的x坐标超出了范围,随后被转化为在规则化设备坐标中坐标值大于1.0的值并且被裁剪掉。
|
||||
|
||||
!!! Important
|
||||
|
||||
注意那些如果只是图元的一部分例如三角形,超出了裁剪区域,则OpenGL会重新构建三角形以使一个或多个三角形能适应在裁剪范围内。
|
||||
|
||||
由投影矩阵创建的观察区域被称为平截头体,且每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将坐标转化到指定的规则化设备坐标系,它可以很容易被映射到2维观察空间的坐标系的整个过程称为投影,因为它投影矩阵能将3维坐标投影到容易映射的规则化设备坐标系中。
|
||||
@@ -78,9 +80,8 @@
|
||||

|
||||
正如你看到的那样,由于透视的原因,线在很远的地方看起来会相交。这个投影矩阵将给定的平截头体范围映射到裁剪空间,但每个顶点坐标仍然产生了w值,顶点如果离观察者越远,则w分量越大。被转换到裁剪控件的坐标都会在-w到w的范围之间(任何处于这个范围的对象都会被裁剪)。OpenGL需要可见的坐标都落在-1.0到1.0范围内作为最后的顶点着色器输出,因此一旦坐标在裁剪空间内,透视划分就会被应用到裁剪空间坐标内:
|
||||
|
||||
<p>
|
||||
out = \begin{pmatrix} x /w \\ y / w \\ z / w \end{pmatrix}
|
||||
</p>
|
||||

|
||||
|
||||
每个顶点坐标的分量都会除以它的w分量,得到一个距离观察者的较小的顶点坐标。这是另一个w分量很重要的原因,因为它帮助我们进行透射投影。最后的结果坐标就是处于规则化设备空间内的。如果你对研究正视投影矩阵和透视投影矩阵是如何计算的很感兴趣(且不会对数学感到恐惧)我推荐这篇由Songho写的[文章](http://www.songho.ca/opengl/gl_projectionmatrix.html)。
|
||||
一个透视投影矩阵可以在GLM中被创建,如下:
|
||||
|
||||
@@ -91,25 +92,29 @@ glm::perspective所做的其实就是再次创建了一个定义了可视空间
|
||||
|
||||
它的第一个参数定义了fov的值,代表了视界范围,且设置了视图空间有多大。对于一个真实的视图,它的值经常设置为45.0,但想要看到更多结果你可以设置一个更大的值。第二个参数设置了宽高比,由视图的高除以宽。第三和第四个参数设置了平截头体的近和远平面。我们经常设置近距离为0.1而远距离设为100.0。所有在近平面和远平面的顶点且处于平截头体内的顶点都会被渲染。
|
||||
|
||||
> 当你把透视矩阵的near值设置太大时(如10.0),OpenGL会将靠近摄像机的坐标都裁剪掉(在0.0和10.0之间),这提供了一个相似的视觉效果就如视频游戏中如果你移动得太靠近某个对象时你会把它看穿那样。
|
||||
!!! Important
|
||||
|
||||
当你把透视矩阵的near值设置太大时(如10.0),OpenGL会将靠近摄像机的坐标都裁剪掉(在0.0和10.0之间),这提供了一个相似的视觉效果就如视频游戏中如果你移动得太靠近某个对象时你会把它看穿那样。
|
||||
|
||||
当使用正视投影时,每一个顶点坐标都会直接映射到裁剪空间中而不经过任何透视划分(它仍然有进行透视划分,只是w分量没有被操作(它保持为1)因此没有起作用)。因为正视投影没有使用透视投影,远处的对象不会显得小以产生神奇的视觉输出。由于这个原因,正视投影主要用于二维渲染以及一些建筑或工程的应用这些我们不需要使用投影来变形顶点。某些应用如Blender是用来进行三维建模的,有时在建模时会使用正视投影,因为它更准确地藐视了每个对象的各个方面。下面你能够看到在Blender里面使用两种投影方式的对比:
|
||||
|
||||

|
||||
|
||||
你可以看到使用透视投影的话,远处的顶点看起来比较小,而在正视投影中每个顶点距离观察者的距离都是一样的。
|
||||
|
||||
组合
|
||||
--
|
||||
我们为上述的每一个步骤都创建了一个转换矩阵:模型矩阵、视图矩阵和投影矩阵。一个顶点的坐标将会被转换到裁剪坐标,如下:
|
||||
|
||||
<p>
|
||||
V_{clip} =M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}
|
||||
</p>
|
||||

|
||||
|
||||
注意每个矩阵被运算的顺序是相反的(记住我们需要从右往左乘上每个矩阵)。最后的顶点应该被赋予顶点着色器中的gl_Position 且OpenGL 将会自动进行透视划分和裁剪。
|
||||
|
||||
> 然后呢?
|
||||
> 顶点着色器的输出需要所有的顶点都在裁剪空间内,而这是我们的转换矩阵所做的。OpenGL然后在裁剪空间中执行透视划分从而将它们转换到规则化设备坐标。OpenGL
|
||||
> 然后会使用 glViewPort内部的参数来将规则化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中屏幕是800 *600)。这个过程成为视口转换。
|
||||
!!! Important
|
||||
|
||||
**然后呢?**
|
||||
|
||||
顶点着色器的输出需要所有的顶点都在裁剪空间内,而这是我们的转换矩阵所做的。OpenGL然后在裁剪空间中执行透视划分从而将它们转换到规则化设备坐标。OpenGL然后会使用 glViewPort内部的参数来将规则化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中屏幕是800 *600)。这个过程成为视口转换。
|
||||
|
||||
这一章的主题比较难理解所以如果你仍然不确定每个空间的作用的话,你也不必太担心。接下来你会看到我们是怎样好好运用这些坐标空间的并且会有足够的展示例子在接下来的教程中。
|
||||
|
||||
@@ -129,18 +134,20 @@ V_{clip} =M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}
|
||||
|
||||
这就是视图空间所做的,我们移动整个场景正如我们相反地想让摄像机所做的那样。因为我们想要往后移动且OpenGL是一个右手坐标系所以我们沿着z轴的负方向移动。我们实现这个,通过将场景沿着z轴正方向平移。这个我们的感觉就是我们在往后移动。
|
||||
|
||||
!!! Important
|
||||
|
||||
**右手坐标系**
|
||||
按照约定,OpenGL是一个右手坐标系。最基本的就是说正x轴在你的右手边,正y轴往上而正z轴是往后的。想象你的屏幕处于三个轴的中心且正z轴穿过你的屏幕朝向你。坐标系画起来如下:
|
||||
|
||||

|
||||
|
||||
为了理解为什么成为右手坐标系,按如下的步骤做:
|
||||
|
||||
- 张开你的右手使正y轴沿着你的手往上。
|
||||
- 使你的大拇指往右。
|
||||
- 使你的食指往上。
|
||||
- 向下90度弯曲你的中指。
|
||||
|
||||
> **右手坐标系**
|
||||
> ----- 按照约定,OpenGL是一个右手坐标系。最基本的就是说正x轴在你的右手边,正y轴往上而正z轴是往后的。想象你的屏幕处于三个轴的中心且正z轴穿过你的屏幕朝向你。坐标系画起来如下:
|
||||
> 
|
||||
>
|
||||
> 为了理解为什么成为右手坐标系,按如下的步骤做:
|
||||
>
|
||||
> - 张开你的右手使正y轴沿着你的手往上。
|
||||
> - 使你的大拇指往右。
|
||||
> - 使你的食指往上。
|
||||
> - 向下90度弯曲你的中指。
|
||||
>
|
||||
如果你都正确地做了,那么你的大拇指朝着正x轴方向,食指朝着正y轴方向,中指朝着正z轴方向。如果你用左手来做这些动作,你会发现z轴的方向是相反的。这就是有名的左手坐标系,被DirectX广泛地使用。注意在规则化设备坐标系中OpenGL使用的是左手坐标系(投影矩阵转换了使用右手或左手的习惯)。
|
||||
|
||||
接下来我们将会讨论如何利用更多细节来在场景中移动。现在视图矩阵看起来像这样的:
|
||||
@@ -154,7 +161,9 @@ V_{clip} =M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}
|
||||
glm::mat4 projection;
|
||||
projection = glm::perspective(45.0f, screenWidth / screenHeight, 0.1f, 100.0f);
|
||||
|
||||
> 再重复一次,在glm指定角度的时候要注意。这里我们将参数fov设置为45度,但有些GLM的实现是将fov当成弧度,在这种情况你需要使用glm::radians(45.0)来设置。
|
||||
!!! Attention
|
||||
|
||||
再重复一次,在glm指定角度的时候要注意。这里我们将参数fov设置为45度,但有些GLM的实现是将fov当成弧度,在这种情况你需要使用glm::radians(45.0)来设置。
|
||||
|
||||
既然我们创建了转换矩阵,我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个单位转换矩阵然后将它乘以顶点坐标:
|
||||
|
||||
|
BIN
img/coordinate_system_1.png
Normal file
BIN
img/coordinate_system_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
img/coordinate_system_2.png
Normal file
BIN
img/coordinate_system_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Reference in New Issue
Block a user