From 6aa348d79898b5df4011659eb092465a16b2eacd Mon Sep 17 00:00:00 2001 From: Meow J Date: Fri, 8 Jul 2016 04:12:15 +0800 Subject: [PATCH] 01-03 --- docs/01 Getting started/03 Hello Window.md | 85 +++++++++++---------- docs/img/01/03/hellowindow.png | Bin 0 -> 7827 bytes docs/img/01/03/hellowindow2.png | Bin 0 -> 7984 bytes 3 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 docs/img/01/03/hellowindow.png create mode 100644 docs/img/01/03/hellowindow2.png diff --git a/docs/01 Getting started/03 Hello Window.md b/docs/01 Getting started/03 Hello Window.md index 5bd6415..f408977 100644 --- a/docs/01 Getting started/03 Hello Window.md +++ b/docs/01 Getting started/03 Hello Window.md @@ -6,8 +6,7 @@ 翻译 | Geequlim 校对 | Geequlim - -上一节中我们获取并编译了GLFW和GLEW这两个开源库,现在我们就可以使用它们来创建一个OpenGL绘图窗口了。首先,新建一个`.cpp`文件,然后把下面的代码粘贴到该文件的最前面。注意,之所以定义`GLEW_STATIC`宏,是因为我们使用GLEW的静态链接库。 +让我们试试能不能让GLFW正常工作。首先,新建一个`.cpp`文件,然后把下面的代码粘贴到该文件的最前面。注意,之所以定义`GLEW_STATIC`宏,是因为我们使用的是GLEW静态的链接库。 ```c++ // GLEW @@ -19,9 +18,9 @@ !!! Attention - 请确认在包含GLFW的头文件之前包含了GLEW的头文件。在包含glew.h头文件时会引入许多OpenGL必要的头文件(例如GL/gl.h),所以#include 应放在引入其他头文件的代码之前。 + 请确认在包含GLFW的头文件之前包含了GLEW的头文件。在包含glew.h头文件时会引入许多OpenGL必要的头文件(例如`GL/gl.h`),所以你需要在包含其它依赖于OpenGL的头文件之前先包含GLEW。 -接下来我们创建`main`函数,并做一些初始化GLFW的操作: +接下来我们创建main函数,在这个函数中我们将会实例化GLFW窗口: ```c++ int main() @@ -31,14 +30,14 @@ int main() glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - + return 0; } ``` -首先我们在`main`函数中调用`glfwInit`函数来初始化GLFW,然后我们可以使用`glfwWindowHint`函数来配置GLFW。`glfwWindowHint`函数的第一个参数表示我们要进行什么样的配置,我们可以选择大量以`GLFW_`开头的枚举值;第二个参数接受一个整形,用来设置这个配置的值。该函数的所有的选项以及对应的值都可以在 [GLFW's window handling](http://www.glfw.org/docs/latest/window.html#window_hints) 这篇文档中找到。如果你现在编译你的cpp文件会得到大量的连接错误,这是因为你还需要进一步设置GLFW。 +首先,我们在main函数中调用glfwInit函数来初始化GLFW,然后我们可以使用glfwWindowHint函数来配置GLFW。glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以`GLFW_`开头的枚举值中选择;第二个参数接受一个整形,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 [GLFW's window handling](http://www.glfw.org/docs/latest/window.html#window_hints) 这篇文档中找到。如果你现在编译你的cpp文件会得到大量的 *undefined reference* (未定义的引用)错误,也就是说你并未顺利地链接GLFW库。 -由于本站的教程都是基于OpenGL3.3以后的版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3,这样GLFW会在创建OpenGL上下文时做出适当的调整。这也可以确保用户在没有适当的OpenGL版本支持的情况下无法运行。在这里我们告诉GLFW想要的OpenGL版本号是3.3,并且不允许用户调整窗口的大小。我们明确地告诉GLFW我们想要使用核心模式(Core-profile),这将导致我们无法使用那些已经废弃的API,而这不正是一个很好的提醒吗?当我们不小心用了旧功能时报错,就能避免使用一些被废弃的用法了。如果你使用的是Mac OSX系统你还需要加下面这行代码这些配置才能起作用: +由于本站的教程都是基于OpenGL 3.3版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3,这样GLFW会在创建OpenGL上下文时做出适当的调整。这也可以确保用户在没有适当的OpenGL版本支持的情况下无法运行。我们将主版本号(Major)和次版本号(Minor)都设为3。我们同样明确告诉GLFW我们使用的是核心模式(Core-profile),并且不允许用户调整窗口的大小。在明确告诉GLFW使用核心模式的情况下,使用旧版函数将会导致**invalid operation**(无效操作)的错误,而这不正是一个很好的提醒吗?在我们不小心用了旧函数时报错,就能避免使用一些被废弃的用法了。如果使用的是Mac OS X系统,你还需要加下面这行代码到你的初始化代码中这些配置才能起作用: ```c++ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); @@ -46,9 +45,9 @@ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); !!! Important - 请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会崩溃或者出现不可预知的错误。可以通过运行glew附带的glxinfo程序或者其他的工具(例如[OpenGL Extension Viewer](http://download.cnet.com/OpenGL-Extensions-Viewer/3000-18487_4-34442.html)来查看你的OpenGL版本。如果你的OpenGL版本低于3.3请更新你的驱动程序或者有必要的话更新设备。 + 请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会崩溃或者出现不可预知的错误。如果想要查看OpenGL版本的话,在Linux上运行**glxinfo**,或者在Windows上使用其它的工具(例如[OpenGL Extension Viewer](http://download.cnet.com/OpenGL-Extensions-Viewer/3000-18487_4-34442.html))。如果你的OpenGL版本低于3.3,检查一下显卡是否支持OpenGL 3.3+(不支持的话你的显卡真的太老了),并更新你的驱动程序,有必要的话请更新显卡。 -接下来我们创建一个窗口对象,这个窗口对象中具有和窗口相关的许多数据,而且会被GLFW的其他函数频繁地用到。 +接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。 ```c++ GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); @@ -61,7 +60,7 @@ if (window == nullptr) glfwMakeContextCurrent(window); ``` -`glfwCreateWindow`函数需要窗口的宽和高作为它的前两个参数;第三个参数表示只这个窗口的名称(标题),这里我们使用**"LearnOpenGL"**,当然你也可以使用你喜欢的名称;最后两个参数我们暂时忽略,先置为空指针就行。它的返回值`GLFWwindow`对象的指针会在其他的GLFW操作中使用到。创建完窗口我们就可以通知GLFW给我们的窗口在当前的线程中创建我们等待已久的OpenGL上下文了。 +glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数;第三个参数表示这个窗口的名称(标题),这里我们使用`"LearnOpenGL"`,当然你也可以使用你喜欢的名称;最后两个参数我们暂时忽略,先设置为空指针就行。它的返回值GLFWwindow对象的指针会在其他的GLFW操作中使用到。创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。 ## GLEW @@ -76,25 +75,30 @@ if (glewInit() != GLEW_OK) } ``` -请注意,我们在初始化GLEW之前设置`glewExperimental`变量的值为`GL_TRUE`,这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术,如果把它设置为`GL_FALSE`的话可能会在使用OpenGL的核心模式(Core-profile)时出现一些问题。 +请注意,我们在初始化GLEW之前设置glewExperimental变量的值为`GL_TRUE`,这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术,如果把它设置为`GL_FALSE`的话可能会在使用OpenGL的核心模式时出现一些问题。 ## 视口(Viewport) -在我们绘制之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道要显示数据的窗口坐标。我们可以通过调用`glViewport`函数来设置这些维度: +在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样相对于窗口大小显示数据和坐标。我们可以通过调用glViewport函数来设置窗口的**维度**(Dimension): ```c++ -glViewport(0, 0, 800, 600); +int width, height; +glfwGetFramebufferSize(window, &width, &height); + +glViewport(0, 0, width, height); ``` -前两个参数设置窗口左下角的位置。第三个和第四个参数设置渲染窗口的宽度和高度,我们设置成与GLFW的窗口的宽高大小,我们也可以将这个值设置成比窗口小的数值,然后所有的OpenGL渲染将会显示在一个较小的区域。 +glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素),这里我们是直接从GLFW中获取的。我们从GLFW中获取视口的维度而不设置为800*600是为了让它在高DPI的屏幕上(比如说Apple的视网膜显示屏)也能[正常工作](http://www.glfw.org/docs/latest/window.html#window_size)。 + +我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。 !!! Important - OpenGL使用`glViewport`定义的位置和宽高进行位置坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(0.5,0.5)有可能被转换为屏幕中的坐标(200,450)。注意,OpenGL只会把-1到1之间的坐标转换为屏幕坐标,因此在此例中(-1,1)转换为屏幕坐标是(0,600)。 + OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。 # 准备好你的引擎 -我们可不希望只绘制一个图像之后我们的应用程序就关闭窗口并立即退出。我们希望程序在我们明确地关闭它之前一直保持运行状态并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为游戏循环(Game Loop),这样我们的程序就能在我们让GLFW退出前保持运行了。下面几行的代码就实现了一个简单的游戏循环: +我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们明确地关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为游戏循环(Game Loop),它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的游戏循环: ```c++ while(!glfwWindowShouldClose(window)) @@ -104,42 +108,42 @@ while(!glfwWindowShouldClose(window)) } ``` -- `glfwWindowShouldClose`函数在我们每次循环的开始前检查一次GLFW是否准备好要退出,如果是这样的话该函数返回true然后游戏循环便结束了,之后为我们就可以关闭应用程序了。 -- `glfwPollEvents`函数检查有没有触发什么事件(比如键盘有按钮按下、鼠标移动等)然后调用对应的回调函数(我们可以手动设置这些回调函数)。我们一般在游戏循环的一开始就检查事件。 -- 调用`glfwSwapBuffers`会交换缓冲区(储存着GLFW窗口每一个像素颜色的缓冲区) - +- glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回`true`然后游戏循环便结束了,之后为我们就可以关闭应用程序了。 +- glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等),然后调用对应的回调函数(可以通过回调方法手动设置)。我们一般在游戏循环的开始调用事件处理函数。 +- glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲区),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。 !!! Important - **双缓冲区(Double buffer)** + **双缓冲区(Double Buffer)** - 应用程序使用单缓冲区绘图可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步地计算结果绘制的,这可能会花费一些时间。为了规避这些问题,我们应用双缓冲区渲染窗口应用程序。前面的缓冲区保存着计算后可显示给用户的图像,被显示到屏幕上;所有的的渲染命令被传递到后台的缓冲区进行计算。当所有的渲染命令执行结束后,我们交换前台缓冲和后台缓冲,这样图像就立即呈显出来,之后清空缓冲区。 + 应用程序使用单缓冲区绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲区渲染窗口应用程序。**前**缓冲区保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在**后**缓冲上绘制。当所有的渲染指令执行完毕后,我们**交换**(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。 ## 最后一件事 -当游戏循环结束后我们需要释放之前的操作分配的资源,我们可以在main函数的最后调用`glfwTerminate`函数来释放GLFW分配的内存。 +当游戏循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来释放GLFW分配的内存。 ```c++ glfwTerminate(); return 0; ``` -这样便能清空GLFW分配的内存然后正确地退出应用程序。现在你可以尝试编译并运行你的应用程序了,你将会看到如下的一个黑色窗口: +这样便能清理所有的资源并正确地退出应用程序。现在你可以尝试编译并运行你的应用程序了,如果没做错的话,你将会看到如下的输出: -![](http://learnopengl.com/img/getting-started/hellowindow.png) +![](../img/01/03/hellowindow.png) -如果你没有编译通过或者有什么问题的话,首先请检查你程序的的链接选项是否正确 -。然后对比本教程的代码,检查你的代码是不是哪里写错了,你也可以[点击这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow)获取我的完整代码。 +如果你看见了一个非常无聊的黑色窗口,那么就对了!如果你没得到正确的结果,或者你不知道怎么把所有东西放到一起,请到[这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow)参考源代码。 + +如果程序编译有问题,请先检查连接器选项是否正确,IDE中是否导入了正确的目录(前面教程解释过)。并且请确认你的代码是否正确,直接对照上面提供的源代码就行了。如果还是有问题,欢迎评论,我或者其他人可能会帮助你的。 ## 输入 -我们同样也希望能够在GLFW中实现一些键盘控制,这是通过设置GLFW的**回调函数(Callback Function)**来实现的。回调函数事实上是一个函数指针,当我们为GLFW设置回调函数后,GLWF会在恰当的时候调用它。**按键回调(KeyCallback)**是众多回调函数中的一种,当我们为GLFW设置按键回调之后,GLFW会在用户有键盘交互时调用它。该回调函数的原型如下所示: +我们同样也希望能够在GLFW中实现一些键盘控制,这可以通过使用GLFW的回调函数(Callback Function)来完成。回调函数事实上是一个函数指针,当我们设置好后,GLWF会在合适的时候调用它。**按键回调**(KeyCallback)是众多回调函数中的一种。当我们设置了按键回调之后,GLFW会在用户有键盘交互时调用它。该回调函数的原型如下所示: ```c++ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); ``` -按键回调函数接受一个`GLFWwindow`指针作为它的第一个参数;第二个整形参数用来表示事件的按键;第三个整形参数描述用户是否有第二个键按下或释放;第四个整形参数表示事件类型,如按下或释放;最后一个参数是表示是否有Ctrl、Shift、Alt、Super等按钮的操作。GLFW会在恰当的时候调用它,并为各个参数传入适当的值。 +按键回调函数接受一个GLFWwindow指针作为它的第一个参数;第二个整形参数用来表示按下的按键;`action`参数表示这个按键是被按下还是释放;最后一个整形参数表示是否有Ctrl、Shift、Alt、Super等按钮的操作。GLFW会在合适的时候调用它,并为各个参数传入适当的值。 ```c++ @@ -152,20 +156,20 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod } ``` -在这个`key_callback`函数中,它检测键盘是否按下了Escape键。如果键的确按下了(不释放),我们使用`glfwSetwindowShouldClose`函数设定`WindowShouldClose`属性为true从而关闭GLFW。main函数的while循环下一次的检测将失败并且程序关闭。 +在我们(新创建的)key_callback函数中,我们检测了键盘是否按下了Escape键。如果键的确按下了(不释放),我们使用glfwSetwindowShouldClose函数设定`WindowShouldClose`属性为`true`从而关闭GLFW。main函数的`while`循环下一次的检测将为失败,程序就关闭了。 -最后一件事就是通过GLFW使用适合的回调来注册我们的函数,代码是这样的: +最后一件事就是通过GLFW注册我们的函数至合适的回调,代码是这样的: ```c++ glfwSetKeyCallback(window, key_callback); ``` -除了按键回调函数之外,我们还能为GLFW注册其他的回调函数。例如,我们可以注册一个函数来处理窗口尺寸变化、处理一些错误信息等。我们可以在创建窗口之后到开始游戏循环之前注册各种回调函数。 +除了按键回调函数之外,我们还能我们自己的函数注册其它的回调。例如,我们可以注册一个回调函数来处理窗口尺寸变化、处理一些错误信息等。我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。 ## 渲染 -我们要把所有的渲染(Rendering)操作放到游戏循环中,因为我们想让这些渲染操作在每次游戏循环迭代的时候都能被执行。我们将做如下的操作: +我们要把所有的渲染(Rendering)操作放到游戏循环中,因为我们想让这些渲染指令在每次游戏循环迭代的时候都能被执行。代码将会是这样的: ```c++ // 程序循环 @@ -174,7 +178,7 @@ while(!glfwWindowShouldClose(window)) // 检查事件 glfwPollEvents(); - // 在这里执行各种渲染操作 + // 渲染指令 ... //交换缓冲区 @@ -182,20 +186,21 @@ while(!glfwWindowShouldClose(window)) } ``` -为了测试一切都正常,我们想让屏幕清空为一种我们选择的颜色。在每次执行新的渲染之前我们都希望清除上一次循环的渲染结果,除非我们想要看到上一次的结果。我们可以通过调用`glClear`函数来清空屏幕缓冲区的颜色,他接受一个整形常量参数来指定要清空的缓冲区,这个常量可以是`GL_COLOR_BUFFER_BIT`,`GL_DEPTH_BUFFER_BIT`和`GL_STENCIL_BUFFER_BIT`。由于现在我们只关心颜色值,所以我们只清空颜色缓冲区。 +为了测试一切都正常工作,我们使用一个自定义的颜色清空屏幕。在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果(这可能是你想要的效果,但通常这不是)。我们可以通过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BITGL_STENCIL_BUFFER_BIT。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。 ```c++ glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ``` -注意,除了`glClear`之外,我们还要调用`glClearColor`来设置要清空缓冲的颜色。当调用`glClear`函数之后,整个指定清空的缓冲区都被填充为`glClearColor`所设置的颜色。在这里,我们将屏幕设置为了类似黑板的深蓝绿色。 + +注意,除了glClear之外,我们还调用了glClearColor来设置清空屏幕所用的颜色。当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。在这里,我们将屏幕设置为了类似黑板的深蓝绿色。 !!! Important - 你应该能够想起来我们在[OpenGL](http://learnopengl-cn.readthedocs.org/zh/latest/01%20Getting%20started/01%20OpenGL/)教程的内容,`glClearColor`函数是一个状态设置函数,而`glClear`函数则是一个状态应用的函数。 + 你应该能够回忆起来我们在 *OpenGL* 这节教程的内容,glClearColor函数是一个状态设置函数,而glClear函数则是一个状态应用的函数。 -![](http://learnopengl.com/img/getting-started/hellowindow2.png) +![](../img/01/03/hellowindow2.png) -此程序的完整源代码可以在[这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow2)找到。 +这个程序的完整源代码可以在[这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow2)找到。 -好了,到目前为止我们已经做好开始在游戏循环中添加许多渲染操作的准备了,我认为我们的闲扯已经够多了,从下一篇教程开始我们将真正的征程。 \ No newline at end of file +好了,现在我们已经做好开始在游戏循环中添加许多渲染调用的准备了,但这是[下一节](04 Hello Triangle.md)教程了,这一节的内容已经太多了。 \ No newline at end of file diff --git a/docs/img/01/03/hellowindow.png b/docs/img/01/03/hellowindow.png new file mode 100644 index 0000000000000000000000000000000000000000..7a315bcb6d59c0385a63acae3205032cfca4766b GIT binary patch literal 7827 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 literal 0 HcmV?d00001