OpenGL学习笔记2——三角形的绘制1

OpenGL学习笔记2——三角形的绘制1

  • 1 精简版的三角形绘制
    • 1.1 顶点数据
    • 1.2 VAO和VBO
    • 1.3 顶点着色器和片元着色器
    • 1.4 着色器程序
    • 1.5 VAO的设置
    • 1.6 在渲染循环中渲染
    • 1.7 完整代码
    • 1.8 EBO

这一次主要是根据LearnOpengGL做的三角形绘制。看了很久看不懂,也做不下去。最后还是看了傅老师才看明白。真是可喜可贺,可喜可贺。

1 精简版的三角形绘制

三角形的绘制在创建窗口的基础上继续。

1.1 顶点数据

首先定义一下我们要画的三角形的三个顶点。比较要理解,就是xyz三个坐标。现在我们不需要深度所以z都是0。

	float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f,
	};

1.2 VAO和VBO

  • VAO,Vertex Array Object。顶点数组对象。
  • VBO,Vertex Buffer Object。顶点缓冲对象。

然后我们创建一个VAO和VBO。

	// 创建顶点数组对象VAO(Vertex Array Object)
	unsigned int VAO;
	glGenVertexArrays(1, &VAO); // 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
	glBindVertexArray(VAO); // 绑定VAO

	// 创建顶点缓冲对象VBO(Vertex Buffer Object)
	unsigned int VBO;
	glGenBuffers(1, &VBO);// 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
	glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO

	// 将顶点数据绑定到GL_ARRAY_BUFFER上
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

其中glGenVertexArrays方法是可以创建多个VAO的,例如:

	// 这只是写个例子,待会并不会写到代码里面。
	unsigned int VAO[10];
	glGenVertexArrays(10, VAO); 

所以其实只有一个VAO的话也可以写成:

	// 这只是写个例子,待会并不会写到代码里面。
	unsigned int VAO[1];
	glGenVertexArrays(10, VAO); 

但是创建一个只有一个元素的数组显得很鸡肋,所以就像上面一样直接一个VAO,然后传入地址。glGenBuffers同理。

解释一下代码,unsigned int VAO,可以理解为声明了一个整数ID,这个ID就代表着VAO这个东西(不知道为啥不用指针,还不太理解)。另外unsigned int可以用GLuint代替,他俩是一样的。就是一个无符号的整型数据。
OpenGL学习笔记2——三角形的绘制1_第1张图片
然后通过glGenVertexArrays把生成的一个或者多个(这里只有一个)顶点数组对象的地址赋值给VAO这个整型ID。glGenBuffers同理。
glBindVertexArray和glBindBuffer都是用来绑定VAO和VBO的。VBO可以绑定到不同的buffer上,所以这里要指定是绑定到GL_ARRAY_BUFFER上。
glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。 GL_STATIC_DRAW代表顶点不会变动,这个函数还不太理解。

1.3 顶点着色器和片元着色器

OpenGL不提供顶点着色器和片元着色器,所以我们必须提供这两个着色器。它们是用GLSL语言写的,看起来和C语言很类似。下面是两个很简单着色器源码。

// 顶点着色器 vertexShader
#version 330 core // 标明使用的版本,OpenGL3.3 ,GLSL就要用330
layout (location = 0) in vec3 aPos;
// 上面这句定义从我们的VAO第0个指针位置获取数据,数据作为一个vec3,叫做aPos
// location = 0对应着等会glVertexAttribPointer方法的参数设置
void main() {
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); // 不做任何处理直接将顶点输出(因为需要4元的向量,所以直接用构造方法添加一个分量上去)
}

// 片元着色器 fragmentShaer
#version 330 core
out vec4 FragColor;
void main() {
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); // 设置颜色
} 

但是GLSL并不能直接在这里编译,所以要写成字符数组,通过glfw的函数进行编译。如下。

// 顶点着色器源码
const char* vertex_shader_source =
"#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"
"}													\n";

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

然后像前面创建VAO和VBO一样。

  1. 首先unsigned int创建shader的id。
  2. 使用glCreateShader函数,通过参数告诉他我们要创建的是什么shader,返回值就是创建的shader的唯一id。
  3. 使用glShaderSource函数,将源码绑定到shader上。第一个参数是绑定到哪个shader,第二个参数是有一个字符数组,我们这里只有一个。第三个参数源码的地址,第四个参数现在还不知道。
  4. 使用glCompileShader函数编译shader。
	// 创建顶点着色器(shader)
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertex_shader_source, nullptr); // 将shader源代码绑定到shader中
	glCompileShader(vertexShader); // 编译shader

	// 创建片元着色器(shader)
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragment_shader_source, nullptr); // 将shader源代码绑定到shader中
	glCompileShader(fragmentShader); // 编译shader

1.4 着色器程序

光是弄好了两个shader还不能丢到GPU里面去运行。需要创建一个着色器程序(shader program),然后把两个shader都附着上去。代码和前面的也很类似,也不用多说了。

// 创建一个着色器程序(shader program)
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader); // 附上vertexShader
	glAttachShader(shaderProgram, fragmentShader); // 附上fragmentSahder
	glLinkProgram(shaderProgram); // 链接起来

1.5 VAO的设置

傅老师的视频里面这里讲的比较形象,还画图了。
而且这个0可以是0-15,这里往哪放,顶点着色器里面就从哪里取(loaction = 这里的位置)。

	// VAO的设置
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

1.6 在渲染循环中渲染

		/*绘制三角形*/
		glBindVertexArray(VAO); // 绑定VAO
		glUseProgram(shaderProgram); // 使用我们刚才创建的shader program
		glDrawArrays(GL_TRIANGLES, 0, 3); //绘制三角形,三个顶点

1.7 完整代码

#include 
#include 
#include 
using namespace std;

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

#pragma region shader源码

// 顶点着色器源码
const char* vertex_shader_source =
"#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"
"}													\n";

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

#pragma endregion

int main() {

#pragma region 窗口创建以及初始化
	glfwInit();// 初始化GLFW
	/*glfwWindowHint用来配置GLFW,第一个参数是选项名,第二个参数是这个选项的值*/
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);// 将主版本号设为3(因为用的OpenGL版本是3.3)
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 将此版本号设为3
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 使用核心模式
	//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);如果是MAC OS X系统需要加上这句

	/*创建窗口对象*/
	GLFWwindow* window = glfwCreateWindow(800, 600, "OpengGL_Testing", nullptr, nullptr);// 设置窗口大小、名字
	if (window == nullptr) {
		cout << "创建窗口失败" << endl;
		glfwTerminate();// 销毁所有剩余的窗口和游标
		return -1;
	}
	glfwMakeContextCurrent(window);// 设置当前的上下文(OpenGL是一个庞大的状态机,其状态被称为上下文)

	/*使用GLAD管理指针*/
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		cout << "初始化GLAD失败" << endl;
		return -1;
	}

	/*设置视口,告诉OpenGL渲染窗口的尺寸大小*/
	glViewport(0, 0, 800, 600);// 前两个是窗口左下角的位置,后两个是宽和高

	/*告诉GLFW,每次窗口调用的时候使用framebuffer_size_callback函数*/
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

#pragma endregion

#pragma region 绘制三角形相关

	// 三角形的顶点数据
	float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f
	};

	// 创建顶点数组对象VAO(Vertex Array Object)
	unsigned int VAO;
	glGenVertexArrays(1, &VAO); // 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
	glBindVertexArray(VAO); // 绑定VAO

	// 创建顶点缓冲对象VBO(Vertex Buffer Object)
	unsigned int VBO;
	glGenBuffers(1, &VBO);// 第一个参数是几个对象,如果是多个对象的时候可以在第二个参数传一个数组进去
	glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO

	// 将顶点数据绑定到GL_ARRAY_BUFFER上
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	// 创建顶点着色器(shader)
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertex_shader_source, nullptr); // 将shader源代码绑定到shader中
	glCompileShader(vertexShader); // 编译shader

	// 创建片元着色器(shader)
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragment_shader_source, nullptr); // 将shader源代码绑定到shader中
	glCompileShader(fragmentShader); // 编译shader

	// 创建一个着色器程序(shader program)
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader); // 附上vertexShader
	glAttachShader(shaderProgram, fragmentShader); // 附上fragmentSahder
	glLinkProgram(shaderProgram); // 链接起来

	// VAO的设置
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

#pragma endregion

#pragma region 渲染循环
	/*渲染循环*/
	// glfwWindowShouldClose用来检查window是否被要求退出
	while (!glfwWindowShouldClose(window)) {
		processInput(window); // 处理输入

		/*渲染操作*/
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 用颜色清空屏幕
		glClear(GL_COLOR_BUFFER_BIT);// 清空一个缓冲位的缓冲(还不太理解)


		/*绘制三角形*/
		glBindVertexArray(VAO);
		glUseProgram(shaderProgram);
		glDrawArrays(GL_TRIANGLES, 0, 3);


		glfwSwapBuffers(window); // 交换颜色缓冲
		glfwPollEvents(); // 检查有没有触发事件(比如键盘输入、鼠标移动等等),更新窗口状态,并调用对应的回调函数
	}
#pragma endregion

	/*释放资源*/
	glfwTerminate();

	return 0;
}

// 回调函数,窗口大小改变的时候也应该改变视口的大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	// 不知道这个window有什么作用
	glViewport(0, 0, 800, 600);
}

// 处理输入
void processInput(GLFWwindow* window) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)// 判断是否按下Esc
		glfwSetWindowShouldClose(window, true);// 将窗口设置为需要关闭
}

运行的结果就是这样的。
OpenGL学习笔记2——三角形的绘制1_第2张图片

1.8 EBO

我们想画一个四边形,我们就需要四个顶点。然后OpenGL是按三角形绘制的,按照上面的方法就需要六个顶点(其中有两对是重复的)。这样是很浪费资源的,特别是在重复的顶点特别多的时候(正常情况下都是这样的情况)。

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,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

所以开始引入了EBO(索引缓冲对象,Element Buffer Object),用索引指定哪三个顶点是一个三角形。

	// 三角形的顶点数据
	float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f,
	 0.9f,  0.8f, 0.0f
	};

	unsigned int indices[] = { // 注意索引从0开始! 
	0, 1, 2, // 第一个三角形
	1, 2, 3  // 第二个三角形
	};

可以在创建VAO和VBO的后面同样的创建EBO。

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

渲染循环里面把原来的glDrawArrays替换为glDrawElements。还有要绑上EBO。

		//glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 使用框线模式渲染,也可以去掉。
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

这样就画出了一个四边形。
OpenGL学习笔记2——三角形的绘制1_第3张图片
下次再试试LearnOpenGL后面的练习,把这个弄清楚一点。

你可能感兴趣的:(#,OpenGL)