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

目录

一.配置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

引入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.mzph.cn/news/134419.shtml

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

相关文章

远程调用,参数压缩问题

错误信息 { "msg": "Error while extracting response for type [XXX] and content type [application/json;charsetUTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal charac…

Azure 机器学习 - 使用Python SDK训练模型

目录 一、环境准备二、工作区限制三、什么是计算目标&#xff1f;四、本地计算机五、远程虚拟机六、Apache Spark 池七、Azure HDInsight八、Azure Batch九、Azure Databricks十、Azure Data Lake Analytics十一、Azure 容器实例十二、Kubernetes 了解如何用 SDK v1 将 Azure 计…

01-基于IDEA,Spring官网,阿里云官网,手动四种方式创建SpringBoot工程

快速上手SpringBoot SpringBoot技术由Pivotal团队研发制作&#xff0c;功能的话简单概括就是加速Spring程序初始搭建过程和Spring程序的开发过程的开发 最基本的Spring程序至少有一个配置文件或配置类用来描述Spring的配置信息现在企业级开发使用Spring大部分情况下是做web开…

UE5、CesiumForUnreal实现加载GeoJson绘制单面(Polygon)功能(StaticMesh方式)

文章目录 1.实现目标2.实现过程2.1 实现原理2.1.1 数据读取2.1.2 三角剖分2.1.3 创建StaticMesh2.2 应用测试2.2.1 具体代码2.2.2 蓝图应用测试3.参考资料1.实现目标 通过读取本地GeoJson数据,在UE中以StaticMeshComponent的形式绘制出面数据,支持Editor和Runtime环境,GIF动…

【验证码系列】利用深度学习构建字符型验证码自动识别模型与算法

文章目录 1. 写在前面2. CSCI级设计决策2.1. 字符型验证码识别智能体流程关联2.2. 字符型验证码识别行为设计 3. 字符型验证码识别智能体结构设计3.1. 智能体部件组成3.2. 智能体结构 4. 接口设计4.1. 字符型验证码识别智能体交互 5. 智能体算法设计细节5.1. 算法目标5.2. 字符…

使用表单登录方法模拟登录通信人家园,要求发送登录请求后打印出来的用户名下的用户组类别

目标网站&#xff1a;https://www.txrjy.com/forum.php 一、进入网页&#xff0c;右键“检查” 二、输入用户名和密码&#xff0c;点击“登录”&#xff0c;点击“Network”,上划加载项找到蓝色框中的内容 三、点击第一个加载项&#xff0c;找到URL 四、相关代码&#xff1a; …

苍穹外卖-day05

苍穹外卖-day05 课程内容 Redis入门Redis数据类型Redis常用命令在Java中操作Redis店铺营业状态设置 功能实现&#xff1a;营业状态设置 效果图&#xff1a; 选择营业中&#xff0c;客户可在小程序端下单&#xff1a; 选择打烊中&#xff0c;客户无法在小程序端下单&#xf…

git and svn 行尾风格配置强制为lf

git CLI配置&#xff1a; // 提交时转换为LF&#xff0c;检出时转换为CRLF git config --global core.autocrlf true // 提交时转换为LF&#xff0c;检出时不转换 git config --global core.autocrlf input // 提交检出均不转换 git config --global core.autocrlf f…

提升(Hoisting)和暂时死区(TDZ)在实践中的应用

变量提升 ● 首先我们先声明三个变量&#xff0c;并在声明之前去使用这个变量 console.log(me); console.log(job); console.log(year);var me IT知识一享; let job teacher; const year 1991;使用var声明变量&#xff0c;变量声明会被提升到作用域的顶部&#xff0c;但是…

EasyExcel实现动态表头功能

EasyExcel实现动态表头功能 开发过程中&#xff0c;大部分都会使用到导出报表功能&#xff0c;目前阶段会用得有 poi导出&#xff08;暂无&#xff09;&#xff0c; easyexcel导出&#xff08;官方文档&#xff0c;https://easyexcel.opensource.alibaba.com/docs/current/&am…

【C++】特殊类设计+类型转换+IO流

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

c语言实现http下载功能,显示进度条和下载速率

#include <stdio.h>//printf #include <string.h>//字符串处理 #include <sys/socket.h>//套接字 #include <arpa/inet.h>//ip地址处理 #include <fcntl.h>//open系统调用 #include <unistd.h>//write系统调用 #include <netdb.h>//…

机器学习实战:基于sklearn的工业蒸汽量预测

文章目录 写在前面工业蒸汽量预测1.基础代码2.模型训练3.模型正则化4.模型交叉验证5.模型超参空间及调参6.学习曲线和验证曲线 写在后面 写在前面 本期内容&#xff1a;基于机器学习的工业蒸汽量预测 实验环境&#xff1a; anaconda python sklearn 注&#xff1a;本专栏内所有…

K8s学习笔记——认识理解篇

1. K8s诞生背景 回顾应用的部署&#xff0c;经历了以下几个阶段&#xff1a; 传统部署&#xff1a;物理服务器上运行应用程序。虚拟机部署&#xff1a;物理服务器上安装虚拟机&#xff0c;在虚拟机上运行应用程序。容器部署&#xff1a;物理服务器上安装容器运行时&#xff0…

YB4554是一款经济高效、完全集成的高端输入电压单电池锂离子电池充电器。

YB4554 带OVP保护的高输入电压充电器 概述&#xff1a; YB4554是一款经济高效、完全集成的高端输入电压单电池锂离子电池充电器。这个充电器使用锂离子要求的CC/CV充电模式电池充电器可接受高达24V&#xff0c;但当输入电压超过OVP阈值&#xff0c;通常为6.8V&#xff0c;以防…

antdv使用a-cascader联级选择器实现自定义浮层样式

一般的使用组件库想要自定义样式都会使用深度选择器deep去实现 但是有的组件不管是deep还是!important还是写行内样式都改不掉 这里主要讲使用a-cascader联级选择器的浮层改变样式 一&#xff0c;使用组件 <a-cascader:options"regionOptions"change-on-selectv…

30道高频Vue面试题快问快答

面试中的快问快答 快问快答的情景在面试中非常常见。 在面试过程中&#xff0c;面试官通常会使用快问快答的方式来快速评估面试者的基础知识、思维能力和反应速度。 这种情景下&#xff0c;面试官会提出一系列简短的问题&#xff0c;并期望面试者能够迅速做出回答或提供简洁明…

云计算实战项目之---学之思在线考试系统

简介&#xff1a; 学之思开源考试系统是一款 java vue 的前后端分离的考试系统。主要优点是开发、部署简单快捷、界面设计友好、代码结构清晰。支持web端和微信小程序&#xff0c;能覆盖到pc机和手机等设备。 支持多种部署方式&#xff1a;集成部署、前后端分离部署、docker部…

初识rust

调试下rust 的执行流程 参考&#xff1a; 认识 Cargo - Rust语言圣经(Rust Course) 新建一个hello world 程序&#xff1a; fn main() {println!("Hello, world!"); }用IDA 打开exe&#xff0c;并加载符号&#xff1a; 根据字符串找到主程序入口&#xff1a; 双击…

SpringCloud 微服务全栈体系(十二)

第十一章 分布式搜索引擎 elasticsearch 一、初识 elasticsearch 1. 了解 ES 1.1 elasticsearch 的作用 elasticsearch 是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在 GitHub 搜…