mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
Update 01-03
This commit is contained in:
@@ -62,7 +62,7 @@ OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描
|
|||||||
|
|
||||||
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
|
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
|
||||||
|
|
||||||
当使用OpenGL的时候,我们会遇到一些<def>状态设置</def>函数(State-changing Function),这类函数将会改变上下文。以及<def>状态应用</def>函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。
|
当使用OpenGL的时候,我们会遇到一些<def>状态设置</def>函数(State-changing Function),这类函数将会改变上下文。以及<def>状态使用</def>函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。
|
||||||
|
|
||||||
## 对象
|
## 对象
|
||||||
|
|
||||||
|
@@ -4,21 +4,18 @@
|
|||||||
---|---
|
---|---
|
||||||
作者 | JoeyDeVries
|
作者 | JoeyDeVries
|
||||||
翻译 | Geequlim
|
翻译 | Geequlim
|
||||||
校对 | Geequlim
|
校对 | 未校对
|
||||||
|
|
||||||
让我们试试能不能让GLFW正常工作。首先,新建一个`.cpp`文件,然后把下面的代码粘贴到该文件的最前面。注意,之所以定义`GLEW_STATIC`宏,是因为我们使用的是GLEW静态的链接库。
|
让我们试试能不能让GLFW正常工作。首先,新建一个`.cpp`文件,然后把下面的代码粘贴到该文件的最前面。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
// GLEW
|
#include <glad/glad.h>
|
||||||
#define GLEW_STATIC
|
|
||||||
#include <GL/glew.h>
|
|
||||||
// GLFW
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Attention
|
!!! Attention
|
||||||
|
|
||||||
请确认在包含GLFW的头文件之前包含了GLEW的头文件。在包含glew.h头文件时会引入许多OpenGL必要的头文件(例如`GL/gl.h`),所以你需要在包含其它依赖于OpenGL的头文件之前先包含GLEW。
|
请确认是在包含GLFW的头文件之前包含了GLAD的头文件。GLAD的头文件包含了正确的OpenGL头文件(例如`GL/gl.h`),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
|
||||||
|
|
||||||
接下来我们创建<fun>main</fun>函数,在这个函数中我们将会实例化GLFW窗口:
|
接下来我们创建<fun>main</fun>函数,在这个函数中我们将会实例化GLFW窗口:
|
||||||
|
|
||||||
@@ -29,15 +26,15 @@ int main()
|
|||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
|
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
首先,我们在main函数中调用<fun>glfwInit</fun>函数来初始化GLFW,然后我们可以使用<fun>glfwWindowHint</fun>函数来配置GLFW。<fun>glfwWindowHint</fun>函数的第一个参数代表选项的名称,我们可以从很多以`GLFW_`开头的枚举值中选择;第二个参数接受一个整形,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 [GLFW's window handling](http://www.glfw.org/docs/latest/window.html#window_hints) 这篇文档中找到。如果你现在编译你的cpp文件会得到大量的 *undefined reference* (未定义的引用)错误,也就是说你并未顺利地链接GLFW库。
|
首先,我们在main函数中调用<fun>glfwInit</fun>函数来初始化GLFW,然后我们可以使用<fun>glfwWindowHint</fun>函数来配置GLFW。<fun>glfwWindowHint</fun>函数的第一个参数代表选项的名称,我们可以从很多以`GLFW_`开头的枚举值中选择;第二个参数接受一个整形,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 [GLFW's window handling](http://www.glfw.org/docs/latest/window.html#window_hints) 这篇文档中找到。如果你现在编译你的cpp文件会得到大量的 *undefined reference* (未定义的引用)错误,也就是说你并未顺利地链接GLFW库。
|
||||||
|
|
||||||
由于本站的教程都是基于OpenGL 3.3版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3,这样GLFW会在创建OpenGL上下文时做出适当的调整。这也可以确保用户在没有适当的OpenGL版本支持的情况下无法运行。我们将主版本号(Major)和次版本号(Minor)都设为3。我们同样明确告诉GLFW我们使用的是核心模式(Core-profile),并且不允许用户调整窗口的大小。在明确告诉GLFW使用核心模式的情况下,使用旧版函数将会导致**invalid operation**(无效操作)的错误,而这不正是一个很好的提醒吗?在我们不小心用了旧函数时报错,就能避免使用一些被废弃的用法了。如果使用的是Mac OS X系统,你还需要加下面这行代码到你的初始化代码中这些配置才能起作用:
|
由于本站的教程都是基于OpenGL 3.3版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3,这样GLFW会在创建OpenGL上下文时做出适当的调整。这也可以确保用户在没有适当的OpenGL版本支持的情况下无法运行。我们将主版本号(Major)和次版本号(Minor)都设为3。我们同样明确告诉GLFW我们使用的是核心模式(Core-profile)。明确告诉GLFW我们需要使用核心模式意味着我们只能使用OpenGL功能的一个子集(没有我们已不再需要的向后兼容特性)。如果使用的是Mac OS X系统,你还需要加下面这行代码到你的初始化代码中这些配置才能起作用(将上面的代码解除注释):
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
@@ -50,8 +47,8 @@ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|||||||
接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。
|
接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
|
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
|
||||||
if (window == nullptr)
|
if (window == NULL)
|
||||||
{
|
{
|
||||||
std::cout << "Failed to create GLFW window" << std::endl;
|
std::cout << "Failed to create GLFW window" << std::endl;
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
@@ -60,35 +57,32 @@ if (window == nullptr)
|
|||||||
glfwMakeContextCurrent(window);
|
glfwMakeContextCurrent(window);
|
||||||
```
|
```
|
||||||
|
|
||||||
<fun>glfwCreateWindow</fun>函数需要窗口的宽和高作为它的前两个参数;第三个参数表示这个窗口的名称(标题),这里我们使用`"LearnOpenGL"`,当然你也可以使用你喜欢的名称;最后两个参数我们暂时忽略,先设置为空指针就行。它的返回值<fun>GLFWwindow</fun>对象的指针会在其他的GLFW操作中使用到。创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。
|
<fun>glfwCreateWindow</fun>函数需要窗口的宽和高作为它的前两个参数。第三个参数表示这个窗口的名称(标题),这里我们使用`"LearnOpenGL"`,当然你也可以使用你喜欢的名称。最后两个参数我们暂时忽略。这个函数将会返回一个<fun>GLFWwindow</fun>对象,我们会在其它的GLFW操作中使用到。创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。
|
||||||
|
|
||||||
## GLEW
|
## GLAD
|
||||||
|
|
||||||
在之前的教程中已经提到过,GLEW是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLEW。
|
在之前的教程中已经提到过,GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
glewExperimental = GL_TRUE;
|
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
|
||||||
if (glewInit() != GLEW_OK)
|
|
||||||
{
|
{
|
||||||
std::cout << "Failed to initialize GLEW" << std::endl;
|
std::cout << "Failed to initialize GLAD" << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
请注意,我们在初始化GLEW之前设置<var>glewExperimental</var>变量的值为`GL_TRUE`,这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术,如果把它设置为`GL_FALSE`的话可能会在使用OpenGL的核心模式时出现一些问题。
|
我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是`glfwGetProcAddress`,它根据我们编译的系统定义了正确的函数。
|
||||||
|
|
||||||
## 视口(Viewport)
|
|
||||||
|
|
||||||
在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样相对于窗口大小显示数据和坐标。我们可以通过调用<fun>glViewport</fun>函数来设置窗口的**维度**(Dimension):
|
## 视口
|
||||||
|
|
||||||
|
在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。我们可以通过调用<fun>glViewport</fun>函数来设置窗口的**维度**(Dimension):
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
int width, height;
|
glViewport(0, 0, 800, 600);
|
||||||
glfwGetFramebufferSize(window, &width, &height);
|
|
||||||
|
|
||||||
glViewport(0, 0, width, height);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<fun>glViewport</fun>函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素),这里我们是直接从GLFW中获取的。我们从GLFW中获取视口的维度而不设置为800*600是为了让它在高DPI的屏幕上(比如说Apple的视网膜显示屏)也能[正常工作](http://www.glfw.org/docs/latest/window.html#window_size)。
|
<fun>glViewport</fun>函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
|
||||||
|
|
||||||
我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。
|
我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。
|
||||||
|
|
||||||
@@ -96,21 +90,46 @@ glViewport(0, 0, width, height);
|
|||||||
|
|
||||||
OpenGL幕后使用<fun>glViewport</fun>中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
|
OpenGL幕后使用<fun>glViewport</fun>中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
|
||||||
|
|
||||||
|
然而,当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。这个回调函数的原型如下:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
|
||||||
|
```
|
||||||
|
|
||||||
|
这个帧缓冲大小函数需要一个<fun>GLFWwindow</fun>作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
|
||||||
|
{
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
我们还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
|
||||||
|
```
|
||||||
|
|
||||||
|
当窗口被第一次显示的时候<fun>framebuffer_size_callback</fun>也会被调用。对于视网膜(Retina)显示屏,<var>width</var>和<var>height</var>都会明显比原输入值更高一点。
|
||||||
|
|
||||||
|
我们还可以将我们的函数注册到其它很多的回调函数中。比如说,我们可以创建一个回调函数来处理手柄输入变化,处理错误消息等。我们会在创建窗口之后,渲染循环初始化之前注册这些回调函数。
|
||||||
|
|
||||||
# 准备好你的引擎
|
# 准备好你的引擎
|
||||||
|
|
||||||
我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们明确地关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为<def>游戏循环</def>(Game Loop),它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的游戏循环:
|
我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为<def>渲染循环</def>(Render Loop),它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的渲染循环:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
while(!glfwWindowShouldClose(window))
|
while(!glfwWindowShouldClose(window))
|
||||||
{
|
{
|
||||||
glfwPollEvents();
|
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
|
glfwPollEvents();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- <fun>glfwWindowShouldClose</fun>函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回`true`然后游戏循环便结束了,之后为我们就可以关闭应用程序了。
|
- <fun>glfwWindowShouldClose</fun>函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回`true`然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
|
||||||
- <fun>glfwPollEvents</fun>函数检查有没有触发什么事件(比如键盘输入、鼠标移动等),然后调用对应的回调函数(可以通过回调方法手动设置)。我们一般在游戏循环的开始调用事件处理函数。
|
- <fun>glfwPollEvents</fun>函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
|
||||||
- <fun>glfwSwapBuffers</fun>函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
|
- <fun>glfwSwapBuffers</fun>函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
|
||||||
|
|
||||||
!!! Important
|
!!! Important
|
||||||
|
|
||||||
@@ -120,7 +139,7 @@ while(!glfwWindowShouldClose(window))
|
|||||||
|
|
||||||
## 最后一件事
|
## 最后一件事
|
||||||
|
|
||||||
当游戏循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在<fun>main</fun>函数的最后调用<fun>glfwTerminate</fun>函数来释放GLFW分配的内存。
|
当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在<fun>main</fun>函数的最后调用<fun>glfwTerminate</fun>函数来完成。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
@@ -131,57 +150,54 @@ return 0;
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
如果你看见了一个非常无聊的黑色窗口,那么就对了!如果你没得到正确的结果,或者你不知道怎么把所有东西放到一起,请到[这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow)参考源代码。
|
如果你看见了一个非常无聊的黑色窗口,那么就对了!如果你没得到正确的结果,或者你不知道怎么把所有东西放到一起,请到[这里](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/1.1.hello_window/hello_window.cpp)参考源代码。
|
||||||
|
|
||||||
如果程序编译有问题,请先检查连接器选项是否正确,IDE中是否导入了正确的目录(前面教程解释过)。并且请确认你的代码是否正确,直接对照上面提供的源代码就行了。如果还是有问题,欢迎评论,我或者其他人可能会帮助你的。
|
如果程序编译有问题,请先检查连接器选项是否正确,IDE中是否导入了正确的目录(前面教程解释过)。并且请确认你的代码是否正确,直接对照上面提供的源代码就行了。如果还是有问题,欢迎评论,我或者其他人可能会帮助你的。
|
||||||
|
|
||||||
## 输入
|
## 输入
|
||||||
|
|
||||||
我们同样也希望能够在GLFW中实现一些键盘控制,这可以通过使用GLFW的回调函数(Callback Function)来完成。<def>回调函数</def>事实上是一个函数指针,当我们设置好后,GLFW会在合适的时候调用它。**按键回调**(KeyCallback)是众多回调函数中的一种。当我们设置了按键回调之后,GLFW会在用户有键盘交互时调用它。该回调函数的原型如下所示:
|
我们同样也希望能够在GLFW中实现一些输入控制,这可以通过使用GLFW的几个输入函数来完成。我们将会使用GLFW的<fun>glfwGetKey</fun>函数,它需要一个窗口以及一个按键作为输入。这个函数将会返回这个按键是否正在被按下。我们将创建一个<fun>processInput</fun>函数来让所有的输入代码保持整洁。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
|
void processInput(GLFWwindow *window)
|
||||||
```
|
|
||||||
|
|
||||||
按键回调函数接受一个<fun>GLFWwindow</fun>指针作为它的第一个参数;第二个整形参数用来表示按下的按键;`action`参数表示这个按键是被按下还是释放;最后一个整形参数表示是否有Ctrl、Shift、Alt、Super等按钮的操作。GLFW会在合适的时候调用它,并为各个参数传入适当的值。
|
|
||||||
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
|
|
||||||
{
|
{
|
||||||
// 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
|
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
|
||||||
// 关闭应用程序
|
glfwSetWindowShouldClose(window, true);
|
||||||
if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
|
}
|
||||||
glfwSetWindowShouldClose(window, GL_TRUE);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在我们(新创建的)<fun>key_callback</fun>函数中,我们检测了键盘是否按下了Escape键。如果键的确按下了(不释放),我们使用<fun>glfwSetwindowShouldClose</fun>函数设定`WindowShouldClose`属性为`true`从而关闭GLFW。main函数的`while`循环下一次的检测将为失败,程序就关闭了。
|
这里我们检查用户是否按下了返回键(Esc)(如果没有按下,<fun>glfwGetKey</fun>将会返回<var>GLFW_RELEASE</var>。如果用户的确按下了返回键,我们将通过<fun>glfwSetwindowShouldClose</fun>使用把`WindowShouldClose`属性设置为 `true`的方法关闭GLFW。下一次while循环的条件检测将会失败,程序将会关闭。
|
||||||
|
|
||||||
最后一件事就是通过GLFW注册我们的函数至合适的回调,代码是这样的:
|
我们接下来在渲染循环的每一个迭代中调用<fun>processInput</fun>:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
glfwSetKeyCallback(window, key_callback);
|
while (!glfwWindowShouldClose(window))
|
||||||
|
{
|
||||||
|
processInput(window);
|
||||||
|
|
||||||
|
glfwSwapBuffers(window);
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
除了按键回调函数之外,我们还能我们自己的函数注册其它的回调。例如,我们可以注册一个回调函数来处理窗口尺寸变化、处理一些错误信息等。我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。
|
这就给我们一个非常简单的方式来检测特定的键是否被按下,并在每一帧做出处理。
|
||||||
|
|
||||||
|
|
||||||
## 渲染
|
## 渲染
|
||||||
|
|
||||||
我们要把所有的渲染(Rendering)操作放到游戏循环中,因为我们想让这些渲染指令在每次游戏循环迭代的时候都能被执行。代码将会是这样的:
|
我们要把所有的渲染(Rendering)操作放到渲染循环中,因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行。代码将会是这样的:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
// 程序循环
|
// 渲染循环
|
||||||
while(!glfwWindowShouldClose(window))
|
while(!glfwWindowShouldClose(window))
|
||||||
{
|
{
|
||||||
// 检查事件
|
// 输入
|
||||||
glfwPollEvents();
|
processInput(window);
|
||||||
|
|
||||||
// 渲染指令
|
// 渲染指令
|
||||||
...
|
...
|
||||||
|
|
||||||
// 交换缓冲
|
// 检查并调用事件,交换缓冲
|
||||||
|
glfwPollEvents();
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -197,10 +213,10 @@ glClear(GL_COLOR_BUFFER_BIT);
|
|||||||
|
|
||||||
!!! Important
|
!!! Important
|
||||||
|
|
||||||
你应该能够回忆起来我们在 *OpenGL* 这节教程的内容,<fun>glClearColor</fun>函数是一个状态设置函数,而<fun>glClear</fun>函数则是一个状态应用的函数。
|
你应该能够回忆起来我们在 *OpenGL* 这节教程的内容,<fun>glClearColor</fun>函数是一个**状态设置**函数,而<fun>glClear</fun>函数则是一个**状态使用**的函数,它使用了当前的状态来获取应该清除为的颜色。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这个程序的完整源代码可以在[这里](http://learnopengl.com/code_viewer.php?code=getting-started/hellowindow2)找到。
|
这个程序的完整源代码可以在[这里](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/1.2.hello_window_clear/hello_window_clear.cpp)找到。
|
||||||
|
|
||||||
好了,现在我们已经做好开始在游戏循环中添加许多渲染调用的准备了,但这是[下一节](04 Hello Triangle.md)教程了,这一节的内容已经太多了。
|
好了,现在我们已经做好开始在渲染循环中添加许多渲染调用的准备了,但这是[下一节](04 Hello Triangle.md)教程了,这一节的内容已经太多了。
|
@@ -32,4 +32,71 @@ GLEW是OpenGL Extension Wrangler Library的缩写,它能解决我们上面提
|
|||||||
|
|
||||||
对于用GCC编译的Linux用户建议使用这个命令行选项`-lGLEW -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi`。没有正确链接相应的库会产生 *undefined reference*(未定义的引用) 这个错误。
|
对于用GCC编译的Linux用户建议使用这个命令行选项`-lGLEW -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi`。没有正确链接相应的库会产生 *undefined reference*(未定义的引用) 这个错误。
|
||||||
|
|
||||||
我们现在成功编译了GLFW和GLEW库,我们已经准备好将进入[下一节](03 Hello Window.md)去真正使用GLFW和GLEW来设置OpenGL上下文并创建窗口。记得确保你的头文件和库文件的目录设置正确,以及链接器里引用的库文件名正确。如果仍然遇到错误,可以先看一下评论有没有人遇到类似的问题,请参考额外资源中的例子或者在下面的评论区提问。
|
我们现在成功编译了GLFW和GLEW库,我们已经准备好将进入[下一节](01 Getting started/03 Hello Window.md)去真正使用GLFW和GLEW来设置OpenGL上下文并创建窗口。记得确保你的头文件和库文件的目录设置正确,以及链接器里引用的库文件名正确。如果仍然遇到错误,可以先看一下评论有没有人遇到类似的问题,请参考额外资源中的例子或者在下面的评论区提问。
|
||||||
|
|
||||||
|
## 01-03 你好,窗口
|
||||||
|
|
||||||
|
### GLEW
|
||||||
|
|
||||||
|
在之前的教程中已经提到过,GLEW是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLEW。
|
||||||
|
|
||||||
|
```c++
|
||||||
|
glewExperimental = GL_TRUE;
|
||||||
|
if (glewInit() != GLEW_OK)
|
||||||
|
{
|
||||||
|
std::cout << "Failed to initialize GLEW" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意,我们在初始化GLEW之前设置<var>glewExperimental</var>变量的值为`GL_TRUE`,这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术,如果把它设置为`GL_FALSE`的话可能会在使用OpenGL的核心模式时出现一些问题。
|
||||||
|
|
||||||
|
|
||||||
|
### 视口(Viewport)
|
||||||
|
|
||||||
|
在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样相对于窗口大小显示数据和坐标。我们可以通过调用<fun>glViewport</fun>函数来设置窗口的**维度**(Dimension):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
int width, height;
|
||||||
|
glfwGetFramebufferSize(window, &width, &height);
|
||||||
|
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
```
|
||||||
|
|
||||||
|
<fun>glViewport</fun>函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素),这里我们是直接从GLFW中获取的。我们从GLFW中获取视口的维度而不设置为800*600是为了让它在高DPI的屏幕上(比如说Apple的视网膜显示屏)也能[正常工作](http://www.glfw.org/docs/latest/window.html#window_size)。
|
||||||
|
|
||||||
|
我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。
|
||||||
|
|
||||||
|
!!! Important
|
||||||
|
|
||||||
|
OpenGL幕后使用<fun>glViewport</fun>中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
|
||||||
|
### 输入
|
||||||
|
|
||||||
|
我们同样也希望能够在GLFW中实现一些键盘控制,这可以通过使用GLFW的回调函数(Callback Function)来完成。<def>回调函数</def>事实上是一个函数指针,当我们设置好后,GLFW会在合适的时候调用它。**按键回调**(KeyCallback)是众多回调函数中的一种。当我们设置了按键回调之后,GLFW会在用户有键盘交互时调用它。该回调函数的原型如下所示:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
|
||||||
|
```
|
||||||
|
|
||||||
|
按键回调函数接受一个<fun>GLFWwindow</fun>指针作为它的第一个参数;第二个整形参数用来表示按下的按键;`action`参数表示这个按键是被按下还是释放;最后一个整形参数表示是否有Ctrl、Shift、Alt、Super等按钮的操作。GLFW会在合适的时候调用它,并为各个参数传入适当的值。
|
||||||
|
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
|
||||||
|
{
|
||||||
|
// 当用户按下ESC键,我们设置window窗口的WindowShouldClose属性为true
|
||||||
|
// 关闭应用程序
|
||||||
|
if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
|
||||||
|
glfwSetWindowShouldClose(window, GL_TRUE);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在我们(新创建的)<fun>key_callback</fun>函数中,我们检测了键盘是否按下了Escape键。如果键的确按下了(不释放),我们使用<fun>glfwSetwindowShouldClose</fun>函数设定`WindowShouldClose`属性为`true`从而关闭GLFW。main函数的`while`循环下一次的检测将为失败,程序就关闭了。
|
||||||
|
|
||||||
|
最后一件事就是通过GLFW注册我们的函数至合适的回调,代码是这样的:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
glfwSetKeyCallback(window, key_callback);
|
||||||
|
```
|
||||||
|
|
||||||
|
除了按键回调函数之外,我们还能我们自己的函数注册其它的回调。例如,我们可以注册一个回调函数来处理窗口尺寸变化、处理一些错误信息等。我们可以在创建窗口之后,开始游戏循环之前注册各种回调函数。
|
@@ -14,7 +14,7 @@
|
|||||||
- State Machine:状态机
|
- State Machine:状态机
|
||||||
- Context:上下文
|
- Context:上下文
|
||||||
- State-changing Function:状态设置函数
|
- State-changing Function:状态设置函数
|
||||||
- State-using Function:状态应用函数
|
- State-using Function:状态使用函数
|
||||||
- Object:对象
|
- Object:对象
|
||||||
- Primitive Type:基元类型
|
- Primitive Type:基元类型
|
||||||
- Option:选项
|
- Option:选项
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
- Viewport:视口
|
- Viewport:视口
|
||||||
- Dimension(窗口):维度
|
- Dimension(窗口):维度
|
||||||
- Game Loop:游戏循环
|
- Game Loop:游戏循环
|
||||||
|
- Render Loop:渲染循环
|
||||||
- Color Buffer:颜色缓冲
|
- Color Buffer:颜色缓冲
|
||||||
- Double Buffer:双缓冲
|
- Double Buffer:双缓冲
|
||||||
- Front Buffer:前缓冲
|
- Front Buffer:前缓冲
|
||||||
|
Reference in New Issue
Block a user