【LearnOpenGL基础入门——2】搭建第一个OpenGL窗口

news2024/11/25 22:26:29

目录

一.配置GLFW

二.配置GLAD

三.第一个OpenGL窗口

3.1 GLFW设置

3.2 GLAD设置

3.3 视口

3.4 输入

3.5渲染


在我们画出出色的效果之前,首先要做的就是创建一个OpenGL上下文(Context)和一个用于显示的窗口。然而,这些操作在每个系统上都是不一样的,OpenGL有意将这些操作抽象(Abstract)出去。这意味着我们不得不自己处理创建窗口,定义OpenGL上下文以及处理用户输入。

幸运的是,有一些库已经提供了我们所需的功能,其中一部分是特别针对OpenGL的。这些库节省了我们书写操作系统相关代码的时间,提供给我们一个窗口和一个OpenGL上下文用来渲染。最流行的几个库有GLUT,SDL,SFML和GLFW。在教程里我们将使用GLFW。你可以随意选用其他类似的库,大多数库的配置方法和GLFW差不多

一.配置GLFW

GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入,对我们来说这就够了。GLFW可以从它官方网站的下载页上获取。GLFW已提供为Visual Studio(2012到2019都有)预编译好的二进制版本和相应的头文件,我们下载它的源码后需要进行编译,编译成功后可以得到它的头文件以及相关的库文件:

接下来新建我们的第一个OpenGL工程,打开Visual Studio ,创建一个新的“Hellow Word”程序。并进行GLFW的配置,右键工程【属性】——【C/C++:常规页”】选择附加包含目录,引入上一步编译的GLFW库的头文件目录。操作如下图所示:

接下来进行链接库目录的配置,在【连接器:常规】下选择附加目录库,选择上一步编译的GLFW库的静态库目录,进行配置如下图所示:

 最后进行链接库的配置,选择【连接器:输入】下选择附加依赖项,添加编译的glfw3.lib,和系统的Opengl32.lib,配置如下:

到此,GLFW的配置完成。

二.配置GLAD

GLAD是一个开源的库,它能解决我们上面提到的那个繁琐的问题。GLAD的配置与大多数的开源库有些许的不同,GLAD使用了一个在线服务。在这里我们能够告诉GLAD需要定义的OpenGL版本,并且根据这个版本加载所有相关的OpenGL函数。

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

GLFW和GLAD编译好的库可以在这里下载直接使用:

引入GLFW和glad头文件,进行编译测试:

至此,OpenGl环境配置完成。

三.第一个OpenGL窗口

3.1 GLFW设置

接下来我们创建main函数,在这个函数中我们将会实例化GLFW窗口:

首先我们进行glfw 初始化和配置,实现如下:

// glfw 初始化和配置
	// ------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

我们在main函数中调用glfwInit函数来初始化GLFW,然后我们可以使用glfwWindowHint函数来配置GLFW。glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。该函数的所有的选项以及对应的值都可以在 GLFW’s window handling 这篇文档中找到。

接下来我们创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。

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

// glfw 窗口创建
	// --------------------
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "【菜鸟驿站】LearnOpenGL  第一个OPenGL窗口", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数。第三个参数表示这个窗口的名称(标题),这里我们使用"【菜鸟驿站】LearnOpenGL  第一个OPenGL窗口",当然你也可以使用你喜欢的名称。最后两个参数我们暂时忽略。这个函数将会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。

3.2 GLAD设置

在之前的教程中已经提到过,GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。

// glad: load all OpenGL function pointers
	// ---------------------------------------
	//GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是glfwGetProcAddress,它根据我们编译的系统定义了正确的函数。

3.3 视口

在我们开始渲染之前还有一件重要的事情要做,我们必须告诉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会调用这个函数并填充相应的参数供你处理。

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height); //glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
}

我们还需要注册这个函数,告诉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();

3.4 输入

创建一个processInput函数来实现键盘输入响应事件:

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
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();
}

3.5渲染

 我们要把所有的渲染(Rendering)操作放到渲染循环中,因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行。代码将会是这样的:

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

    // 渲染指令
    ...

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

为了测试一切都正常工作,我们使用一个自定义的颜色清空屏幕。在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果(这可能是你想要的效果,但通常这不是)。我们可以通过调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。由于现在我们只关心颜色值,所以我们只清空颜色缓冲。

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

编译运行程序,渲染出第一个窗口:

附完整代码如下:

#include <glad/glad.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 初始化和配置
	// ------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);


	// glfw 窗口创建
	// --------------------
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "【菜鸟驿站】LearnOpenGL  第一个OPenGL窗口", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数

	// glad: load all OpenGL function pointers
	// ---------------------------------------
	//GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// render loop 渲染循环
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// input
		// -----
		processInput(window);

		// render
		// ------
		glClearColor(0.2f, 0.6f, 0.2f, 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)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height); //glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1173548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Swift 和 Python 两种语言中带关联信息错误(异常)类型的比较

0. 概览 如果我们分别在平静如水、和谐感人的 Swift 和 Python 社区抛出诸如“Python 是天下最好的语言…” 和 “Swift 是宇宙第一语言…”之类的言论会有怎样的“下场”&#xff1f; 我们并不想对可能发生的“炸裂”景象做出什么预测&#xff0c;也无意比较 Swift 与 Pytho…

[pytorch]手动构建一个神经网络并且训练

0.写在前面 上一篇博客全都是说明类型的,实际代码能不能跑起来两说,谨慎观看.本文中直接使用fashions数据实现softmax的简单训练并且完成结果输出.实现一个预测并且观测到输出结果. 并且更重要的是,在这里对一些训练的过程,数据的形式,以及我们在softmax中主要做什么以及怎么…

14.1 Linux 并发与竞争

一、并发与竞争 并发&#xff1a;多个执行单元同时、并行执行。 竞争&#xff1a;并发的执行单元同时访问共享资源(硬件资源和软件上的全局变量等)易导致竞态。 二、原子操作 1. 原子操作简介 原子操作&#xff1a;不能再进一步分割的操作&#xff0c;一般用于变量或位操作。 …

关于iOS:如何使用SwiftUI调整图片大小?

How to resize Image with SwiftUI? 我在Assets.xcassets中拥有很大的形象。 如何使用SwiftUI调整图像大小以缩小图像&#xff1f; 我试图设置框架&#xff0c;但不起作用&#xff1a; 1 2 Image(room.thumbnailImage) .frame(width: 32.0, height: 32.0) 在Image上应用…

数二真题强化

高等数学 定积分 变上限积分求导 被积函数不能含有x&#xff0c;用换元法 线性代数

20.7 OpenSSL 套接字SSL加密传输

OpenSSL 中的 SSL 加密是通过 SSL/TLS 协议来实现的。SSL/TLS 是一种安全通信协议&#xff0c;可以保障通信双方之间的通信安全性和数据完整性。在 SSL/TLS 协议中&#xff0c;加密算法是其中最核心的组成部分之一&#xff0c;SSL可以使用各类加密算法进行密钥协商&#xff0c;…

leetcode-887-鸡蛋掉落(包含最大值最小化,最小值最大化的二分优化+滚动数组的原理)

这里写目录标题 题意解题KNN复杂度DP解法思想&#xff08;超时&#xff09;上述方法的优化 &#xff08;最大值最小化二分优化&#xff09;完整代码 逆向思维的DP代码空间优化&#xff08;滚动数组&#xff09;代码 题意 链接&#xff1a;leetcode-887-鸡蛋掉落 给你 k 枚相同…

AD CS证书攻击与防御:ESC1

简介 2021年的BlackHat大会上&#xff0c;Will Schroeder和Lee Christensen发布了关于Active Directory Certificate Services 利用白皮书《Certified Pre-Owned - Abusing Active Directory Certificate Services》。 攻击对象为AD CS&#xff0c;攻击手法主要是利用证书模版…

【算法专题】双指针—和为s的两个数

一、题目解析 只需在这个数组中找出两个数相加等于target即可 二、算法原理 1、暴力解法&#xff08;时间复杂度&#xff1a;O(n^2)&#xff09; 两个for循环嵌套遍历这个数组即可&#xff0c;不过会超时 class Solution { public:vector<int> twoSum(vector<int&…

开启AWS的ubuntu服务器的root用户登录权限

设置root用户密码 输入以下命令修改root用户密码 sudo passwd root输入以下命令切换到root用户 su root仅允许root用户用密码登录 输入以下命令编辑ssh配置文件 vi /etc/ssh/sshd_config新增以下配置允许root用户登录 PermitRootLogin yes把PasswordAuthentication修改为…

latex设置图片的位置

Latex提供了一些命令来控制图片的位置。我们可以通过使用\begin{figure}[位置选项]来控制图片的位置。位置选项可以有h、t、b、p、!这五个&#xff0c;分别表示以下含义&#xff1a; h:表示放在当前位置&#xff0c;不过有时由于论文的格式限制&#xff0c;可能放不下。 t:表示…

详解IPD需求分析工具$APPEALS

够让企业生存下去的是客户&#xff0c;所以&#xff0c;众多企业提出要“以客户为中心”&#xff0c;那如何做到以客户为中心&#xff1f;IPD中给出的答案是需求管理。 需求管理流程&#xff0c;是IPD&#xff08;集成管理开发&#xff09;体系中的四大支撑流程之一&#xff0…

Transformer的最简洁pytorch实现

目录 前言 1. 数据预处理 2. 模型参数 3. Positional Encoding 4. Pad Mask 5. Subsequence Mask 6. ScaledDotProductAttention 7. MultiHeadAttention 8. FeedForward Networks 9. Encoder Layer 10. Encoder 11. Decoder Layer 12. Decoder 13. Transformer 1…

看!MySQL 8.2 数据库支持读写分离啦!

更多文章&#xff0c;欢迎关注作者公众号&#xff0c;欢迎一起交流。 MySQL 8.2.0创新版本已于2023-10-17发布&#xff0c;MySQL Router 8.2 支持数据库的读/写分离&#xff0c;这里将在InnoDB Cluster集群中演示数如何进行读写分离&#xff0c;本篇内容包括&#xff1a;MySQL …

【数字三角形】

题目描述 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径&#xff0c;把路径上面的数加起来可以得到一个和&#xff0c;你的任务就是找到最大的和。 路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右 边的那个数。此外…

逆向学习记录(1)windows系统基本配置

我的环境&#xff1a;win10系统64位。 可以在虚拟机上操作。改天记录一下如何安装虚拟机及里面的系统。 1、查看windows版本情况 按下winr&#xff0c;输入winver&#xff0c;即可。 2、新装的windows系统&#xff0c;桌面上没有我的电脑图标&#xff1f; 在桌面上右键&…

创建基于多任务的并发服务器

有几个请求服务的客户端&#xff0c;我们就创建几个子进程。 这个过程有以下三个阶段&#xff1a; 这里父进程传递的套接字文件描述符&#xff0c;实际上不需要传递&#xff0c;因为子进程会复制父进程拥有的所有资源。 #include <stdio.h> #include <stdlib.h>…

1822_使用python内置的库进行日期序列的生成

使用python的内置的库进行日期序列的生成 用到的库介绍 datetime 实现这样的功能其实只需要这一个库就够了&#xff0c;但是网络上找到的例程很多都额外增加了对time库的引用。只能说&#xff0c;这样不会出现错误&#xff0c;但是这样肯定会有一些计算资源上的消耗。 #!/u…

2023年【山东省安全员C证】考试技巧及山东省安全员C证模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 山东省安全员C证考试技巧考前必练&#xff01;安全生产模拟考试一点通每个月更新山东省安全员C证模拟试题题目及答案&#xff01;多做几遍&#xff0c;其实通过山东省安全员C证模拟考试题很简单。 1、【多选题】《环境…

Leetcode-283 移动零

count记录0的个数&#xff0c;不为0的数取代0位置&#xff0c;最后把剩余位置置零 class Solution {public void moveZeroes(int[] nums) {int count 0;for(int i0;i<nums.length;i){if(nums[i]0){count;}else{nums[i-count]nums[i];}}for(int inums.length-count;i<nu…