LearnOpenGL02-hellotriangles

LearnOpenGL第二课,你好三角形。学习笔记。
原文链接:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
1、图形渲染管线
OpenGL中,任何事物都是在3D空间,而屏幕和窗口时2D空间,因此OpenGL的大部分工作都是关于把3D坐标转变为适应屏幕的2D像素。3D坐标转为2D坐标的处理过程就是由OpenGL的图形渲染管线(Graphics Pipeline)管理的。图形渲染管线分成两个主要部分:一、把3D坐标转换为2D坐标;二、把2D坐标转变为实际的有颜色的像素。
注:2D坐标和像素是不相同的,2D坐标精确表示一个点在2D空间中的位置,2D像素是这个点的近似值,2D像素受屏幕/窗口分辨率的限制。
图形渲染管线有顶点数据、顶点着色器、图元装配、几何着色器、光栅化、片段着色器、测试与混合七个阶段。每个阶段的输出都是下一个阶段的输入。
(1)顶点数据:顶点数据是一系列顶点的集合,用顶点属性(Attribute)表示。是渲染管线的数据主要来源。包括顶点坐标、纹理坐标、顶点法线、顶点颜色等顶点属性。
(2)顶点着色器:把一个单独的顶点作为输入,主要功能是进行坐标的变换,把3D坐标转换为另一种3D坐标,即把输入的局部坐标变换到世界坐标、观察坐标和裁剪坐标。
(3)图元装配:把顶点着色器输出的所有顶点作为输入,并把所有的点装配成指定图元的形状。
(4)几何着色器:输入的是完整的图元(比如点),把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或其它的)图元来生成其他形状。输出的是一个或多个其他的图元(如,三角面),或者不输出任何的图元。主要是将输入的点或线扩展成多边形。
(5)光栅化:把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。
(6)片段着色器:主要目的是计算一个像素的最终颜色。通常包括3D场景的数据(如,光照、阴影、光的颜色等)。是渲染管线高级效果产生的地方。
(7)测试与混合:检测片段对应的深度,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃,也会检查alpha的值并对物体进行混合。
2、关于VAO、VBO、EBO
VAO(Vertex Array Object)顶点数组对象。
VBO(Vertex Buffer Object)顶点缓冲对象
VEO(Element Buffer Object或Index Buffer Object(IBO))索引缓冲对象
3、顶点输入
输入的顶点数据都是3D数据,OpenGL首先把输入的3D数据转换为标准化设备坐标(范围[-1.0,1.0])后才能在屏幕上显示,所有在标准化设备坐标范围内的坐标才会最终呈现在屏幕上,范围外的不会显示。
注:一旦顶点坐标在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在[-1.0,1.0]。该范围外的坐标都会被丢弃/裁剪,不会显示在屏幕上。接着使用glViewport函数提供的数据进行视口变换,将标准化设备坐标变换为屏幕空间坐标。
4、VBO管理GPU上用于存储顶点数据内存
定义顶点数据后,会作为输入发送给顶点着色器,它会在GPU上创建内存存储顶点数据,并且配置OpenGL如何解释这些内存,指定如何发送给显卡。
用顶点缓冲对象VBO管理这个内存,在GPU内存(显存)中存储大量顶点。
使用VBO优点:
可以一次性发送一大批数据到显卡上,而不是每个顶点发送一次。
CPU到GPU发送数据较慢,应尽量一次性发送尽可能多的数据,数据发送至显存后,顶点着色器几乎能立即访问顶点,程序运行速度快。
VBO的使用:
VBO在OpenGL中有一个独一无二的ID,使用glGenBuffers函数和一个缓冲ID生成一个VBO对象。
unsigned int VBO;
glGenBuffers(1,&VBO);
把新创建的缓冲进行目标绑定,绑定后使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会来配置当前绑定的缓冲(VBO)。OpenGL有很多缓冲对象类型,顶点缓冲对象类型是GL_ARRAY_BUFFER,OpenGL允许同时绑定多个缓冲,但是缓冲类型不同。
glBindBuffer(GL_ARRAY_BUFFER,VBO);
绑定后,调用glBufferData函数,把之前定义的顶点数据复制到缓冲(VBO)的内存中。
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
glBufferData是专门用来把用户定义的数据复制到当前绑定的缓冲的函数。对应参数含义:
第一个参数:目标缓冲的类型;
第二个参数:指定传输数据的大小(以字节为单位);
第三个参数:发送的实际数据;
第四个参数:指定显卡如何管理给定的数据。
GL_STATIC_DRAW:数据不会或几乎不会改变;
GL_DYNAMIC_DRAW:数据会被改变很多;
GL_STREAM_DRAW:数据每次绘制时都会改变。
5、顶点着色器和GLSL
5.1顶点着色器是可编程着色器中的一个。首先用着色器语言GLSL编写顶点着色器。如下:
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
先是GLSL版本声明,下一步使用in关键字,在顶点着色器中声明所有的输入顶点属性,layout (location = 0)设定输入变量的位置值。函数体中是一个4维向量,vec4.x、vec4.y、vec4.z表示空间位置,vec4.w用在透视除法上。
5.2编译顶点着色器
OpenGL使用它之前,必须对其进行编译。
首先用glCreateShader创建着色器对象,传递参数GL_VERTEX_SHADER表明创建的是顶点着色器。还是用ID来引用。
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
然后把着色器源码附加到着色器对象。
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glShaderSource函数的参数:
第一个参数:要编译的着色器对象;
第二个参数:传递的源码字符串数量;
第三个参数:顶点着色器的真正源码;
第四个参数:先设置为NULL。
对其进行编译。
glCompileShader(vertexShader);
注:检测着色器是否编译成功:
int success;
char infolog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infolog);
std::cout << “ERROR::SHADER::COMPILATION_FAILED\n” << infolog << std::endl;
}
6、片段着色器与GLSL
6.1片段着色器所做的是计算像素最后的颜色输出。输出的颜色即为你想要你的图形呈现的颜色。在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,通常缩写为RGBA。当在OpenGL或GLSL中定义一个颜色的时候,我们把颜色每个分量的强度设置在0.0到1.0之间。比如说我们设置红为1.0f,绿为1.0f,我们会得到两个颜色的混合色,即黄色。
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
6.2编译片段着色器
编译过程与顶点着色器编译过程类似,只不过使用GL_FRAGMENT_SHADER常量作为着色器类型;
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(fragmentShader);
7、着色器程序
着色器程序对象是多个着色器合并之后最终链接完成的版本,然后在渲染对象的时候激活这个着色器程序,已激活的着色器程序的着色器将在发送渲染调用的时候被调用。
glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
把之前编译的着色器附加到程序对象上;
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
最后进行链接。
glLinkProgram(shaderProgram);
着色器对象链接到程序对象后,将着色器对象删除。
glDeleteShader(vertexShader);
glDeleteShader(fragmenShader);
注:检测链接是否成功
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infolog);
std::cout << “ERROR::PROGRAM::LINK_FAILES\n” << infolog << std::endl;
}
链接的结果得到一个程序对象,调用glUseProgram函数,激活这个程序对象。
glUseProgram(shaderProgram);
8、链接顶点属性
顶点着色器允许指定任何形式的顶点属性的输入,因此必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个属性。
使用glVertexAttribPointer函数告诉OpenGL如何解析顶点数据。
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3sizeof(float),(void)0);
glVertexAttribPointer参数:
第一个参数:指定要配置的顶点属性。与vertexShader中的location的值一致;
第二个参数:指定顶点属性的大小;
第三个参数:指定数据的类型;
第四个参数:是否希望数据被标准化,如果设置为GL_TRUE,无符号数据被映射到[0,1],有符号数据被映射到[-1,1],设置为GL_FALSE不被标准化。
第五个参数:叫做步长,连续的顶点属性组之间的间隔,即几个数据为一组。
第六个参数:表示位置数据在缓冲中起始位置的偏移量。
定义完如何解释顶点数据,启用顶点属性。
glEnableVertexAttribArray(0);
8、顶点数组对象
顶点数组对象VAO可以像VBO那样被绑定,任何随后的顶点属性调用都会存储在这个VAO中。
好处:
当配置顶点属性指针时,只需要将那些调用执行一次,之后在绘制物体的时候只需要绑定相应的VAO就可以。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行,链接顶点属性设置的都在VAO中。

VAO的使用
创建VAO
unsigned int VAO;
glGenVertexArrays(1,&VAO);
使用VAO时,对其进行绑定即可。
glBindVertexArray(VAO);
9、索引缓冲对象
索引缓冲对象EBO,也是一个缓存,它专门存储索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。
创建索引缓冲对象与VBO类似。
unsigned int EBO;
glGenBuffers(1,&EBO);
绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
把索引数据复制到缓冲里。
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
代码实现:

#include 
#include 
//回调函数,每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
//处理输入函数
void processInput(GLFWwindow* window);
//顶点坐标
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,1,3,
	1,2,3
};
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 *fragShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";
int main()
{
	//初始化glfw
	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)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	//初始化GLAD
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}
	//视口大小
	glViewport(0, 0, 800, 600);
	//每当窗口调整大小的时候调用该函数
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	//顶点着色器
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
	int success;
	char infolog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infolog);
		std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infolog << std::endl;
	}
	//片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragShaderSource, NULL);
	glCompileShader(fragmentShader);
	//着色器程序
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infolog);
		std::cout << "ERROR::PROGRAM::LINK_FAILES\n" << infolog << std::endl;
	}
	//使用着色器程序
	//glUseProgram(shaderProgram);
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);
	//VBO
	unsigned int VAO, VBO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	//渲染循环
	while (!glfwWindowShouldClose(window))
	{
		//输入
		processInput(window);
		//渲染指令
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		//glBindVertexArray(0);
		//glDrawArrays(GL_TRIANGLES, 0, 3);
		//检查并调用事件,交换缓冲
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glDeleteProgram(shaderProgram);
	glfwTerminate();
	return 0;
}
//调整窗口大小,回调函数
void framebuffer_size_callback(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);
	}
}

你可能感兴趣的:(OpenGL,opengl,图形学)