[OpenGL]使用OpenGL绘制三角形

server/2024/9/20 4:44:52/ 标签: opengl, c++

一、简介

本文介绍了如何在linux/win(wsl2)环境下,使用GLFW+GLAD实现绘制三角形。
本文内容基本根据LearnOpengGL-入门-你好,三角形整理完成,读者也可以参考LearnOpengGL-入门-你好,三角形自行学习如何使用OpenGL绘制三角形。

按照本文一步一步实现后,最终会得到以下结果:

Hello triangle

二、使用OpenGL绘制三角形

0. 环境需要

  • Linux 或者 windos下使用wsl2
  • 安装GLFW和GLAD,请参考[OpenGL] wsl2上安装使用cmake+OpenGL教程

1. 项目文件夹结构

文件夹结构

2. CMakeLists.txt 代码

cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 14)project(OpenGL_Hello_Triangle)include_directories(include)find_package(glfw3 REQUIRED)
file(GLOB project_file main.cpp glad.c)
add_executable(${PROJECT_NAME} ${project_file})
target_link_libraries(${PROJECT_NAME} glfw)

3. main.cpp 代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>#include <iostream>
// 用于处理窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
// 用于处理用户输入的函数
void processInput(GLFWwindow *window);// 指定窗口默认width和height像素大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
/*-----vertex shader 和 fragment shader 代码-----*/
// 若对glsl编写的shader程序不了解,可以忽略此处的代码// vertex shader, 用于处理各顶点的位置信息,此处的vertex shader不会对顶点位置进行操作
// 只需要记住此处我们将顶点position数据的 location 设为 0
// layout (location = 0) in vec3 aPos; 这句代码用来设置用到的 顶点 position 数据的location值
const char *vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 aPos;\n""void main()\n""{\n""   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n""}\0";
// fragment shader,用于处理各顶点的颜色信息,此处的fragment shader将各顶点的颜色(r,g,b,alpha)设置为
// (1.0f,0.5f,0.2,1.0f)
const char *fragmentShaderSource = "#version 330 core\n""out vec4 FragColor;\n""void main()\n""{\n""   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""}\n\0";
/*--------------------------------------------*/int main()
{// glfw 初始化 + 配置 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", NULL, NULL);if (window == NULL){// 检查是否成功生成窗口,如果没有成功打印出错信息并且退出std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// 设置窗口window的上下文glfwMakeContextCurrent(window);// 配置window变化时的回调函数glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// 使用 glad 加载 OpenGL 中的各种函数if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}/* 构建,编译 shader 程序 */// vertex shaderunsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// 检查是否成功编译 vertex shaderint success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// fragment shaderunsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// 检查是否成功编译 fragment shaderglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// 链接 shadersunsigned int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// 检查是否成功链接 vertex shader 和 fragment shaderglGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);/* 设置待绘制的三角形的数据 */// ------------------------------------------------------------------// 三角形 positionfloat vertices[] = {0.0f,  0.5f,  0.0,  // 上-0.5f, -0.5f, 0.0f, // 左下0.5f,  -0.5f, 0.0f  // 右下};// 三角形 index// 我们只需要绘制一个三角形,三角形的三个点的position分别使用 vertices 数组中的第0,第1和第2个点的position数据unsigned int indices[] = {0, 1, 2 // first Triangle};/* 初始化 VBO, VAO 和 EBO */// 1. VBO 用来打包三角形各顶点的所有数据(比如position, normal, color等。本文中只用到了position数据,即// vertices数组),并将打包好的数据一起传给GPU。// 2. EBO 用来打包(存储)待绘制的三角形的索引数据,即 indices 数组,用于指定顶点的顺序。// 3. VAO 用于记录顶点属性的格式,即如何从 VBO中提取数据。并存储了EBO数据,从而管理顶点数据的输入配置。// 生成 VBO, VAO 和 EBOunsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO); // 生成一个VAO对象glGenBuffers(1, &VBO);      // 生成一个VBO对象(VBO对象本质上是一个buffer)glGenBuffers(1, &EBO);      // 生成一个EBO对象(EBO对象本质上也是一个buffer)// 使用 VBO, VAO 和 EBO 的顺序为// 先绑定 VAO -> 绑定 VBO -> 设置如何读取VBO中数据 -> 绑定 EBO -> 解绑 VAO -> 解绑 VBOglBindVertexArray(VAO); // 绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBOglBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices,GL_STATIC_DRAW); // 将vertices中的数据复制到刚刚绑定的VBO buffer中去,VBO buffer是GPU内存上的一块区域glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); // 绑定EBOglBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,GL_STATIC_DRAW); // 将indices中的数据复制到刚刚绑定的EBO buffer中去,EBO buffer是GPU内存上的一块区域// 使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据// glVertexAttribPointer() 需要6个参数,每个参数的含义如下:// 1. 指定要配置的顶点属性, 即 shader 中指定 数据 location 值,此处填入 0// 2. 指定顶点属性的大小,shader中我们将VBO中的数据传给了一个 vec3 类型的变量 aPos,vec3 是一个包含3个float变量的// vector,因此此处填入 3// 3. 指定数据的类型,用于使用的是浮点型,因此此处填入 GL_FLOAT// 4. 指定是否自动对数据进行归一化,我们不需要自动诡异化,因此此处填入 GL_FALSE// 5.// 指定VBO中数据的步长,即连续的顶点属性组之间的间隔,因为我们的VBO(vertices数组)中的数据紧密相连,每组数据在3*float之后,因此此处填入// 3*sizeof(float)// 6. 指定读取VBO(vertices数组)的起始位置偏移,由于位置数据在数组的开头,所以这里填入 0glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); // 设置如何读取VBO中数据glEnableVertexAttribArray(0); // 启用 location = 0 处的数据,因此该函数的参数为 0glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑VBO// remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO;// keep the EBO bound.// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);// 注意!// 在解绑 VAO 之前不要解绑 EBO (但是可以解绑VBO)// 这是因为 VAO// 只是存储了如何读取VBO中数据的信息,当调用glVertexAttribPointer()函数之后VAO中就有了如何读取VBO数据的配置,而绑定EBO确是实实在在地将EBO数据存储到VAO中,因此在解绑VAO之前不能解绑EBOglBindVertexArray(0); // 解绑 VAO// 绘制主循环while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 使用我们之前编译连接好的 shader 程序glUseProgram(shaderProgram);glBindVertexArray(VAO); // 绑定VAO,指定当前绘制使用的VAO对象// glDrawArrays(GL_TRIANGLES, 0, 6);// 使用glDrawElements()函数指定使用基于EBO索引的绘制,函数参数如下:// 1. 指定绘制的模式,绘制模式选择绘制三角形,因此填入 GL_TRIANGLES// 2. 指定绘制的顶点个数,我们只需要绘制一个三角形,3个点,因此填入 3// 3. 指定绘制的索引类型,即EBO中的数据类型,类型为unsigned int,因此填入 GL_UNSIGNED_INT// 3. 指定绘制的索引起始偏移,此处填入 0glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);glfwSwapBuffers(window); // 在gfw中启用双缓冲,确保绘制的平滑和无缝切换glfwPollEvents(); // 用于处理所有挂起的事件,例如键盘输入、鼠标移动、窗口大小变化等事件}// 释放之前申请的 VBO, VAO, EBO资源和shader程序glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteProgram(shaderProgram);// glfw 释放 glfw使用的所有资源glfwTerminate();return 0;
}// 用于处理用户输入的函数
void processInput(GLFWwindow *window)
{// 当按下 Esc 按键时调用 glfwSetWindowShouldClose() 函数,关闭窗口if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// 在使用 OpenGL 和 GLFW 库时,处理窗口大小改变的回调函数
// 当窗口大小发生变化时,确保 OpenGL 渲染的内容能够适应新的窗口大小,避免图像被拉伸、压缩或出现其他比例失真的问题
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{glViewport(0, 0, width, height);
}

4. 编译运行及结果

编译运行:

cd ./build
cmake ..
make 
./OpenGL_Hello_Triangle

绘制结果:

绘制结果

三、参考引用

[1]. LearnOpenGL-CN-入门-你好,三角形


http://www.ppmy.cn/server/116909.html

相关文章

表格多列情况下,loading不显示问题

问题描述&#xff1a; 用element plus 做得表格&#xff0c;如下图&#xff0c;列数较多&#xff0c;且部分表格内容显示比较复杂&#xff0c;数据量中等的情况下&#xff0c;有一个switch 按钮&#xff0c;切换部分列的显示和隐藏&#xff0c;会发现&#xff0c;切换为显示的时…

网络安全-intigriti-0422-XSS-Challenge Write-up

目录 一、环境 二、解题 2.1看源码 一、环境 Intigriti April Challenge 二、解题 要求&#xff1a;弹出域名就算成功 2.1看源码 我们看到marge方法&#xff0c;肯定是原型链污染题目 接的是传参&#xff0c;我们可控的点在于qs.config和qs.settings&#xff0c;这两个可…

3.3k star开源的Notepad++文本编辑器替代品,跨平台

1 简介 notepad作者在软件readme中有不当言论&#xff0c;之前公司就让强制卸载掉了&#xff0c;对于习惯了实用notepad的属实不方便&#xff0c;前段时间有一篇推荐notepad next的&#xff0c;使用起来也不错&#xff0c;今天推荐一款新的替代品&#xff0c;notepad–。 采用…

已配置好的Linux CentOS7虚拟机转换为可视化界面问题

一、发现问题 学习过程中发现可视化界面比较有意思&#xff0c;就想尝试搞一下看看&#xff0c;于是去网站上搜索&#xff0c;看到的一些是在新建虚拟机的时候进行设置的&#xff0c;我尝试跟着步骤去搞&#xff0c;发现其中最关键的一步&#xff0c;软件选择中&#xff0c;没有…

Request Response

1 前言 1.1 内容概要 理解Request、Response和HTTP报文之间的关系掌握通过Request能够获得的信息 请求URL、URI、请求协议请求头、客户机和主机请求参数 掌握通过Response能够完成的设置 响应中文乱码问题响应&#xff08;Json&#xff09;字符串、图片&#xff08;文件&a…

应用层简单实现udp / tcp网络通信

一、常见网络接口总结 1、创建 socket 文件描述符 (TCP/UDP, 客户端 服务器) int socket(int domain, int type, int protocol); domain&#xff1a;AF_INET&#xff1a;网络通信&#xff0c;AF_LOCAL&#xff1a;本地通信 type&#xff1a;UDP&#xff1a;SOCK_DGRAM&…

QXlsx编译静态库-配置为Qt模块

Qt读写Excel–QXlsx编译为静态库-配置为Qt模块&#x1f346; 文章目录 Qt读写Excel--QXlsx编译为静态库-配置为Qt模块&#x1f346;[toc]1、概述&#x1f954;2、准备工作&#x1f955;3、配置环境&#x1f33d;4、加载QXlsx静态库&#x1f952; &#x1f449;QXlsx使用&#x…

Spring MVC的异步模式(ResponseBodyEmitter、SseEmitter、StreamingResponseBody)

在SpringBoot异步接口实现&#xff1a;提高系统的吞吐量中&#xff0c;讲到使用Callable、WebAsyncTask、DeferredResult来提供异步接口&#xff0c;但是他们仅用于返回单个异步值——即返回一个值之后&#xff0c;就不能再返回值了。如果想生成多个异步值并将这些值写入响应&a…

怎样将vue项目 部署在ngixn的子目录下

如果同一服务器的80端口下,需要部署两个或以上数量的vue项目,那么就需要将其中一个vue项目部署在根目录下,其他的项目部署在子目录下. 像这样的配置 访问根目录 / 访问灭火器后台管理,访问 /mall/ 访问商城的后台管理 那么商场的vue项目,这样配置,才能在/mall/下正常访问? 1…

[数据集][目标检测]岩石种类检测数据集VOC+YOLO格式4766张9类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4766 标注数量(xml文件个数)&#xff1a;4766 标注数量(txt文件个数)&#xff1a;4766 标注…

网络丢包是如何产生的

在网络通信过程中&#xff0c;数据以数据包的形式在发送方和接收方之间传输。理想状态下&#xff0c;所有数据包都应准确无误地到达目的地。然而&#xff0c;在实际的网络环境中&#xff0c;数据包可能会在传输过程中丢失&#xff0c;这种现象被称为网络丢包。网络丢包会导致数…

MySQL从C盘迁移到D盘

文章目录 前言一、停止MySQL服务打开服务&#xff08;方式一&#xff09;打开服务&#xff08;方式二&#xff09;停止MySQL服务 二、找到C盘中的文件文件夹1文件夹2文件夹3 三、修改文件内容1.对应文件夹12.对应文件夹3 四、 修改注册表中文件路径1.打开注册表2. 修改注册表中…

黑龙江等保测评二级系统费用解析:如何合理预算?

在信息安全日益受到重视的今天&#xff0c;等保测评成为企业合规的重要环节。尤其是在黑龙江&#xff0c;随着网络安全法的实施&#xff0c;越来越多的企业开始关注等保测评的相关费用。那么&#xff0c;黑龙江等保测评二级系统的费用是如何计算的呢&#xff1f; 首先&#xf…

基于SpringBoot+Vue的鲜花销售/鲜花商城/花店管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的鲜花销售…

Kafka原理剖析之「Topic创建」

一、前言 Kafka提供了高性能的读写&#xff0c;而这些读写操作均是操作在Topic上的&#xff0c;Topic的创建就尤为关键&#xff0c;其中涉及分区分配策略、状态流转等&#xff0c;而Topic的新建语句非常简单 bash kafka-topics.sh \ --bootstrap-server localhost:9092 \ // …

OZON电子产品大幅增长,OZON跨境PS5销量激增

Top1 存储卡 Карта памяти Canvas Select Plus 128 ГБ 商品id&#xff1a;1548303593 月销量&#xff1a;2131 欢迎各位卖家朋友点击这里&#xff1a; &#x1f449; D。DDqbt。COm/74rD 免费体验 随着智能手机和平板电脑的普及&#xff0c;用户对于存储空…

Apache SeaTunnel Committer 进阶指南

Apache SeaTunnel 作为一个开源的数据集成工具&#xff0c;旨在简化和加速海量数据的采集和传输。 社区的 Committer 是指拥有项目存储库的写权限的社区成员&#xff0c;即 Committer 可以自行修改代码、文档和网站&#xff0c;也可以合并其他成员的贡献。成为 Apache SeaTunn…

类组件化websocket的方法(心跳机制)

/*** WebSocket统一管理*/ export class WebSocketClient {constructor(url) {if (!url) {throw new Error("WebSocket URL is required.");}this.url url;this.websocket null;this.listeners {};this.heartbeatInterval 30000; // 心跳检测间隔&#xff08;毫秒…

动态获取git版本号

有时为了方便查用户使用版本情况&#xff0c;我们需要在某些接口加入git版本号。那问题来了&#xff0c;每次发版时都要手动修改版本号&#xff0c;既加大了工作量&#xff0c;又容易忘记。如果能动态注册版本号就方便多了。 接下来我们说下如何动态注入版本号。 // vue.confi…

决策树基础概论

1. 概述 在机器学习领域&#xff0c;决策树&#xff08;Decision Tree&#xff09; 是一种高度直观且广泛应用的算法。它通过一系列简单的是/否问题&#xff0c;将复杂的决策过程分解为一棵树状结构&#xff0c;使得分类或回归问题的解决过程直观明了。决策树的最大特点在于可…