OpenGL 窗口
学习地址
我是从这里学习的 OpenGL LearnOpenGL-CN
了解背景
首先,了解背景很重要的,这能让我们更好地初步对OpenGL有一个概念
概念
OpenGL 并不是一个 API ,它仅仅是一个由Khronos
组织制定并维护的规范( Specification )。
文档链接
实际的OpenGL
库的开发者通常是显卡的生产商。你购买的显卡所支持的OpenGL
版本都为这个系列的显卡专门开发的。
当你使用Apple
系统的时候,OpenGL
库是由Apple
自身维护的。
在 Linux 下,有显卡生产商提供的OpenGL
库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL
库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug
。
渲染模式的变更
早期的OpenGL
使用立即渲染模式(Immediate mode
,也就是固定渲染管线),这个模式下绘制图形很方便,容易学习和使用,但不够灵活。一旦要进行细节实现,效率低下的弊端就会暴露出来。
因此从OpenGL3.2
开始,规范文档开始废弃立即渲染模式,并鼓励开发者在OpenGL
的核心模式 (Core-profile)下进行开发,这个分支的规范完全移除了旧的特性。
正是因为完全废除了旧特性,所以我们就没法使用已经废弃的函数了,而不像某些语言,废弃只是不建议使用,而废弃函数本身还可以勉强使用
ps:相对于
立即渲染模式
,核心渲染模式
更加灵活高效,但你需要深入理解OpenGL
的渲染流程,因此学习起来更加复杂。
对扩展(Extension)的支持
当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展
的方式在驱动中实现。
开发者可以使用支持此扩展
的显卡来编写更新的代码,只需判断是否支持即可
就像这样
1 | if(GL_ARB_extension_name){ |
OpenGL 是个状态机
OpenGL
的状态通常被称为OpenGL_Context
。我们通常使用如下途径去更改OpenGL
状态:设置选项,操作缓冲。最后,我们使用当前OpenGL_Context
来渲染。
假设当我们想告诉OpenGL
去画线段而不是三角形的时候,我们通过改变一些Context
变量来改变OpenGL
状态,从而告诉OpenGL
如何去绘图。
一旦我们改变了 OpenGL 的状态为绘制线段,下一个绘制命令
就会画出线段而不是三角形。
所以很多人在学习
OpenGL
的时候总是感觉不明白gl
函数的意义,其实本质就是在操作OpenGL
这个状态机
OpenGL 里的对象
OpenGL
库是用C
语言写的,同时也支持多种语言的派生,但其内核仍是一个C
库。
所以OpenGL
里的对象就是C
语言里的结构体。
他表示的就是OpenGL
里的状态小集合
1 | struct object_name { |
而OpenGL
本身就是一个状态的集合
1 | // OpenGL的状态 |
我们操作OpenGL
的时候,就会类似下面这样的操作
1 | unsigned int objectId = 0; |
这一小段代码展现了你以后使用OpenGL
时常见的工作流。
我们首先创建一个对象,然后用一个id
保存它的引用(实际数据被储存在后台)。
然后我们将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET
)。
接下来我们设置窗口的选项。
最后我们将目标位置的对象id
设回0
,解绑这个对象。
设置的选项将被保存在objectId
所引用的对象中,一旦我们重新绑定这个对象到GL_WINDOW_TARGET
位置,这些选项就会重新生效。
代码讲解
这里我以在 Mac 端为例,贴上全部代码,其实就是 OpenGL on Mac 环境 的那些代码
源码
1 |
|
ps: 再次强调,引用的时候
glad
在前glfw
在后,不能反过来
初始化工作
1 | glfwInit(); // init glfw |
这一部分基本算是死代码,所以暂时不需要过多关心
设置 window
1 | GLFWwindow* window = glfwCreateWindow(800, 600,"LearnOpenGL", NULL, NULL); // 800*600 size window called LearnOpenGL |
这里是window
设置,也就是窗口大小(没错就是我们平时看到的 Mac 窗口)
viewport
指的是渲染的区域大小
doc here
1 | /** |
如果指定的viewport
比windows
小,就会出现这样的现象
左下角有一个远远小于窗口大小的图形
1 | glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); |
这是设置framebuffer
回调,framebuffer_size_callback
的定义在下面
1 | void framebuffer_size_callback(GLFWwindow* window, int width, int height){ |
渲染循环
1 | while (!glfwWindowShouldClose(window)){ |
我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。
因此,我们需要在程序中添加一个while
循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW
退出前一直保持运行。
processInput
是检测按键是否是ESC
,定义在这里,glfwSetWindowShouldClose(window, true);
通过把WindowShouldClose
属性设置为true
的方法关闭GLFW
1 | void processInput(GLFWwindow* window){ |
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
这是设置glClearColor
的值,也就是清除颜色缓冲的颜色值,在下一行glClear
调用的时候用的就是我们设置的这个颜色
1 | glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 调glClearColor来设置清空屏幕所用的颜色 |
glfwSwapBuffers(window);
是渲染下一帧的指令
为了渲染的连贯性,opengl
有两个缓冲,当上一帧在前台显示的时候,会在后台继续渲染,然后将后台渲染好的缓冲直接加载到前台变成了前台缓冲,而前台的buffer
变成了后台缓冲,继续渲染后续的帧,就这么不断地交换缓冲,这就叫双缓冲
双缓冲(Double Buffer)
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
glfwPollEvent
:poll
是轮训的意思,意思就是在检查事件,并返回给 OpenGL
1 | glfwPollEvents(); // 检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置) |
放个文档在这
1 | /*This function processes only those events that are already in the event queue and then returns immediately. Processing events will cause the window and input callbacks associated with those events to be called. |
另外有一个翻译插件非常好用,之前也有safari 版本
现在好像只有Chrome 版本
了
1 | glfwTerminate(); |
这个是设置所有资源,我们放在return 0;
的前一行
- Title: OpenGL 窗口
- Author: lucas
- Created at : 2023-08-13 11:13:27
- Updated at : 2024-11-13 10:37:32
- Link: https://darkflamemasterdev.github.io/2023/08/13/OpenGL-窗口/
- License: This work is licensed under CC BY-NC-SA 4.0.