当前位置: 首页 > article >正文

OpenGL(二)-更详细版的三角形

在上篇blog中已经画了一个三角形了,这篇讲解一下一个三角形的渲染过程。
上篇blog中的glbegin搭配glend的流程,在OpenGL3.2中已经被弃用了,3.3以后推荐使用VBO+EBO+VAO的流程。

图形渲染管线

作用:将三维坐标经过一系列变换,生成一个二维坐标,这个二维坐标的值就是渲染的结果。
在这里插入图片描述
在这里插入图片描述
渲染管线的过程就如上图所示,不同的图形API可能每个阶段的任务和名称有所不同,但是整体流程都大同小异。
顶点数据中包含了很多数据,包括:顶点坐标,顶点颜色,顶点法线等。

  • 顶点着色器:处理顶点
  • 曲面细分着色器:4.0以后才有的功能,自动将一个曲面细分为很多三角形,在复杂地形构建中很实用
  • 几何着色器:处理一个图元,可以同时访问图元中所有的三角形的所有顶点
  • 光栅化:将3D连续的三角形进行格栅化,对应输出像素。连续的三角形变成离散的像素点数据
  • 片段着色器:片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

数据的流动

我们使用显卡进行渲染,需要将数据由内存放到显存里面,OpenGL中使用 顶点缓冲对象(Vertex Buffer Objects, VBO) 管理这部分用于存放顶点数据的显存。但是VBO只是存储数据,OpenGL读取的数据的时候,需要知道数据的格式,数据存储的位置,每个顶点数据的大小等等。这步我们可以使用 顶点数组对象(Vertex Array Object, VAO) 来记录,后续当我们想使用这个VBO的数据时,先绑定一下对应的VAO,就可以知道数据的具体信息了。

相邻三角形的两个顶点是共用的,如果按照上面的存储方式三角形的共同顶点会被存储三次,这样会浪费很多空间,所以常见的存储方式是,将顶点单独存储起来,三角形中存储顶点索引,渲染的时候根据索引在顶点数据序列中找顶点数据。在OpenGL中,使用 元素缓冲对象(Element Buffer Object,EBO) 来实现这个功能,

其中蓝色部分,是要使用着色器语言编程的,OpenGL使用GLSL着色器语言。

渲染管线是软硬相耦合的,具体发展历史可以看这两篇blog:
GPU硬件发展
GPU软件发展

代码

使用VAO和VBO画三角形的代码
OpenGL代码:

#include <iostream>
#include "glad/glad.h" //管理OpenGL的函数指针的
#include "GLFW/glfw3.h"
#include "gl/GL.h"



void framebufferSizeCallback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void rendering(GLFWwindow* window);
unsigned int shaderProgramInit();

int main()
{
	glfwInit(); // 初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);// 配置GLFW,OpenGL版本,Core核心模式

	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); //将上下文设置为该窗口

	// 给GLAD传入用来加载系统相关的OpenGL函数指针地址的函数
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed initialize GLAD" << std::endl;
		return -1;
	}
	glViewport(0, 0, 800, 600); 
	//设置视口,即窗口中需要渲染的部分,前两个参数是左下角坐标,后两个是宽高
	//当用户改变窗口的大小的时候,视口也应该被调整,可以设置一个窗口回调函数,自动调整
	//将回调函数注册到GLFW
	glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);

	while (!glfwWindowShouldClose(window)) //渲染主循环
	{
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //使用颜色清除buffer
		glClear(GL_COLOR_BUFFER_BIT);

		processInput(window); //处理输入事件
		rendering(window);
		glfwSwapBuffers(window); //交换渲染buffer,将glfw窗口显示到屏幕。双缓冲buffer,之前龚大大视频讲过
		glfwPollEvents(); //检查有没有触发什么事件,调用相应的回调函数
	}

	glfwTerminate(); //结束glfw
	return 0;

}

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

void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

void rendering(GLFWwindow* window)
{
	//OpenGL的坐标系是-1到1之间,中心点在屏幕中心,与OpenCV和DX的不同。
	float vertices[] = {
		-0.5f, -0.5f, 0.0f,
		 0.5f, -0.5f, 0.0f,
		 0.0f,  0.5f, 0.0f
	};
	unsigned int shaderProgram = shader_program_init();
	

	unsigned int VBO;
	glGenBuffers(1, &VBO); //创建顶点缓冲buffer,在显存上分配一段空间,存储顶点数据

	unsigned int VAO;
	glGenVertexArrays(1, &VAO); //创建VAO,存储应该使用的VBO位置,以及该VBO中顶点数据的组成和分布
	glBindVertexArray(VAO);


	glBindBuffer(GL_ARRAY_BUFFER, VBO); //将这段内存绑定到顶点缓存上,GL_ARRAY_BUFFER表示缓存buffer的类型
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 
	// 传输数据,类似于cudaMemcpy作用,
	// 最后的GL_STATIC_DRAW是向显卡表示如何管理该段数据,
	// GL_STATIC_DRAW表示该段数据几乎不变化,GL_DYNAMIC_DRAW表示该段数据经常变换,GL_STREAM_DRAW表示该段数据每次渲染都会变换
	// 显卡会根据数据的特性选择如何管理数据,以达到最快速度

	//渲染程序已经搞定了,接下来要告诉程序如何从缓冲区获取并解析顶点数据
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	// arg1表示顶点坐标信息在顶点数据中的哪个位置,arg2表示数据格式,arg3决定是数据否需要归一化,arg4表示每个顶点数据的大小,作为step来判断取下一个顶点数据
	// arg5表示顶点数据在缓冲区的那个位置,因为在缓冲区开头位置,所以直接为0
	
	//一般不需要主动解绑VAO和VBO,当我们再次绑定其他VAO,VBO时,会自动把旧的解绑换新的。

	//当我们要渲染一个物体的时候
	glEnableVertexAttribArray(0);// 开启顶点渲染功能
	glBindVertexArray(VAO); //告诉显卡,用这个VAO
	glUseProgram(shaderProgram); //告诉显卡,使用这个着色器
	glDrawArrays(GL_TRIANGLES, 0, 3);

}

unsigned int shaderProgramInit()
{
	const char* vertexShaderSource = "#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n" //告诉顶点坐标信息在哪里,position=0,也就是一开始就是坐标信息
		"void main()\n"
		"{\n"
		"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
		"}\0"; // GLSL代码,先存储到一个字符串中,后续多了可以存到文件里

	unsigned int vertexShader; //着色器ID
	vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建VERTEX_SHADER类型的shader,返回shader的ID
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //将着色器源代码绑定到shader
	glCompileShader(vertexShader); //编译shader
	int  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;
	}

	const char* fragmentShaderSource = "#version 330 core \n"
		"out vec4 FragColor;\n" //声明输出变量FragColor,类型为vec4
		"void main()\n"
		"{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\0";
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); //创建片段着色器
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}

	unsigned int shaderProgram;
	shaderProgram = glCreateProgram(); //创建着色程序,可以理解为前面都是在编译目标文件,这一步是将所有目标文件链接成一个可执行文件
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader); //添加shader,着色器程序会自动把上一个shader的输出作为下一个shader的输入,如果输出输入格式不匹配,就会报错
	glLinkProgram(shaderProgram);
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINK_FAILED\n" << infoLog << std::endl;
	}

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader); //清理掉之前的shader,已经不需要了
	
	return shaderProgram;
}

使用VAO+VBO+EBO画矩形的代码:


void rectangleRendering()
{
	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,2,3)就是顶点数组vertices的下标,
		// 这样可以由下标代表顶点组合成矩形

		0, 1, 3, // 第一个三角形
		1, 2, 3  // 第二个三角形
	};

	unsigned int shaderProgram = shaderProgramInit();

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	unsigned int VAO;
	glGenVertexArrays(1, &VAO); //创建VAO,存储应该使用的VBO位置,以及该VBO中顶点数据的组成和分布
	glBindVertexArray(VAO);

	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

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

	glEnableVertexAttribArray(0);
	glUseProgram(shaderProgram);
	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
	// 跟glDrawArrays基本一样,arg1表示模式,arg2表示绘画点数,arg3表示数据类型,arg4表示索引偏移量
}

http://www.kler.cn/news/294779.html

相关文章:

  • Java项目——苍穹外卖(一)
  • TypeScript系列 :接口interfaces
  • BERN2(生物医学领域)命名实体识别与命名规范化工具
  • 机器学习(西瓜书)第 4 章 决策树
  • API安全 | 发现API的5个小tips
  • AtCoder Beginner Contest 370 Solution
  • vue如何实现路由缓存
  • 解决面板安装Node.js和npm后无法使用的问题
  • 浙大数据结构:堆栈和队列的定义与操作
  • 2024全国大学省数学建模竞赛A题-原创参考论文(部分+第一问代码)
  • 大数据-124 - Flink State 01篇 状态原理和原理剖析:状态类型 执行分析
  • 网页开发 HTML
  • [代码已更新]2024数学建模国赛高教社杯C题:农作物的种植策略 思路代码文章助攻手把手保姆级
  • uniapp网站和微信小程序 添加 百度统计
  • 一起学习LeetCode热题100道(71/100)
  • 大数据时代的技术hive:hive的数据类型和数据模型
  • How can I provide a RGBA png file to OpenAI PHP library
  • 前缀和 — 利用前缀信息解决子数组问题
  • 【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error
  • 实践reflex:项目架构解析
  • 去中心化网络:Web3如何颠覆传统互联网
  • 标准IO与系统IO
  • Java架构师未来篇大模型
  • 新加坡服务器:亚洲地区的优选之选
  • 【软件工程】软件开发模型
  • k8s中的层级结构,及节点组件的作用
  • Termius for Mac/Win:高效、安全的跨平台多协议远程管理软件
  • 黑马点评2——商户查询缓存(P37店铺类型查询业务添加缓存练习题答案)redis缓存、更新、穿透、雪崩、击穿、工具封装
  • 2-85 基于matlab的FrFT下时变幅度LFM信号参数估计
  • ROADM(可重构光分插复用器)-介绍