【第三节】 你好,窗口

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/78163741

本节.cpp源代码下载为: Hello Window OpenGL-CSDN下载 http://download.csdn.net/download/qq_34536551/10011443

● 创建main函数,在这个函数中我们将会实例化GLFW窗口:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    return 0;
}

注意 : 请确认是在包含GLFW的头文件之前包含了GLAD的头文件。GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。

说明:

(1) main函数中调用glfwInit()函数来初始化GLFW

(2) 使用glfwWindowHint() 函数来配置 GLFW.

(3)glfwWindowHint ()函数的第一个参数代表选项的名称,我们打算配置哪个选项,这里我们可以从一个枚举中选择可用的选项,这些选项带有GLFW_前缀

第二个参数接受一个整形,用来设置这个选项的值。


注意: 该函数的所有的选项以及对应的值都可以在 GLFW’s window handling 这篇文档中找到。网址为: GLFW: Window guide http://www.glfw.org/docs/latest/window.html#window_hints


● 创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到:

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

说明:

(1) glfwCreateWindow函数需要窗口作为它的前两个参数;第三个参数表示这个窗口的名称(标题),这里我们使用”LearnOpenGL”,当然你也可以使用你喜欢的名称;最后两个参数我们暂时忽略,先设置为空指针就行。

这个函数将会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。

(2) 然后,我们告诉GLFW去创建我们窗口的环境(glfwMakeContextCurrent), 这个环境是当前线程的主环境。


GLAD


● GLAD是用来管理OpenGL的函数指针的所以在调用任何OpenGL的函数之前我们需要初始化GLAD。

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout << 未能初始化 GLAD" << std::endl;
    return -1;
}

我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是glfwGetProcAddress,它根据我们编译的系统定义了正确的函数。


Viewport(视口)


● 在我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和位置我们可以通过调用glViewport()函数来设置窗口的大小:

glViewport(0, 0, 800, 600);

说明: glViewport ()函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。

我们可以把这个值设置得比GLFW窗口尺寸小;这样之后所有的OpenGL的渲染都会在一个更小的窗口(区域)进行显示,我们可以在OpenGL的视口之外显示其他的元素。


注意: OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。(即 通过glViewport把这种2D坐标加工为屏幕上的坐标)

例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。


然而,当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。这个回调函数的原型如下:

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

这个帧缓冲大小函数需要一个GLFWwindow作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

我们还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

当窗口被第一次显示的时候framebuffer_size_callback也会被调用。对于视网膜(Retina)显示屏,width和height都会明显比原输入值更高一点。

我们还可以将我们的函数注册到其它很多的回调函数中。比如说,我们可以创建一个回调函数来处理手柄输入变化,处理错误消息等。我们会在创建窗口之后,渲染循环初始化之前注册这些回调函数。


准备好你的引擎


我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染(游戏)循环(Render Loop),它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的渲染(游戏)循环

while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();   
} 

(1) glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出(关闭)指示,如果是的话该函数返回true然后游戏循环便结束了,之后为我们就可以关闭应用程序了。

(2) glfwPollEvents函数检验是否有任何事件被触发(比如键盘输入或是鼠标移动的事件),接着调用相应函数(我们可以通过回调方法设置它们)。我们一般在游戏循环的开始调用事件处理函数。


双缓冲(Double Buffer)

应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。

为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。


最后一件事


当游戏循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来释放GLFW分配的内存。


glfwTerminate();
return 0;

这样便能清理所有的资源并正确地退出应用程序。


输入


我们同样也希望能够在GLFW中实现一些输入控制,这可以通过使用GLFW的几个输入函数来完成。我们将会使用GLFW的glfwGetKey函数它需要一个窗口以及一个按键作为输入。**这个函数将会返回这个按键是否正在被按下。我们将创建一个**processInput 函数来让所有的输入代码保持整洁。

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
     {
       glfwSetWindowShouldClose(window, true);
     }
}

这里我们检查用户是否按下了返回键(Esc)(如果没有按下,glfwGetKey将会返回GLFW_RELEASE。

如果用户的确按下了返回键,我们将通过glfwSetwindowShouldClose使用把WindowShouldClose属性设置为 true的方法关闭GLFW。下一次while循环的条件检测将会失败,程序将会关闭。

while (!glfwWindowShouldClose(window))
{
    processInput(window);

    glfwSwapBuffers(window);
    glfwPollEvents();
}

这就给我们一个非常简单的方式来检测特定的键是否被按下,并在每一帧做出处理。


渲染


我们要把所有的渲染(Rendering)操作放到游戏循环中因为我们想让这些渲染指令在每次游戏循环迭代的时候都能被执行。代码将会是这样的:

// 渲染循环
while(!glfwWindowShouldClose(window))
{
    // 输入
    processInput(window);

    // 渲染指令
    ...

    // 检查并调用事件,交换缓冲
    glfwPollEvents();
    glfwSwapBuffers(window);
}

为了测试一切都正常工作,我们使用一个自定义的颜色清空屏幕在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果(这可能是你想要的效果,但通常这不是)。

我们可以通过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲.

可能的缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

注意,除了glClear之外,我们还调用了glClearColor来设置清空屏幕所用的颜色。当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。在这里,我们将屏幕设置为了类似黑板的深蓝绿色。


注意: glClearColor函数是一个状态设置函数,而glClear函数则是一个状态使用的函数,它使用了当前的状态来获取应该清除为的颜色。


猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/78163741