当前位置: 代码迷 >> 综合 >> OpenGL学习笔记(LearnOpenGL)正在更新
  详细解决方案

OpenGL学习笔记(LearnOpenGL)正在更新

热度:90   发布时间:2023-12-22 02:43:06.0

此文章在这里继续更新。

目录

  • OpenGL简介
    • OpenGL是什么
    • 立即渲染模式与核心模式
    • OpenGL实现:状态机
  • 使用GLFW创建窗口
    • 使用GLAD获取OpenGL函数地址
    • 创建窗口
      • 一些常用回调函数
    • 渲染循环
      • 双缓冲
  • 渲染管线(Pipeline)

学习资料来源: LearnOpenGL CN

OpenGL简介

OpenGL是什么

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

OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由OpenGL库的开发者自行决定(译注:这里开发者是指编写OpenGL库的人)。因为OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配(亦即,作为用户不会感受到功能上的差异)。

实际的OpenGL库的开发者通常是显卡的生产商。你购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。当你使用Apple系统的时候,OpenGL库是由Apple自身维护的。在Linux下,有显卡生产商提供的OpenGL库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug。

立即渲染模式与核心模式

早期的OpenGL使用立即渲染模式(Immediate mode,也就是固定渲染管线),这个模式下绘制图形很方便。OpenGL的大多数功能都被库隐藏起来,开发者很少有控制OpenGL如何进行计算的自由。而开发者迫切希望能有更多的灵活性。随着时间推移,规范越来越灵活,开发者对绘图细节有了更多的掌控。立即渲染模式确实容易使用和理解,但是效率太低。因此从OpenGL3.2开始,规范文档开始废弃立即渲染模式,并鼓励开发者在OpenGL的核心模式(Core-profile)下进行开发,这个分支的规范完全移除了旧的特性。

当使用OpenGL的核心模式时,OpenGL迫使我们使用现代的函数。当我们试图使用一个已废弃的函数时,OpenGL会抛出一个错误并终止绘图。现代函数的优势是更高的灵活性和效率,然而也更难于学习。立即渲染模式从OpenGL实际运作中抽象掉了很多细节,因此它在易于学习的同时,也很难让人去把握OpenGL具体是如何运作的。现代函数要求使用者真正理解OpenGL和图形编程,它有一些难度,然而提供了更多的灵活性,更高的效率,更重要的是可以更深入的理解图形编程。

OpenGL实现:状态机

OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。

假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。

~  
~  

使用GLFW创建窗口

在官网下载源码,在本地使用CMake编译、生成即可。

使用GLAD获取OpenGL函数地址

为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。有些库能简化此过程,其中GLAD是目前最新,也是最流行的库。

打开GLAD的在线服务,将语言(Language)设置为C/C++,在API选项中,选择3.3以上的OpenGL(gl)版本(我们的教程中将使用3.3版本,但更新的版本也能正常工作)。之后将模式(Profile)设置为Core,并且保证生成加载器(Generate a loader)的选项是选中的。现在可以先(暂时)忽略拓展(Extensions)中的内容。都选择完之后,点击生成(Generate)按钮来生成库文件。

GLAD提供了一个zip压缩文件,包含两个头文件目录,和一个glad.c文件。将两个头文件目录(glad和KHR)复制到你的Include文件夹中(或者增加一个额外的项目指向这些目录),并添加glad.c文件到你的工程中。

经过前面的这些步骤之后,你就应该可以将以下的指令加到你的文件顶部了:

#include <glad/glad.h>

创建窗口

GLFW相关WindowHint关键字

#include <glad/glad.h>
#include <GLFW/glfw3.h>void framebuffer_size_callback(GLFWwindow* window, int width, int height);int main() {
    //初始化glfwInit();//设置OpenGL版本号3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//设置核心模式glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//Mac OS系统特有//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//创建一个宽800,高600,名字叫做LearnOpenGL的窗口GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);if (window == NULL) {
    std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}//通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。glfwMakeContextCurrent(window);/*配置GLAD给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是glfwGetProcAddress,它根据我们编译的系统定义了正确的函数。*/if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    std::cout << "Failed to initialize GLAD" << std::endl;return -1;}	//使用OpenGL函数对窗口进行操作glViewport(0, 0, 800, 600);//设置回调函数glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

glViewPort函数:前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。

当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),比如上述代码中的framebuffer_size_callback。
注册这个函数,就是告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

当用户改变窗口大小时,将会执行framebuffer_size_callback这个函数。

一些常用回调函数

//窗口大小调整,width和height为调整后的窗口大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height); //原型
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //注册//鼠标移动,回调函数接受三个参数,第一个是窗口,后两个表示鼠标当前位置
void mouse_callback(GLFWwindow* window, double xpos, double ypos); //原型
glfwSetCursorPosCallback(window, mouse_callback); //注册//鼠标滚轮,yoffset值代表我们竖直滚动的大小。
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); //原型
glfwSetScrollCallback(window, scroll_callback); //注册

更多详细用法见摄像机一节。

渲染循环

我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行。

while(!glfwWindowShouldClose(window)) {
    //处理用户输入processInput(window);//渲染指令glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glfwPollEvents();glfwSwapBuffers(window);
}
glfwTerminate();
  • glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
  • glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
  • glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
  • 当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来完成。
  • glClear函数将会把指定缓冲中的颜色全部改为glClearColor中设定的颜色(RGBA)。对于OpenGL状态机来说,glClearColor函数是一个状态设置函数,而glClear函数则是一个状态使用的函数,它使用了当前的状态来获取应该清除为的颜色。

双缓冲

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

至此,窗口创建完成。
~  
~  

渲染管线(Pipeline)