OpenGL学习笔记第二课--Hello Triangle

完整版原文地址:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

https://learnopengl.com/Getting-started/Hello-Triangle

1.三个概念 

  • 顶点数组对象:Vertex Array Object,VAO
  • 顶点缓冲对象:Vertex Buffer Object,VBO
  • 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

2.在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的

2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。

下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分

实际的obj文件不仅有顶点数组,还包含uv,索引等,从obj到顶点数组需要经过VBO和VAO

 

一个AVO可以可以持有两种类型的buffer ,GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER

在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)

流水线流程

3.造一个VAO

在适当的位置,如在glViewport之后造一个VAO

	unsigned int VAO;
	glGenVertexArrays(1, &VAO);//glGenVertexArrays方法可以产生多个VAO,第一个参数代表产生的VAO个数,第二个参数是VAO对象的地址,这个函数返回一个VAO ID填充到变量VAO中
    //扩展 由于glGenVertexArays可以返回多个ID当需要产生多个VAO时,如10个,需定义一个数组接受函数返回值
    /*
    unsigned int VAO[10];
    glGenVertexArrays(10,AVO); //数组名就是数组的首地址
     
    */

4.将造完的VAO绑定到渲染管道

使用glBindVertexArray函数,此函数需要一个ID作为参数

	unsigned int VAO;
	glGenVertexArrays(1, &VAO);//glGenVertexArrays方法可以产生多个VAO,第一个参数代表产生的VAO个数,第二个参数是VAO对象的地址,这个函数返回一个VAO ID填充到变量VAO中
    //扩展 由于glGenVertexArays可以返回多个ID当需要产生多个VAO时,如10个,需定义一个数组接受函数返回值
    /*
    unsigned int VAO[10];
    glGenVertexArrays(10,AVO); //数组名就是数组的首地址
     
    */
    glBindVertexArray(VAO);

5.造一个VBO绑到VAO的造一个VBO绑定到VAO的GL_ARRAY_BUFFER上上

类似VAO,创建VBO使用函数glGenBuffers

	//造一个VBO绑定到VAO中的GL_ARRAY_BUFFER上
	unsigned int VBO;
	glGenBuffers(1, &VBO);//同上
	glBindBuffer(GL_ARRAY_BUFFER, VBO);//两个参数,第一个是绑去哪里,第二个是要绑定的VBO的id

6.将顶点塞到VBO中

使用glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

方法,完成数据从CPU到GPU

glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数

四个参数:

第一个参数表示要填充的缓冲类型

第二个参数具体指明了缓冲的数据有多少(单位是字节)

第三个参数是具体要发送的数组

第四个参数指明显卡如何处理刚才发送的数组,他有三种形式:

 

  • GL_STATIC_DRAW :数据不会或几乎不会改变。
  • GL_DYNAMIC_DRAW:数据会被改变很多。
  • GL_STREAM_DRAW :数据每次绘制时都会改变。

 

7.到此步骤黑色的部分完成,但是还不能显示,因为还没有写shader(着色器)

8.Vertex shader 

暂时将教材中的代码硬编码为一个字符数组

将代码贴到notepad++中,减少行数,不然看起来很乱,然后在每一行后面加\n,按alt键复选行可以全部添加

然后在每一行的开始和结尾都加上"

最后加上;

把这段代码复制到源程序的顶部 赋值给一个变量 const char* vertexShader 

//顶点着色器
const char* vertexShaderSource =
"#version 330 core                                     \n"
"layout (location = 0) in vec3 aPos;                   \n"
"void main(){                                          \n"
"    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";

9. Fragment shader 

片段着色器同理

//片段着色器
const char* fragmentShaderSource = 
"#version 330 core                             \n"
"out vec4 FragColor;                           \n"
"void main(){                                  \n"
"    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n";

10在VBO之后compile 着色器

	//编译着色器语言

		//顶点着色
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(vertexShader);//编译
		//片段着色
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(fragmentShader);//编译

11.Shader program

将两个shader 组装成program才能给GPU用

	//shader program
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();//glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用




		//将编译过的着色器附加到程序对象上,然后用glLinkProgram连接
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

12.设置顶点指针


 

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

    glEnableVertexAttribArray(0);
  • 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
  • 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
  • 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  • 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  • 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
  • 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。

 

每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。由于在调用glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。

 

13.设置绘制

glUseProgram(shaderProgram);

glBindVertexArray(VAO);

glDrawArrays(GL_TRIANGLES, 0, 3);

添加到绘制循环中


	//绘制图形

		//循环render loop
	while (!glfwWindowShouldClose(window))
	{
		//input
		processInput(window);//先处理

		//rendering commands here
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//glClearColor是一个状态设置函数
		glClear(GL_COLOR_BUFFER_BIT);//glClear是一个状态使用函数,它使用了当前状态来获取应该清楚为的颜色


		//
		glBindVertexArray(VAO);
		glUseProgram(shaderProgram);
		glDrawArrays(GL_TRIANGLES, 0, 3);//第一个参数表示图元类型,第二个参数表示从第几个三角形开始画,第三个表示画几个顶点


		//check and call events and swap the buffers
		glfwPollEvents();//后获取用户的按键等行为
		glfwSwapBuffers(window);

	}
	glfwTerminate();

附完整代码和结果:

#define GLEW_STATIC
#include <iostream>
#include <Gl/glew.h>
#include <Glfw/glfw3.h>
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.0f,  0.5f, 0.0f
};



//顶点着色器
const char* vertexShaderSource =
"#version 330 core                                     \n"
"layout (location = 0) in vec3 aPos;                   \n"
"void main(){                                          \n"
"    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";

//片段着色器
const char* fragmentShaderSource = 
"#version 330 core                             \n"
"out vec4 FragColor;                           \n"
"void main(){                                  \n"
"    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n";

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//Open GLFW Window
	GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL);
	if (window == NULL)
	{
		std::cout << "Open window failed." << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); // 假如window不是空指针,使用这个 window为当前上下文


	// Init GLEW
	glewExperimental = true; // 固定写法,因为有些特性是试验阶段
	if (glewInit() != GLEW_OK)  // GLEW的操作如果成功的话,返回GLEW_OK关键字
	{
		std::cout << "Init GLEW failed" << std::endl;
		glfwTerminate();
		return -1;
	}

	glViewport(0, 0, 800, 600);

	//造一个VAO并且绑定到管道中
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);//glGenVertexArrays方法可以产生多个VAO,第一个参数代表产生的VAO个数,第二个参数是VAO对象的地址,这个函数返回一个VAO ID填充到变量VAO中
		//扩展 由于glGenVertexArays可以返回多个ID当需要产生多个VAO时,如10个,需定义一个数组接受函数返回值
	/*
	unsigned int VAO[10];
	glGenVertexArrays(10,AVO); //数组名就是数组的首地址,在c语言里数组和指针部分

	*/
	glBindVertexArray(VAO);//绑定

	//造一个VBO绑定到VAO中的GL_ARRAY_BUFFER上
	unsigned int VBO;
	glGenBuffers(1, &VBO);//同上
	glBindBuffer(GL_ARRAY_BUFFER, VBO);//两个参数,第一个是绑去哪里,第二个是要绑定的VBO的id
	

	//将数组从cpu发送到gpu缓存中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	
	//编译着色器语言

		//顶点着色
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(vertexShader);//编译
		//片段着色
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(fragmentShader);//编译

	//shader program
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();//glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用




		//将编译过的着色器附加到程序对象上,然后用glLinkProgram连接
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	

	//设置顶点属性指针
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);



	//绘制图形

		//循环render loop
	while (!glfwWindowShouldClose(window))
	{
		//input
		processInput(window);//先处理

		//rendering commands here
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//glClearColor是一个状态设置函数
		glClear(GL_COLOR_BUFFER_BIT);//glClear是一个状态使用函数,它使用了当前状态来获取应该清楚为的颜色


		//
		glBindVertexArray(VAO);
		glUseProgram(shaderProgram);
		glDrawArrays(GL_TRIANGLES, 0, 3);//第一个参数表示图元类型,第二个参数表示从第几个三角形开始画,第三个表示画几个顶点


		//check and call events and swap the buffers
		glfwPollEvents();//后获取用户的按键等行为
		glfwSwapBuffers(window);

	}
	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);
	}

}

 

14.回头理解第12条,顶点属性:

即从VBO到VAO的过程

如从VBO到VAO,v可能会被存在VAO的0号位置,norma可能l会被存在1号位置,2号位置存uv等

 

每一个X,Y,Z都是一个位置

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
顶点属性函数

第一个参数主要将怎么挖VAO送进来的这些数值,造一个栏位,0号栏位,

第二个参数,我们送进来的这组数值,然后每三个数值是一个顶点

第三个参数,每个栏位数值类型是一个float

第四个参数,挖出来的是否归一化到0到1 或者-1 到 1 之间

第五个参数,设置每挖完一个顶点,下一个顶点要间隔多少去挖,因为这里顶点类型是float,间隔3个顶点,所以每隔3 * sizeof(float)挖一次

第六个参数,第一个参数偏移量是多少

 

    glEnableVertexAttribArray(0);

宣告完之后就可以把0号特征值塞到shader里面去用了

const char* vertexShaderSource =
"#version 330 core                                     \n"
"layout (location = 0) in vec3 aPos;                   \n"
"void main(){                                          \n"
"    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";

0 号在着色器语言里 已经通过location == 0 设置了 ,然后对应到了  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);里面的0,

不理解的话,就把这三个标红的0 设置成范围内相等的任何正整数都行

 

14.其他扩充,比如画一个四边形,只需要改连个地方

结果

15.背面剔除,在glViewport(0,0,800,600)后面加

	//面剔除(不显示顺时针的反面)
	glEnable(GL_CULL_FACE);//在状态机中开启面剔除功能
	glCullFace(GL_BACK);//剔除背面

opengl默认正面和反面都画(默认逆时针是正面)

16.索引缓冲对象 EBO

opengl是一个状态机

opengl在运行时只有一个状态 只会认得当前运行的VAO

同时间只能操作一个VBO,索引要执行bind这个动作,bind一个VAO和bind一个VBO

VBO的生命周期是opengl负责的

EBO要使用另一个管道进入到状态机

17 创造EBO

	//造一个EVO,并绑定
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);//绑定EVO然后用glBufferData把索引复制到缓冲里

注意:要先绑定不然不显示

要注意的是,我们传递了GL_ELEMENT_ARRAY_BUFFER当作缓冲目标。最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染。使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//绑定EBO
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//第一个参数表示要画什么形状,第二个表示要画几个顶点,第三个参数索引值的类型,第四个表示偏移量

第一个参数指定了我们绘制的模式,这个和glDrawArrays的一样。第二个参数是我们打算绘制顶点的个数,这里填6,也就是说我们一共需要绘制6个顶点。第三个参数是索引的类型,这里是GL_UNSIGNED_INT。最后一个参数里我们可以指定EBO中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。

18.线框模式开关

在glViewport 下面粘贴代码即可

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);表示绘制线框

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);三角面

附线框模式完整代码和结果

#define GLEW_STATIC
#include <iostream>
#include <Gl/glew.h>
#include <Glfw/glfw3.h>
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.0f,  0.5f, 0.0f,//逆时针
		// 0.5f, -0.5f, 0.0f,
	 //0.0f,  0.5f, 0.0f,
	 0.8f,0.8f,0.0f,

};
unsigned int indices[] = {
	0,1,2,
	2,1,3
};



//顶点着色器
const char* vertexShaderSource =
"#version 330 core                                     \n"
"layout (location = 0) in vec3 aPos;                   \n"
"void main(){                                          \n"
"    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";

//片段着色器
const char* fragmentShaderSource = 
"#version 330 core                             \n"
"out vec4 FragColor;                           \n"
"void main(){                                  \n"
"    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n";

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//Open GLFW Window
	GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL);
	if (window == NULL)
	{
		std::cout << "Open window failed." << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); // 假如window不是空指针,使用这个 window为当前上下文


	// Init GLEW
	glewExperimental = true; // 固定写法,因为有些特性是试验阶段
	if (glewInit() != GLEW_OK)  // GLEW的操作如果成功的话,返回GLEW_OK关键字
	{
		std::cout << "Init GLEW failed" << std::endl;
		glfwTerminate();
		return -1;
	}

	glViewport(0, 0, 800, 600);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	面剔除(不显示顺时针的反面)
	//glEnable(GL_CULL_FACE);//在状态机中开启面剔除功能
	//glCullFace(GL_BACK);//剔除背面

	//造一个VAO并且绑定到管道中
	unsigned int VAO;
	glGenVertexArrays(1, &VAO);//glGenVertexArrays方法可以产生多个VAO,第一个参数代表产生的VAO个数,第二个参数是VAO对象的地址,这个函数返回一个VAO ID填充到变量VAO中
		//扩展 由于glGenVertexArays可以返回多个ID当需要产生多个VAO时,如10个,需定义一个数组接受函数返回值
	/*
	unsigned int VAO[10];
	glGenVertexArrays(10,AVO); //数组名就是数组的首地址,在c语言里数组和指针部分

	*/
	glBindVertexArray(VAO);//绑定

	//造一个VBO绑定到VAO中的GL_ARRAY_BUFFER上
	unsigned int VBO;
	glGenBuffers(1, &VBO);//同上
	glBindBuffer(GL_ARRAY_BUFFER, VBO);//两个参数,第一个是绑去哪里,第二个是要绑定的VBO的id
		//VBO方法将数组从cpu发送到gpu缓存中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//造一个EBO,并绑定
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//绑定EBO
	glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);//绑定EVO然后用glBufferData把索引复制到缓冲里


	//编译着色器语言

		//顶点着色
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(vertexShader);//编译
		//片段着色
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//使用glShaderSource方法填充代码
	//第一个参数表示要将结果赋给谁,第二个表示有几个字符串,我们这种形式的声明为一个字符串,第三个表示字符串常量的地址,第四个参数不关键,设为NULL
	glCompileShader(fragmentShader);//编译

	//shader program
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();//glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用




		//将编译过的着色器附加到程序对象上,然后用glLinkProgram连接
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	

	//设置顶点属性指针
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);



	//绘制图形

		//循环render loop
	while (!glfwWindowShouldClose(window))
	{
		//input
		processInput(window);//先处理

		//rendering commands here
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//glClearColor是一个状态设置函数
		glClear(GL_COLOR_BUFFER_BIT);//glClear是一个状态使用函数,它使用了当前状态来获取应该清楚为的颜色


		//
		glBindVertexArray(VAO);
		glUseProgram(shaderProgram);
		glDrawArrays(GL_TRIANGLES, 0, 6);//第一个参数表示图元类型,第二个参数表示从第几个三角形开始画,第三个表示画几个顶点

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//第一个参数表示要画什么形状,第二个表示要画几个顶点,第三个参数索引值的类型,第四个表示偏移量
		
		//check and call events and swap the buffers
		glfwPollEvents();//后获取用户的按键等行为
		glfwSwapBuffers(window);

	}
	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);
	}

}

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页