OpenGL学习笔记3-Hello Window

Hello Window

让我们看看能否启动并运行GLFW。首先,创建一个.cpp文件,并将以下内容添加到新创建的文件的顶部。

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

请确保在GLFW之前包括GLAD。GLAD的包含文件在幕后包含了所需的OpenGL头文件(如GL/ GL .h),所以请确保在其他需要OpenGL的头文件(如GLFW)之前包含GLAD。

接下来,我们创建main函数,在这里我们将实例化GLFW窗口:

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;
}

在主函数中,我们首先使用glfwInit初始化GLFW,然后使用glfwWindowHint配置GLFW。glfwWindowHint的第一个参数告诉我们要配置哪个选项,我们可以从以GLFW_为前缀的大量可能选项中选择选项。第二个参数是一个设置选项值的整数。在GLFW's window handling中可以找到所有可能选项及其对应值的列表。如果您现在尝试运行应用程序,但是它给出了许多未定义的引用错误,这意味着您没有成功地链接到GLFW库。

由于本书的重点是OpenGL 3.3版本,我们想告诉GLFW 3.3是我们想使用的OpenGL版本。通过这种方式,GLFW可以在创建OpenGL上下文时做出适当的安排。这确保了当用户没有正确的OpenGL版本时,GLFW不能运行。我们将主版本和副版本都设置为3。我们还告诉GLFW我们想显式地使用核心配置文件。告诉GLFW我们想要使用核心概要文件意味着我们可以访问更小的OpenGL特性子集,而不需要我们不再需要的向后兼容特性。注意,Mac OS X上需要添加glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);到您的初始化代码,以使其工作。

确保你的系统/硬件上安装了OpenGL 3.3或更高版本,否则应用程序将崩溃或显示未定义的行为。要在您的机器上找到OpenGL版本,可以在Linux机器上调用glxinfo,或者使用像OpenGL Extension Viewer这样的实用工具。如果你支持的版本更低,试着检查你的显卡是否支持OpenGL 3.3+(否则它真的很旧)和/或更新你的驱动程序。

接下来,我们需要创建一个窗口对象。这个窗口对象保存所有窗口数据,并且是大多数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);

glfwCreateWindow函数要求窗口宽度和高度分别作为它的前两个参数。第三个参数允许我们为窗口创建一个名称;现在我们称它为“LearnOpenGL”,但你可以随意命名它。我们可以忽略最后两个参数。该函数返回一个GLFWwindow对象,稍后我们将需要该对象进行其他GLFW操作。之后,我们告诉GLFW将窗口的上下文作为当前线程的主上下文。

GLAD

在上一章我们提到了GLAD管理OpenGL的函数指针,所以我们想在调用任何OpenGL函数之前初始化GLAD:


if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
}

我们传递GLAD函数来加载特定于操作系统的OpenGL函数指针的地址。GLFW提供了glfwGetProcAddress,它根据我们要编译的操作系统定义正确的函数。

视窗(Viewport)

在我们开始渲染之前,我们必须做最后一件事。我们必须告诉OpenGL渲染窗口的大小以便OpenGL知道我们想要如何显示数据和与窗口相关的坐标。我们可以通过glViewport函数设置这些维度:

glViewport(0, 0, 800, 600);

glViewport的前两个参数设置了窗口左下角的位置。第三和第四个参数以像素为单位设置渲染窗口的宽度和高度,我们将其设置为等于GLFW的窗口大小。

我们可以设置viewport的维数小于GLFW的维数;然后所有的OpenGL渲染会在一个更小的窗口中显示,我们可以在OpenGL视窗之外显示其他元素。

在幕后,OpenGL使用通过glViewport指定的数据将它处理的2D坐标转换为屏幕上的坐标。例如,一个经过处理的位置点(-0.5,0.5)将(作为它的最终转换)映射到屏幕坐标中的(200,450)。注意,OpenGL中处理过的坐标在-1和1之间,因此我们有效地从范围(-1到1)映射到(0,800)和(0,600)。

然而,当用户调整窗口大小时,也应该调整视窗。我们可以在窗口上注册一个回调函数,该函数在每次窗口调整大小时被调用。这个调整大小回调函数有以下原型:

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

framebuffer大小函数接受一个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也会被调用,并得到结果窗口的尺寸。视网膜显示器的宽度和高度最终会明显高于原始输入值。

我们可以设置许多回调函数来注册我们自己的函数。例如,我们可以做一个回调函数来处理操纵杆输入的变化,处理错误信息等。我们在创建窗口之后和在渲染循环启动之前注册回调函数。

准备好你的引擎(Ready your engines)

我们不希望应用程序绘制一个单一的图像,然后立即退出并关闭窗口。我们希望应用程序继续绘制图像和处理用户输入,直到程序被明确告知停止。因为这个原因,我们必须创建一个while循环,我们现在调用渲染循环,它会一直运行,直到我们告诉GLFW停止。下面的代码显示了一个非常简单的渲染循环:

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

glfwWindowShouldClose函数在每个循环迭代开始时检查GLFW是否被指示关闭。如果是,函数返回true, render循环停止运行,之后我们可以关闭应用程序。

glfwPollEvents函数检查是否触发了任何事件(如键盘输入或鼠标移动事件),更新窗口状态,并调用相应的函数(我们可以通过回调方法注册它)。glfwSwapBuffers将交换颜色缓冲区(一个较大的2D缓冲区,包含GLFW窗口中每个像素的颜色值),并将其作为渲染迭代期间渲染的输出显示在屏幕上。

双缓冲

当应用程序绘制单个缓冲区时,生成的图像可能会显示闪烁问题。这是因为输出的结果图像不是瞬间绘制的,而是逐像素绘制的,通常是从左到右,从上到下。由于此图像在仍然呈现给用户时不会立即显示给用户,因此结果可能包含工件。为了规避这些问题,窗口应用程序为呈现应用了一个双重缓冲区。前缓冲区包含显示在屏幕上的最终输出图像,而所有呈现命令都绘制到后缓冲区。一旦所有的渲染命令完成,我们就将后缓冲区交换到前缓冲区,这样图像就可以在不被渲染的情况下显示出来,删除所有前面提到的工件。

最后一件事

一旦我们退出渲染循环,我们想要正确地清除/删除所有分配给GLFW的资源。我们可以通过在main函数末尾调用的glfwTerminate函数来实现这一点。

glfwTerminate();
return 0;

这将清理所有资源并正确退出应用程序。现在尝试编译您的应用程序,如果一切顺利,您应该看到以下输出:

如果它是一个非常沉闷和无聊的黑色图像,你做得对!如果您没有得到正确的图像,或者您对如何将所有内容组合在一起感到困惑,请查看here的完整源代码。

如果在编译应用程序时遇到问题,首先要确保所有链接器选项都正确设置了,并且在IDE中正确包含了目录(如前一章所述)。还要确保你的代码是正确的;您可以通过将其与完整的源代码进行比较来验证它。

输入

我们还希望在GLFW中有某种形式的输入控制,我们可以通过几个GLFW的输入函数来实现这一点。我们将使用GLFW的glfwGetKey函数,它将窗口和一个键作为输入。函数返回当前是否按下此键。我们正在创建一个processInput函数,以保持所有输入代码的组织:

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

这里我们检查用户是否按下了escape键(如果没有按下,glfwGetKey返回GLFW_RELEASE)。如果用户确实按了escape键,我们通过使用glfwSetwindowShouldClose将其WindowShouldClose属性设置为true来关闭GLFW。主while循环的下一个条件检查将失败,应用程序关闭。

然后我们调用processInput的渲染循环的每次迭代:

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

    glfwSwapBuffers(window);
    glfwPollEvents();
}

这为我们提供了一种简单的方法来检查特定的按键并相应地对每一帧做出反应。渲染循环的迭代通常称为帧。

呈现

我们希望将所有的渲染命令放在渲染循环中,因为我们希望在循环的每次迭代或帧中执行所有的渲染命令。这看起来有点像这样:

// render loop
while(!glfwWindowShouldClose(window))
{
    // input
    processInput(window);

    // rendering commands here
    ...

    // check and call events and swap the buffers
    glfwPollEvents();
    glfwSwapBuffers(window);
}

为了测试是否可以正常工作,我们想用自己选择的颜色清除屏幕。在帧的开始,我们想要清除屏幕。否则我们仍然会看到前一帧的结果(这可能是您想要的效果,但通常不会)。我们可以使用glClear清除屏幕的颜色缓冲区,在这里我们传递缓冲区位来指定我们想要清除的缓冲区。我们可以设置的可能的位是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);

注意,我们还使用glClearColor指定了要清除屏幕的颜色。每当我们调用glClear并清除颜色缓冲区时,整个颜色缓冲区将被glClearColor配置的颜色填充。这将导致一个暗绿色的蓝色。

在OpenGL一章中,glClearColor函数是一个状态设置函数,而glClear是一个状态使用函数,因为它使用当前状态来检索清除颜色。

应用程序的完整源代码可以在here找到。

现在我们已经准备好了用大量渲染调用填充渲染循环的一切,但那是下一章的内容。我想我们在这里已经漫谈够久了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值