OpenGL学习笔记一——绘制两个三角形,理解VAO,VBO和EBO_opengl vao、vbo、ebo画三角形-程序员宅基地

技术标签: OpenGL  

基础概念

顶点数据

我们在创建一个最基本的几何体时必须要告诉GPU要渲染的几何体的基本信息,包括顶点的坐标,颜色,法线,贴图等等,这些信息全部保存至数组当中,在使用的时候需要将这些数据传入顶点着色器。示例如下:

	//顶点信息
	float vertices[] = {
    
	0.5f, 0.5f, 0.0f,   // 右上角
	0.5f, -0.5f, 0.0f,  // 右下角
	-0.5f, -0.5f, 0.0f, // 左下角
	-0.5f, 0.5f, 0.0f   // 左上角
	};

两个三角形,有两个公共顶点,这时用到EBO

	float vertices[] = {
    
		// positions          // texture coords
		 0.5f,  0.5f, 0.0f,   1.0f, 1.0f, // top right
		 0.5f, -0.5f, 0.0f,   1.0f, 0.0f, // bottom right
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, // bottom left
		-0.5f,  0.5f, 0.0f,   0.0f, 1.0f  // top left 
	};

带有颜色信息

float vertices[] = {
    
    // 位置              // 颜色
     0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下
     0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部
};

VAO,VBO及EBO

  • 顶点数组对象:Vertex Array Object,VAO

  • 顶点缓冲对象:Vertex Buffer Object,VBO

  • 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

使用说明
顶点数组对象即为顶点数据,顶点缓冲对象用来管理顶点数据的,需要VBO存在的原因如下:
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。简而言之就是为了通过它加快数据传输速度。
索引缓冲对象存在的原因是当顶点数据发生重合时会造成不必要的内存浪费,而当引入索引缓冲对象时就可以大量减少这类空间的浪费,节约内存。
例如,当我们要绘制两个三角形,但是这两个三角形有公共顶点时:

	//顶点信息
	float vertices[] = {
    
	0.5f, 0.5f, 0.0f,   // 右上角
	0.5f, -0.5f, 0.0f,  // 右下角
	-0.5f, -0.5f, 0.0f, // 左下角
	-0.5f, 0.5f, 0.0f   // 左上角
	};
	unsigned int indices[] = {
     // 注意索引从0开始! 
		0, 1, 3, // 第一个三角形,对应数组的第1,2,4行
		1, 2, 3  // 第二个三角形,对应数组的第2,3,4行
	};

使用indices数组就是索引缓冲对象,减少了两组顶点数据的空间,当有大量三角形时,这个节省的空间将非常巨大,占用更少的显存。

顶点着色器及片元着色器

要想理解着色器是什么,就必须理解渲染管线,之前我的博文有相关介绍:
渲染管线

  • 顶点着色器

顶点着色器接收从CPU传入的数据,就是上面创建的顶点数组数据,经过顶点着色器处理计算之后,会有对应的输出数据作为片元着色器的输入数据,当然在这个传值过程中需要经过图元装配和插值光栅化计算,将顶点坐标转化成对应的像素坐标信息。
在OpenGL中的创建过程如下:

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
  • 片元着色器

像素信息作为片元着色器的传入数据做对应的计算后,经过几次测试(模板测试和深度测试等)进入frontBuffer,利用双缓冲技术从frontBuffer传入frameBuffer中,显示到屏幕上。
在OpenGL中的创建过程如下:

	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

图片显示全部过程:
在这里插入图片描述

创建流程

创建窗口

在创建窗口之前需要配置开发环境,最新的API配置之前有介绍,链接如下:
环境配置
代码及API不用死记硬背,只要理解其作业就行了

glfwInit();//初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//配置glfw,glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。

创建窗口的核心函数:

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

设置窗口大小

(OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。):

glViewport(0, 0, 800, 600);

分配/释放资源:

glfwTerminate();

创建顶点/片元着色器

顶点着色器

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

片元着色器

	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

创建着色器程序

	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

销毁着色器程序,创建完着色器程序之后便可销毁了

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

将数据传给顶点着色器

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

完整代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

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";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(0.0f, 1f, 0f, 1.0f);\n"
 "}\n\0";
int main() {
    
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL) {
    
		cout << "Failed to create GLFW window" << endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}
	glViewport(0, 0, 800, 600);

	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	//Info获取编译出错信息
	int success;
	char info[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
    
		glGetShaderInfoLog(vertexShader, 512, NULL, info);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << info << std::endl;
	}
	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//编译信息
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
    
		glGetShaderInfoLog(fragmentShader, 512, NULL, info);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << info << std::endl;
	}

	//shaderProgramshader程序
	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success)
	{
    
		glGetProgramInfoLog(shaderProgram, 512, NULL, info);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info << std::endl;
	}
	//没用了,直接删除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//顶点信息
	float vertices[] = {
    
	0.5f, 0.5f, 0.0f,   // 右上角
	0.5f, -0.5f, 0.0f,  // 右下角
	-0.5f, -0.5f, 0.0f, // 左下角
	-0.5f, 0.5f, 0.0f   // 左上角
	};
	unsigned int indices[] = {
     // 注意索引从0开始! 
		0, 1, 3, // 第一个三角形
		1, 2, 3  // 第二个三角形
	};
	unsigned int VAO, VBO;
	glGenVertexArrays(1, &VAO);//1代表数组长度为1
	glGenBuffers(1, &VBO);//1代表数组长度为1
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//索引,管理重复数据
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	//赋值数据到顶点着色器
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

	glEnableVertexAttribArray(0);//传入location值
	//glBindBuffer(GL_ARRAY_BUFFER, 0);    //取消绑定
	//glBindVertexArray(0);                         //取消绑定
// 	int nrAttributes;
// 	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
// 	std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
	while (!glfwWindowShouldClose(window)) {
    
		glClearColor(1, 0, 0, 1);//设置背景颜色
		glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓存

		//glBindVertexArray(VAO);
		//glDrawArrays(GL_TRIANGLES, 0, 3);

		glUseProgram(shaderProgram);//使用着色器程序
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//当使用EBO索引时使用该函数,当不用索引时使用glDrawArrays
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	//没用了,销毁
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glfwTerminate();
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    
	glViewport(0, 0, width, height);
}

运行截图:
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36696486/article/details/104198142

智能推荐

数据结构 树与二叉树 笔记合集(C语言)完结_树与二叉树笔记-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏10次。树与二叉树树常考考点二叉树二叉树的顺序存储二叉树的链式存储二叉树的遍历二叉树的层次遍历线索二叉树找中序前驱中序线索化先序线索化中序线索二叉树找中序后继先序线索二叉树找先序后继先序线索二叉树找先序前驱树的存储结构双亲表示法(顺序存储)孩子兄弟表示法(链式存储) C树的层次遍历(广度优先遍历)树的先根、后根遍历(深度优先遍历)树·结点之间的路径只能从上往下·结点的度:结点的分支数·树的度:树中..._树与二叉树笔记

PSTOOLS系列工具分析----对PSEXEC的逆向解析-程序员宅基地

文章浏览阅读1.4k次。PSTOOLS系列工具分析----对PSEXEC的逆向解析 在网络攻击中,经常用到一个工具系列叫pstools,它是由Sysinternals公司推出的一个功能强大的Windows NT/2000远程管理工具包,最新版本为V2.1,包含了10多个工具。在入侵时,最常用的就是其中的psexec(获取对方Shell)、pslist(列举进程)、pskill(杀进程)等工具,它们使用简单,功能

Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.9.1:jar (default) on project-程序员宅基地

文章浏览阅读5.6k次,点赞2次,收藏4次。打包报错:[ERROR] Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.9.1:jar (default) on project tencentlvb: MavenReportException: Error while creating archive: [ERROR] Exit code: ..._failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.9.1:j

huangxq_huangxq625-程序员宅基地

文章浏览阅读263次。我好难过,好想我的爸爸妈妈dddddddd_huangxq625

CSS预处理器 ---less_前端less预处理器-程序员宅基地

文章浏览阅读429次。Less(Leaner Style Sheets 精简样式表) 是一种动态样式语言,属于 css 预处理器的范畴,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性,使 CSS 更易维护和扩展,Less 既可以在 客户端 上运行 ,也可以借助 Node.js 在服务端运行。在Hbuildx中下载插件less,右键点击less文件,选择外部命令,点击编译less,即可把less文件编译为css文件存放在相同路径下。_前端less预处理器

读书笔记-python,数据可视化之matplotlib简单实用_plt.tick_params(axis='both', labelsize=20)-程序员宅基地

文章浏览阅读628次。matplotlib使用的几个例子_plt.tick_params(axis='both', labelsize=20)

随便推点

Android安装GDB/GDB server_安卓13安装gdbserver-程序员宅基地

文章浏览阅读2.7k次。在没有安卓系统源码,还想调试系统代码查看崩溃信息的时候也可以用gdb或者gdbserver来调试,但是手机里没有装gdb或gdbserver。记录一下手动安装踩的坑: 首先,需要下载编译好的gdbserver。官方渠道可以从ndk-toolchain中里找,解压后在/prebuild文件夹下找对应处理器架构的版本。 然后通过adb push到手机上。貌似即使手机root之后,adb push也只_安卓13安装gdbserver

Git查看并修改name和email_gi 查看name-程序员宅基地

文章浏览阅读1.3w次。修改自己的用户名和邮箱地址:  $ git config --global al user.name "xx "xxx" $ git config --global al user.email "xx "xxx 显示git config user.name _gi 查看name

Java使用lambda自定义Arrays.sort排序规则_java lambda 数组排序-程序员宅基地

文章浏览阅读5.9k次,点赞3次,收藏14次。首先注意默认排规则,当使用sort(Objetc[] a)来进行对象的自然排序,该对象必需实现Compareable接口,重写compareableTo方法,并一般在此方法中定义这3种返回值(1,0,-1)来进行排序标准的确认。return 1 时,按照从小到大排序 (也可以是2,3.....正数) return 0 时,原位置不动 return-1 时,按照从在到小排序Integer[] numsArr = new Integer[10];Arrays.sort(numsArr,..._java lambda 数组排序

[Protobuf]PB高效的数据压缩编码方式_pb编码格式-程序员宅基地

文章浏览阅读7.6k次,点赞6次,收藏11次。一. protocol buffers 是什么?Protocol buffers 是一种语言中立,平台无关,可扩展的序列化数据的格式,可用于通信协议,数据存储等。Protocol buffers 在序列化数据方面,它是灵活的,高效的。相比于 XML 来说,Protocol buffers 更加小巧,更加快速,更加简单。一旦定义了要处理的数据的数据结构之后,就可以利用 Protocol buff..._pb编码格式

null和System.DBNull.Value的区别-程序员宅基地

文章浏览阅读238次。  我记得之前在写一个程序的时候用到了这个知识点,当时判断的时候,有时候null可以,有时候必须是System.DBNull.Value由于不清楚这两个的区别所以纠结了很久。查了一下,二者的区别如下:  null是表示C#语言中的变量值为空值。  DBNull.Value本身并不是null值,而是表示数据库里的空值。  一个用于C#语言,一个用于数据库。转载于:http..._system.dbnull”和“system.guid?

高德地图浏览器定位_高德watchposition-程序员宅基地

文章浏览阅读4k次。浏览器定位 body{margin:0;height:100%;width:100%;position:absolute;}#mapContainer{position: absolute;top:0;left: 0;right:0;bottom:0; }#tip{min-height:65px;background-color_高德watchposition