OpenGl入门2:第一个窗口

本文是我的学习记录,学习建议看教程 https://learnopengl-cn.github.io/
很是感谢原做者JoeyDeVries和两位翻译的gjy_1992, Krasjet提供的优质教程php

测试GLFW

在咱们的test.cpp中加入下面两个头文件html

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

接下来咱们在main函数里写上以下代码,在main里咱们将会实例化GLFW窗口:ios

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);//for Mac

    return 0;
}

首先,调用glfwInit()函数来初始化GLFW,而后使用glfwWindowHint()函数来配置GLFWc++

glfwWindowHint()函数的第一个参数表明选项的名称,咱们能够从不少以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值
该函数的全部的选项以及对应的值均可以在 GLFW’s window handling 这篇文档中找到
若是你如今编译你的cpp文件会获得大量的 undefined reference (未定义的引用) 错误,也就是说你并未顺利地连接GLFW库git

因为我看的教程是基于OpenGL 3.3版本展开讨论的,因此咱们须要告诉GLFW咱们要使用的OpenGL版本是3.3,因此咱们将主版本号(Major)和次版本号(Minor)都设为3,这样GLFW会在建立OpenGL上下文时作出适当的调整,这也能够确保用户在没有适当的OpenGL版本支持的状况下没法运行github

咱们一样明确告诉GLFW咱们使用的是核心模式(Core-profile)。明确告诉GLFW咱们须要使用核心模式意味着咱们只能使用OpenGL功能的一个子集(没有咱们已再也不须要的向后兼容特性)函数

若是使用的是Mac OS X系统,还须要将上面的代码解除注释oop


接下来咱们建立一个窗口对象,这个窗口对象存放了全部和窗口相关的数据,并且会被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,它根据咱们编译的系统定义了正确的函数。

视口

在咱们开始渲染以前,咱们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。咱们能够经过调用glViewport函数来设置窗口的维度(Dimension):

glViewport(0, 0, 800, 600);

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

咱们实际上也能够将视口的维度设置为比GLFW的维度小,这样子以后全部的OpenGL渲染将会在一个更小的窗口中显示,这样子的话咱们也能够将一些其它元素显示在OpenGL视口以外

OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,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();    
}
  • glfwWindowShouldClose函数在咱们每次循环的开始前检查一次GLFW是否被要求退出,若是是的话该函数返回 true 而后渲染循环便结束了,以后为咱们就能够关闭应用程序了
  • glfwPollEvents函数检查有没有触发什么事件(好比键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(能够经过回调方法手动设置)
  • glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每个像素颜色值的大缓冲),它在这一迭代中被用来绘制,而且将会做为输出显示在屏幕上

双缓冲(Double Buffer)

应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是由于生成的图像不是一会儿被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是经过一步一步生成的,这会致使渲染的结果很不真实。为了规避这些问题,咱们应用双缓冲渲染窗口应用程序。缓冲保存着最终输出的图像,它会在屏幕上显示;而全部的的渲染指令都会在缓冲上绘制。当全部的渲染指令执行完毕后,咱们交换(Swap)前缓冲和后缓冲,这样图像就当即呈显出来,以前提到的不真实感就消除了。

最后一件事

当渲染循环结束后咱们须要正确释放/删除以前的分配的全部资源,咱们能够在main函数的最后调用glfwTerminate()来完成。

glfwTerminate();
return 0;

这样便能清理全部的资源并正确地退出应用程序

如今咱们的代码是这样的

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

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

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

    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);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }    


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

    glfwTerminate();
    return 0;
}

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

如今你能够尝试编译并运行咱们的应用程序了,若是没作错的话,你将会看到以下的输出:

一个很是无聊的黑色窗口,这就对了!


输入

咱们一样也但愿可以在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循环的条件检测将会失败,程序将会关闭

咱们接下来在渲染循环的每个迭代中加入 processInput 的调用:

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

    glfwSwapBuffers(window);
    glfwPollEvents();
}

这就给咱们一个很是简单的方式来检测特定的键是否被按下,并在每一帧作出处理

渲染

咱们要把全部的渲染(Rendering)操做放到渲染循环中,由于咱们想让这些渲染指令在每次渲染循环迭代的时候都能被执行,代码将会是这样的:

为了测试一切都正常工做,咱们使用一个自定义的颜色清空屏幕

在每一个新的渲染迭代开始的时候咱们老是但愿清屏,不然咱们仍能看见上一次迭代的渲染结果(这多是你想要的效果,但一般这不是)

咱们能够经过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT 和 GL_STENCIL_BUFFER_BIT

因为如今咱们只关心颜色值,因此咱们只清空颜色缓冲 GL_COLOR_BUFFER_BIT,讲下述代码加入到渲染循环的渲染指令部分

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

注意,除了glClear以外,咱们还调用了glClearColor来设置清空屏幕所用的颜色,当调用glClear函数,清除颜色缓冲以后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色

如今个人代码是这样的:

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

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

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

    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);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

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


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

        // 渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

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

    glfwTerminate();
    return 0;
}

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

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回键
        glfwSetWindowShouldClose(window, true);
}

编译运行:

咱们将屏幕设置为了相似黑板的深蓝绿色,而且按Esc才会结束运行

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

至此,如今咱们已经作好开始在渲染循环中添加许多渲染调用的准备了

参考:教程源代码

相关文章
相关标签/搜索