😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《LearnOpenGl》系列专栏,相信一份耕耘一份收获,我会分享Opengl相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!
此系列文章为我记录学习Opengl,原文链接,大模型结合费曼学习,若有博客有不恰当之处,还请包涵~
掌握这些基础知识对于深入学习OpenGL和其他图形API至关重要。
1. 引言
1.1 博客目的
本技术博客旨在为读者提供一个深入浅出的OpenGL入门指南,通过实践示例"Hello, Window"来展示如何创建一个基本的OpenGL窗口并进行渲染。我们的目标是让读者理解OpenGL的基本概念,并能够自行构建简单的图形应用程序。
1.2 预期读者
本博客面向所有对计算机图形学感兴趣的初学者,无需深厚的图形学背景。无论您是学生、业余爱好者还是专业开发者,只要您有基本的编程知识,本博客都将引导您走进OpenGL的世界。
2. 窗口创建基础
2.1 GLFW库介绍
GLFW是一个开源的、多平台的库,用于创建窗口、接收输入和处理事件。它提供了一个简单易用的API,使得开发者能够快速地创建和管理窗口,而无需关心底层操作系统的细节。
- 跨平台支持:GLFW支持Windows、macOS和Linux等多个操作系统,为开发者提供了统一的接口来处理不同平台的窗口创建和管理。
- 易于集成:GLFW与OpenGL、DirectX和Vulkan等图形API紧密集成,方便开发者在不同的渲染上下文中使用。
- 事件处理:GLFW能够处理键盘、鼠标等输入设备的事件,使得开发者可以轻松地实现用户交互。
2.2 窗口初始化过程
窗口初始化是OpenGL编程的第一步,以下是使用GLFW进行窗口初始化的基本步骤:
-
初始化GLFW:首先,需要调用
glfwInit
函数来初始化GLFW库。这是创建窗口之前的必要步骤。 -
设置配置:在创建窗口之前,可以通过
glfwWindowHint
函数设置窗口的各种属性,如窗口的初始大小、是否可见、OpenGL的版本等。 -
创建窗口:使用
glfwCreateWindow
函数创建窗口。此函数需要窗口的宽度和高度作为参数,并返回一个GLFWwindow
对象的指针。 -
获取上下文:创建窗口后,需要通过调用
glfwMakeContextCurrent
函数来获取OpenGL上下文,这样才能在窗口上进行渲染。 -
事件循环:在窗口中,需要设置一个事件循环来处理用户的输入和窗口的其他事件。可以通过
glfwSetKeyCallback
等函数设置回调函数来响应不同的事件。 -
渲染循环:在事件循环中,除了处理事件外,还需要进行渲染循环,不断地调用
glfwSwapBuffers
来交换前后缓冲区,实现图像的更新。 -
清理资源:在窗口关闭前,需要调用
glfwTerminate
来清理GLFW分配的资源,确保程序的稳定退出。
通过以上步骤,可以完成一个基本的窗口创建和管理过程,为OpenGL编程打下坚实的基础。
3. OpenGL环境配置
3.1 版本与核心模式设置
OpenGL(Open Graphics Library)是一个跨语言、跨平台的图形API,广泛用于渲染2D和3D矢量图形。在配置OpenGL环境时,首先需要确定使用的版本。OpenGL有多个版本,包括2.x、3.x、4.x等,每个版本都支持不同的功能集。
- 版本选择:选择OpenGL的版本取决于目标平台和所需的功能。例如,OpenGL 3.3及以上版本引入了核心模式(Core Profile),它只包含现代OpenGL的特性,不包括旧的遗留特性。
- 核心模式:核心模式设置是为了确保应用程序使用OpenGL的最新特性,避免使用已废弃的函数,从而提高性能和兼容性。在创建OpenGL上下文时,可以通过设置属性来请求核心模式。
3.2 兼容性与前向兼容
兼容性是OpenGL环境配置中的另一个重要考虑因素。
- 兼容性概念:OpenGL的兼容性模式(Compatibility Profile)包括了所有旧版本的特性,适合需要在旧硬件上运行的应用程序。
- 前向兼容:前向兼容(Forward-Compatible)意味着应用程序使用的特性集是面向未来的,不依赖于旧的扩展或特性。这通常与核心模式结合使用,以确保应用程序在新的硬件和驱动上能够运行。
- 配置示例:在使用现代OpenGL(3.0及以上版本)时,可以通过设置请求核心模式,例如在Windows上使用WGL_CONTEXT_CORE_PROFILE_BIT属性,而在macOS上使用NSOpenGLPFAOpenGLProfile属性。
正确配置OpenGL环境对于开发高性能和可移植的图形应用程序至关重要。通过选择适当的版本和模式,开发者可以确保应用程序利用最新的图形技术,同时保持与未来硬件和操作系统的兼容性。
4. 窗口对象与上下文
4.1 窗口对象的创建与管理
窗口对象是图形用户界面中的基本构成元素,为用户提供了一个交互界面。在OpenGL中,窗口对象的创建和管理通常依赖于第三方库,例如GLFW或SDL。
- 第三方库的作用:简化窗口和上下文的创建过程,提供跨平台的兼容性。
- 创建流程:初始化库、设置窗口参数、创建窗口、获取上下文。
4.2 上下文(Context)的概念与重要性
在OpenGL中,上下文定义了渲染状态和GLSL程序的执行环境。每个窗口对象都需要一个上下文来执行渲染命令。
- 上下文的作用:存储OpenGL状态、纹理、缓冲区等资源信息。
- 上下文的创建:通过第三方库函数创建,与窗口对象关联。
- 上下文的激活:在渲染前必须激活相应的上下文,以确保渲染命令的正确执行。
4.3 窗口与上下文的交互
窗口对象和上下文之间的关系密切,窗口提供了渲染的画布,而上下文则定义了在该画布上如何渲染。
- 渲染流程:设置窗口大小、创建着色器、编写顶点数据、绘制图形。
- 事件处理:监听用户输入,如键盘和鼠标事件,对渲染过程进行控制。
- 双缓冲技术:为了避免画面撕裂,大多数窗口系统使用双缓冲技术,即在一个缓冲区进行渲染,同时在另一个缓冲区显示结果。
4.4 窗口的关闭与资源释放
在应用程序结束时,需要正确关闭窗口并释放与窗口相关的资源,以避免内存泄漏和其他资源浪费。
- 关闭窗口:调用第三方库提供的关闭窗口函数。
- 资源释放:释放与窗口关联的所有资源,包括纹理、缓冲区等。
- 库的清理:在所有窗口关闭后,清理并卸载第三方库。
5. GLAD函数指针加载器
GLAD是一个用于加载OpenGL函数指针的库,它允许开发者以一种更为现代和跨平台的方式使用OpenGL。
5.1 GLAD初始化
初始化GLAD是开始使用OpenGL的第一步,以下是初始化GLAD的一般步骤:
-
安装GLAD:首先,需要在你的项目中包含GLAD库。这可以通过多种方式完成,例如使用包管理器或直接下载源码。
-
包含GLAD:在你的代码中,需要包含GLAD的头文件,通常如下所示:
#include <glad/glad.h>
-
创建OpenGL上下文:在初始化GLAD之前,必须创建一个OpenGL上下文。这可以通过多种窗口创建库来完成,比如GLFW或SDL。
-
调用
gladLoadGLLoader
:一旦创建了OpenGL上下文,就可以使用GLAD提供的函数来加载所有的OpenGL函数指针。通常如下调用:if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { // 初始化失败的处理 }
-
验证OpenGL版本:在GLAD成功加载后,可以通过查询OpenGL版本来确认其可用性:
const GLubyte* renderer = glGetString(GL_RENDERER); // 获取渲染器信息 const GLubyte* version = glGetString(GL_VERSION); // 获取OpenGL版本
-
使用OpenGL功能:加载完成后,就可以开始使用OpenGL的所有功能了。例如,可以设置视口、清屏颜色等:
glViewport(0, 0, 800, 600); // 设置视口大小 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清屏颜色为深蓝色
-
渲染循环:在初始化GLAD并设置好OpenGL状态后,可以进入渲染循环,开始绘制图形:
while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); // 清屏 // 绘制图形的代码 glfwSwapBuffers(window); // 交换前后缓冲区 glfwPollEvents(); // 处理事件 }
通过以上步骤,可以成功地初始化GLAD并开始使用OpenGL进行图形编程。
6. 视口设置与回调函数
在OpenGL中,视口设置和回调函数是渲染流程中的重要组成部分。视口定义了最终图像在窗口中的显示区域,而回调函数则允许开发者处理各种事件,如键盘和鼠标输入。
6.1 视口的概念与设置
视口是OpenGL中的一个抽象概念,它将逻辑坐标映射到窗口的像素坐标。在进行渲染之前,必须正确设置视口,以确保图像能够正确显示。
- 视口的创建:视口的创建涉及到设置其宽度和高度,这通常与窗口的尺寸相匹配。OpenGL允许开发者通过
glViewport
函数来定义视口的大小和位置。 - 视口的作用:视口不仅决定了图像的显示区域,还影响了深度测试和裁剪等操作。因此,正确设置视口对于实现预期的渲染效果至关重要。
6.2 回调函数的作用与实现
回调函数是OpenGL中处理事件的一种机制。通过注册回调函数,开发者可以响应键盘、鼠标等输入事件,从而实现交互式渲染。
- 键盘回调函数:通过注册键盘回调函数,可以捕获键盘输入,并根据输入执行相应的操作,如移动物体、切换视角等。
- 鼠标回调函数:鼠标回调函数允许开发者捕获鼠标的移动、点击等事件,并据此调整摄像机位置或触发其他操作。
- 回调函数的注册:在GLFW中,可以通过
glfwSetKeyCallback
和glfwSetCursorPosCallback
等函数来注册键盘和鼠标的回调函数。
6.3 视口与回调函数的结合使用
在实际应用中,视口设置和回调函数通常结合使用,以实现动态的渲染效果和用户交互。
- 动态调整视口:在回调函数中,可以根据用户的输入动态调整视口的大小和位置,从而实现不同的视图效果。
- 事件驱动的渲染:通过回调函数捕获的事件来触发渲染流程,可以实现基于事件的渲染控制,提高渲染的灵活性和响应性。
6.4 实例分析
在OpenGL的学习过程中,通过具体的实例来理解视口设置和回调函数的应用是非常有帮助的。
- 示例代码:展示如何使用
glViewport
设置视口,以及如何通过glfwSetKeyCallback
注册键盘回调函数。 - 效果展示:通过运行示例代码,展示视口设置和回调函数在实际渲染中的效果,加深对概念的理解。
通过上述内容的详细阐述,可以为读者提供一个全面而深入的视角,理解视口设置与回调函数在OpenGL渲染流程中的重要性和应用方法。
7. 渲染循环与双缓冲
渲染循环是OpenGL程序的核心,它是一个持续运行的循环,不断地处理输入、更新场景并渲染图像。双缓冲技术则用于避免图像撕裂和闪烁,提供平滑的视觉体验。
7.1 渲染循环
渲染循环是OpenGL程序的心脏,它负责不断地执行以下步骤:
- 处理输入事件:如键盘、鼠标等,这些事件可能会影响场景的状态或摄像机的位置。
- 更新场景:根据物理引擎或用户输入更新对象的位置、旋转等属性。
- 渲染场景:清除屏幕,设置视图,绘制场景中的所有对象。
渲染循环通常包含以下代码结构:
while (!glfwWindowShouldClose(window)) {
// 处理输入事件
processInput(window);
// 渲染
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
renderScene();
// 交换缓冲区
glfwSwapBuffers(window);
// 轮询和处理事件
glfwPollEvents();
}
7.2 双缓冲
双缓冲是一种用于减少图像撕裂和闪烁的技术。它涉及两个缓冲区:前缓冲区和后缓冲区。前缓冲区是当前显示在屏幕上的图像,而后缓冲区是下一个要渲染的图像。
- 前缓冲区:当前显示的图像。
- 后缓冲区:下一个要渲染的图像。
在渲染循环中,我们首先在后缓冲区中渲染图像,然后交换两个缓冲区,使得后缓冲区的内容显示在屏幕上。这个过程可以减少图像在渲染过程中被部分更新的情况,从而避免撕裂和闪烁。
双缓冲的实现
在OpenGL中,双缓冲通常是通过调用glClear(GL_COLOR_BUFFER_BIT)
来实现的,它会清除当前绑定的帧缓冲区的内容。然后,我们绘制场景,最后通过glfwSwapBuffers(window)
交换缓冲区。
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清除颜色
glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区
renderScene(); // 渲染场景
glfwSwapBuffers(window); // 交换缓冲区
双缓冲的优势
双缓冲的主要优势在于:
- 减少撕裂:通过在后缓冲区渲染,我们可以在不影响当前显示图像的情况下更新图像。
- 减少闪烁:避免了在渲染过程中部分更新图像,从而减少了闪烁。
- 提高视觉体验:提供平滑、连贯的图像更新,增强了用户体验。
通过以上步骤,我们可以确保OpenGL程序能够以稳定和高效的方式渲染图像,同时提供高质量的视觉体验。
8. 输入处理与事件
在图形编程中,输入处理和事件管理是实现交互性的关键组成部分。以下是对这一主题的深入探讨。
8.1 输入设备与数据获取
输入设备,如键盘、鼠标、触摸板等,提供了用户与应用程序之间的交互手段。每种输入设备都能够生成特定的输入事件,例如键盘的按键事件、鼠标的点击或移动事件。
- 键盘输入:可以通过监听特定的按键状态(按下、释放)来实现文本输入或快捷键功能。
- 鼠标输入:包括位置信息、滚轮滚动以及各个按钮的点击状态,常用于3D视图中的相机控制和对象选择。
8.2 事件处理机制
事件处理机制允许应用程序响应用户的操作。在OpenGL中,事件通常由窗口系统管理,并传递给应用程序。
- 事件队列:操作系统通常会维护一个事件队列,应用程序需要定期检查并处理队列中的事件。
- 事件分发:应用程序接收到事件后,根据事件类型和内容将其分发给相应的处理函数。
8.3 事件驱动的编程模型
事件驱动的编程模型是一种异步处理方式,它允许应用程序在不阻塞主线程的情况下响应用户输入。
- 回调函数:为不同的事件类型定义回调函数,当事件发生时,相应的回调函数被调用。
- 事件监听器:在对象中注册事件监听器,当事件发生时,监听器会被通知并执行相应的操作。
8.4 处理常见输入事件
理解和处理常见的输入事件是创建交互式应用程序的基础。
- 按键事件:处理键盘输入,实现如移动、跳跃等控制功能。
- 鼠标事件:处理鼠标的位置变化、点击和释放,实现视角的旋转和平移。
- 触摸事件:对于触摸屏设备,处理多点触控事件,实现缩放、旋转等手势操作。
8.5 集成第三方库
在OpenGL开发中,经常使用第三方库来简化输入处理和事件管理。
- GLFW:一个开源的库,提供了创建窗口、上下文管理和事件处理的功能。
- SDL:另一个流行的库,除了窗口和事件管理,还提供了音频处理和文件I/O等功能。
8.6 性能考虑
输入处理和事件响应的速度直接影响用户体验。
- 事件处理的优化:确保事件处理逻辑尽可能高效,避免在主线程中执行耗时操作。
- 异步事件处理:考虑使用异步事件处理机制,减少对主渲染循环的影响。
8.7 安全性与异常处理
处理用户输入时,需要考虑到安全性和异常情况。
- 输入验证:对所有用户输入进行验证,防止恶意数据导致程序崩溃或安全漏洞。
- 异常捕获:妥善处理事件处理过程中可能出现的异常,确保程序的稳定性。
通过上述分析,我们可以看到输入处理与事件管理在图形编程中的重要性,以及实现高效、稳定和安全的用户交互所需的关键技术和策略。
9. 渲染操作与清屏
渲染操作是图形编程的核心部分,它负责将我们创建的图像或3D模型显示在屏幕上。在OpenGL中,这个过程涉及到几个关键步骤,包括设置视口、清屏、以及调用绘图命令。
9.1 设置视口
视口(Viewport)定义了渲染图像在窗口中的显示区域。在OpenGL中,视口是一个矩形区域,其大小和位置可以通过glViewport
函数设置。通常,视口的大小与窗口的大小相匹配,但也可以调整以实现特定的渲染效果。
9.2 清屏操作
在每次渲染循环之前,需要清除屏幕上的图像,以便为新的渲染内容腾出空间。OpenGL提供了几种清屏选项,包括清除颜色缓冲区、深度缓冲区和模板缓冲区。使用glClear
函数可以一次性清除多个缓冲区。
9.3 绘制调用
绘制调用是OpenGL中实际执行渲染操作的命令。OpenGL提供了多种绘制模式,如点、线、三角形等。根据需要绘制的几何体类型,可以使用glDrawArrays
或glDrawElements
函数来执行绘制操作。
9.4 交换缓冲区
在双缓冲模式下,OpenGL使用两个缓冲区:前缓冲区和后缓冲区。渲染操作首先在后缓冲区进行,完成后通过交换缓冲区的操作将后缓冲区的内容显示在屏幕上。这个过程可以通过调用glutSwapBuffers
(在使用GLUT的情况下)或其他交换缓冲区的函数来实现。
9.5 渲染循环
渲染循环是图形应用程序的心脏,它不断重复执行渲染操作,以更新屏幕上的图像。在每次循环中,通常包括处理输入事件、更新场景状态、执行渲染操作、清屏和交换缓冲区等步骤。
效果图
我的源码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void proessInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
}
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, "Worker Duan", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW Window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "failed to initialized GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//while (!glfwWindowShouldClose(window))
//{
// glfwSwapBuffers(window);
// glfwPollEvents();
//}
while (!glfwWindowShouldClose(window))
{
proessInput(window);
glClearColor(0.2f, 0.7f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~
🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经。
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~