从0到1实战 OpenGL——创建窗口(案例1)【OpenGL for macOS】

393 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

一、准备

1.1. 依赖工具的安装

  • homebrew
  • cmake

1.2. 相关库安装

  • GLFW
    • 允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入
  • GLAD

二、入门

2.1. OpenGL

OpenGL一般它被认为是一个API(Application Programming Interface, 应用程序编程接口),包含了一系列可以操作图形、图像的函数。然而,OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)

如果有需要OpenGL API Specification 这里可以找到不同版本的OpenGL的规范。

所学习教程 面向OpenGL3.3的核心模式。所有OpenGL的更高的版本都是在3.3的基础上,引入了额外的功能,并没有改动核心架构。

2.1.1. 核心模式与立即渲染模式

立即渲染模式(Immediate mode,也就是固定渲染管线) 核心模式(Core-profile)

2.1.2. 扩展

开发者只需检查显卡是否支持某扩展,就可以根据需求完成新的渲染特性。

if(GL_ARB_extension_name)
{
    // 使用硬件支持的全新的现代特性
}
else
{
    // 不支持此扩展: 用旧的方式去做
}

2.1.3. 状态机

OpenGL的状态通常被称为OpenGL上下文(Context)

我们通常使用如下途径去更改OpenGL状态:

  • 设置选项
  • 操作缓冲
  • 使用当前OpenGL上下文来渲染 上述过程会使用到
  • 状态设置函数(State-changing Function),这类函数将会改变上下文。
  • 状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。

2.1.4. 对象

是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。

// OpenGL的状态
struct OpenGL_Context {
    ...
    object* object_Window_Target;
    ...     
};
// 创建对象
unsigned int objectId = 0;
//用一个id保存它的引用(实际数据被储存在后台)
glGenObject(1, &objectId);
// 绑定对象至上下文,此处上下文的目标位置被定义成GL_WINDOW_TARGET
glBindObject(GL_WINDOW_TARGET, objectId);
// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文对象设回默认,即将目标位置的对象id设回0,解绑这个对象
glBindObject(GL_WINDOW_TARGET, 0);

2.2. 创建窗口(案例1)

创建一个OpenGL上下文(Context)和一个用于显示的窗口以及处理用户输入

2.2.1. 完成GLFW环境配置(for macOs)

  • 获取 ,从官网下载GLFW源码,如下图所示 在这里插入图片描述

    • 解压 ,解压 glfw-3.3.6.zip,得到glfw-3.3.6目录,并在glfw-3.3.6 目录下建立build目录

    • make ,打开已经安装的CMake,按下图所示选择对应的源文件,和编译后的目标位置。【对于cmake的原理还不是很清晰,有空学习学习】 在这里插入图片描述

    • Configure,然后点击Configure,按图中指示操作。 在这里插入图片描述

  • Done,点击Done之后如下图所示 在这里插入图片描述

  • Configure,再次点击Configure,如图: 在这里插入图片描述

  • Generate,点击Generate,如图 在这里插入图片描述

  • 编译, 在终端进入glfw-3.3.6/build目录,执行命令make进行编译 在这里插入图片描述

  • 安装编译, 执行make install命令 在这里插入图片描述

2.2.2. 配置glad

  • 配置,前往 在线服务 界面,按照下图配置 glad 在这里插入图片描述
  • GENERATE,点击页面下方的 GENERATE 在这里插入图片描述
  • 下载,点击GENERATE后,进入下载页面,下载glad.zip 在这里插入图片描述
  • 解压&复制,解压 glad.zip,把include目录中的glad和KHR目录复制到/usr/local/include。不要删除src/glad.c,待添加到xcode项目里

2.2.3. 配置 xcode

  • 新建,新建一个Common Line Tool App,打开xcode,点击file-new-project,按照下图选择 在这里插入图片描述

  • 设置信息,设置 product name 等信息,选择下一步即可 在这里插入图片描述

  • 头、库文件,设置头文件(Header Search Paths)和库文件(Library Search Paths)目录 在这里插入图片描述 将头文件和库文件路径分别填写在 Header Search Paths 和 Library Search Paths 这个之后,如下所示 在这里插入图片描述

  • 添加 frameWorks,按照下图找到 Link Binary With Libraries,点击 “ + ” 按钮,添加相关 frameWorks。查找时可以使用快捷键command+shift+G,快速访问文件。 在这里插入图片描述

    这里需要的其他的 framework 可根据下图名称自行搜索添加 请添加图片描述

  • 添加glad.c,过程如图 在这里插入图片描述 在弹出的如下对话框中,点击 Options 按钮,选择刚才解压好的 glad.c 文件,完成配置。 在这里插入图片描述

2.2.4. 创建窗口

涉及函数

  • glfwInit()
  • glfwWindowHint(参数一, 参数二) 第一个参数,代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择; 第二个参数,接受一个整型,用来设置这个选项的值。 该函数的所有的选项以及对应的值都可以在 GLFW’s window handling 这里找到。我们需要告诉GLFW我们要使用的OpenGL版本是3.3,所以这里将主版本号(Major)和次版本号(Minor)都设为3,今后我们可以根据需要来对版本号进行设置。
  • glfwCreateWindow(窗口宽度, 窗口长度, 窗口名称, NULL, NULL) 这个函数将会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。

将以下代码复制到你的工程中,如果成功显示渲染窗口,则成功,目前还会有一些警告⚠️待处理。

//
//  main.cpp
//  Test001ShowWindow
//
//  Created by GD on 2022/3/21.
//


#include <glad/glad.h>//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h)
#include <GLFW/glfw3.h>

#include <iostream>

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

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();//初始化GLFW
    //OpenGL版本是3.3,以下为设置方式
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//第一个参数代表选项的名称
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//第二个参数接受一个整型,用来设置第一个参数的值
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用核心模式(Core-profile)

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "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);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

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

        //渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);//清空颜色缓冲
        
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);//函数会交换颜色缓冲
        glfwPollEvents();//函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();//当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    // 在GLFW中实现一些输入控制,这里按下esc,渲染结束
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);//函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes,对窗口注册一个回调函数,当用户改变窗口的大小的时候,视口也应该被调整
// 处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, width)和(0, height) ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

【参考资料】 本教程为参考博客 learnopengl.com/ 的学习实战教程。随着理解的不断加深,还会持续补充,欢迎大家一起探讨,批评指正。