LearnGL - 04.1 - DrawDoubleQuad - UsingVAO


LearnGL - 学习笔记目录

本人才疏学浅,如有什么错误,望不吝指出。

上一篇:LearnGL - 04 - VAO 探究,对 VAO 有了个基本的了解。

这一篇:使用 VAO 来绘制两个不同材质的 Quad(Quadrilateral) 四边形。


顺便值得一提:前一个星期,EA 公司开源了两个游戏项目:《红色警戒》、《命令与征服》

然后进去瞄了一下源码,是 C++ 的,书写的相当工整,都是 1991~1995 年的代码。注释也是相当全面与工整,看起来很养眼。


因为要使用到 VAO 所以概述一下结构,用了Excel画了一些图解:

想要理解 VAO 的大致工作原理,最好想看看我之前写的一个模仿 OpenGL 的 API 来实现 VAO 的功能项目:LearnGL - 04 - VAO 探究

这里再简述一下 VAO 一般的使用方式:

  • 先生成 VAO
  • 再绑定 VAO
  • 再设置VAO 当前指向的 VBO
  • 再设置VAO 当前指向的 VBO格式
  • 再设置VAO 当前指向的 EBO
  • 绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式 之前先绑定 对应的 VAO

具体可以查看我上面的车时项目:LearnGL - 04 - VAO 探究

着色器的定义改了

这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便

// 这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
	gl_Position = transformMat * vec4(vPos, 1.0);
	fCol = vCol;
}
)glsl";

static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
	gl_FragColor = vec4(fCol, 1.0);
}
)glsl";

顶点坐标、颜色,索引缓存数据定义

GLfloat vertices[] = {
	// x,	y,	  z
	// 直接放4个顶点
	-0.25f, -0.25f, 0.0f,						// 第0个顶点,左下角
	 0.25f, -0.25f, 0.0f,						// 第1个顶点,右下角
	 0.25f,  0.25f, 0.0f,						// 第2个顶点,右上角
	-0.25f,  0.25f, 0.0f,						// 第3个顶点,左上角
};

GLfloat colors_1[] = {							// 顶点颜色缓存数据1
	1.0f, 0.0f, 0.0f,							// 第0个顶点颜色
	0.0f, 1.0f, 0.0f,							// 第1个顶点颜色
	1.0f, 1.0f, 0.0f,							// 第2个顶点颜色
	0.0f, 0.0f, 1.0f,							// 第3个顶点颜色
};

GLfloat colors_2[] = {							// 顶点颜色缓存数据2
	1.0f, 1.0f, 0.0f,							// 第0个顶点颜色
	0.0f, 1.0f, 1.0f,							// 第1个顶点颜色
	1.0f, 1.0f, 1.0f,							// 第2个顶点颜色
	1.0f, 0.0f, 1.0f,							// 第3个顶点颜色
};

GLuint indices[] = {							// 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
	0, 1, 3,									// 放置顶点的索引,第一个三角形
	1, 2, 3										// 放置顶点的索引,第二个三角形
};

因为这次绘制的两个 Quad 都是不同的颜色的,所以我们的 colors 有两个缓存数据。

而顶点的坐标、绘制索引顺序都是一样的,索引这些数据都没有变化。

VAO 使用

要记得 VAO 的使用步骤哦(前面有列出来的):

  • 先生成 VAO
  • 再绑定 VAO
  • 再设置VAO 当前指向的 VBO
  • 再设置VAO 当前指向的 VBO格式
  • 再设置VAO 当前指向的 EBO
  • 绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式 之前先绑定 对应的 VAO

先生成 VAO

GLuint vertex_array_object[2];
...
	glGenVertexArrays(2, vertex_array_object);						// 生成两个 VAO
	glGenBuffers(3, vertex_buffer);									// 创建3个 VBO,这里我们因为有一个一样的顶点,两个不同的顶点顔色
	glGenBuffers(1, &index_buffer);									// 创建1个 EBO,因为两个 Quad 的顶点索引顺序都是一样的
  • glGenVertexArrays(2, vertex_array_object); 生成了2个 VAO 指针,分别用于指向两个 Quad 的绘制 VBO,EBO,VBO格式,等。
  • glGenBuffers(3, vertex_buffer); 生成3个 VBO 指针,[0] 是坐标,都一样;[1]Quad[0] 的顶点颜色 绑定于VAO[0][2]Quad[1] 的顶点颜色 绑定于VAO[1]
  • glGenBuffers(1, &index_buffer); 生成一个 EBO 指针,用于两个 VAO 都指向这个同样的 EBO。

主要是 glGenVertexArrays(2, vertex_array_object);生成 VBO

再绑定 VAO

	glBindVertexArray(vertex_array_object[0]);						// 绑定 VAO[0],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 VAO

执行这句后,当前的 VAO 就是 vertex_array_object[0] 对应的 VAO。

再设置VAO 当前指向的VBO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 设置 VBO 坐标数据

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors_1), colors_1, GL_STATIC_DRAW); // 设置 VBO 颜色数据

这时绑定的 VBO 数据都会在 VAO 对象中有指针保存着。(当前的 VAO 是 vertex_array_object[0]

再设置VAO 当前指向的VBO格式

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0],因为后面要设置该 VBO 的坐标格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 启用 顶点缓存 location 位置的属性

这时在 glVertexAttribPointer 后,当前的 VBO 的格式将保存在该 VAO 对象中。(当前的 VAO 是 vertex_array_object[0]

再设置VAO 当前指向的EBO

EBO 的绘制也不是必须的,主要看的绘制接口是否用到。(当然 VBO 也不是必须的,因为可以使用其他绘制接口,如:立即绘制模式的接口。但如果 VBO 都不用的话, VAO就没有什么意义了)

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 绑定 EBO
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据

绘制对应的 VAO 指向的数据的 VBO,EBO,和VBO格式之前,先绑定对应的VAO

...
glBindVertexArray(vertex_array_object[0]);				// 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0);
...

glDrawElements 接口之前说过是使用了 GL_ELEMENT_ARRAY_OBJECT 类型的缓存数据,就是索引缓存的数据。

另一个 vertex_array_object[1] 也是需要上面的过程来处理的。

唯独需要注意的是 colors_2 的顶点颜色设置是不同的。

glBindVertexArray(vertex_array_object[0]);
...
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[0]
glBufferData(GL_ARRAY_BUFFER, sizeof(colors_2), colors_2, GL_STATIC_DRAW); // 设置 VBO 颜色数据

绘制

第一个Quad

glBindVertexArray(vertex_array_object[0]);				// 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
mat4x4_identity(tMat);									// 给矩阵单位化,消除之前的所有变换
mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);				// x轴位移-0.5,注意是NDC下的坐标
glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。

瞧瞧,现在绘制简单多了,以后绘制多个对象的时候,都可以这么使用。

注意有:mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);,将第一个 Quad 以x轴位移到 NDC 坐标下偏移:-0.5f 的单位。

第二个Quad

		glBindVertexArray(vertex_array_object[1]);				// 先绘制 VAO[1] 的 VBO,EBO,VAF,ENABLED
		mat4x4_identity(rMat);									// 给矩阵单位化,消除之前的所有变换
		mat4x4_rotate_Z(rMat, rMat, (float)glfwGetTime());		// 先旋转,沿着 z 轴旋转,旋转量为当前 glfw 启用到现在的时间点(秒)
		mat4x4_translate(tMat, +0.5, 0.0f, 0.0f);				// 再位移
		mat4x4_mul(tranformMat, tMat, rMat);					// 将旋转与位移的变换合并
		glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tranformMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。

第二个 Quad 可以看到,除了有 位移,而且还有 旋转,然后还使用另一个 linmath.h 的API:mat4x4_mul,来将两个变换柔和在一起了,这里要注意 mat4x4_mul API中 参数2参数3 的位移,这里我是旋转,位移,将 变换的矩阵放到 边,后续 新增 变换的矩阵放 左边,有点类似 矩阵左乘(就是将新的要变换的矩阵放到左边去乘上右边原来的变换矩阵,如:有矩阵A,矩阵B。矩阵C=BA,B矩阵就是A的左乘矩阵)的。注意矩阵乘法是不满足交换律的,所以这些相乘的顺序要注意。

绘制效果

在这里插入图片描述

完整源码

// jave.lin
#include"glad/glad.h"
#include"GLFW/glfw3.h"
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include会有错误,所以放到iostream 后include就好了
// 而这个错误正式 xkeycheck.h 文件内 #error 提示的,所以可以使用 #define _XKEYCHECK_H 这个头文件的引用标记宏
// 就可以避免对 xkeycheck.h 头文件的 include 了。
#include<iostream>
#include"linmath.h"

#define PRINT_VERSION // 打印版本相关信息

#ifdef PRINT_VERSION

#define PROFILE_NAME_CORE   "core"
#define PROFILE_NAME_COMPAT "compat"

static const char* get_api_name(int api) {
	if (api == GLFW_OPENGL_API)
		return "OpenGL";
	else if (api == GLFW_OPENGL_ES_API)
		return "OpenGL ES";

	return "Unknown API";
}

static const char* get_profile_name_gl(GLint mask) {
	if (mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
		return PROFILE_NAME_COMPAT;
	if (mask & GL_CONTEXT_CORE_PROFILE_BIT)
		return PROFILE_NAME_CORE;

	return "unknown";
}

// 打印各种版本信息
static void print_infos(GLFWwindow* window) {
	//
	// ====== GLFW Library的版本 ======
	//
	int glfw_major, glfw_minor, glfw_revision;
	glfwGetVersion(&glfw_major, &glfw_minor, &glfw_revision);

	// 头文件声明版本
	printf("GLFW header version: %u.%u.%u\n",
		GLFW_VERSION_MAJOR,
		GLFW_VERSION_MINOR,
		GLFW_VERSION_REVISION);
	// 库版本
	printf("GLFW library version: %u.%u.%u\n", glfw_major, glfw_minor, glfw_revision);
	// 库版本的字符串描述
	printf("GLFW library version string: \"%s\"\n", glfwGetVersionString());

	//
	// ====== client, context, profile 的版本 ======
	//
	int ch, client, major, minor, revision, profile;

	client = glfwGetWindowAttrib(window, GLFW_CLIENT_API);
	major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
	minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
	revision = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION);
	profile = glfwGetWindowAttrib(window, GLFW_OPENGL_PROFILE);

	printf("%s context version string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_VERSION));

	printf("%s context version parsed by GLFW: %u.%u.%u\n",
		get_api_name(client),
		major, minor, revision);


	GLint mask;
	glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);

	printf("%s profile mask (0x%08x): %s\n",
		get_api_name(client),
		mask,
		get_profile_name_gl(mask));
	//
	// ====== render, vendor 的信息 ======
	//
	printf("%s context renderer string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_RENDERER));
	printf("%s context vendor string: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_VENDOR));

	printf("%s context shading language version: \"%s\"\n",
		get_api_name(client),
		glGetString(GL_SHADING_LANGUAGE_VERSION));
}

#endif // PRINT_VERSION

// 这回我们的着色器程序的硬边码直接使用的是C++的 R"flag()flag" 的raw-string的方式来定义,书写更方便
static const char* vertex_shader_text = R"glsl(
#version 450 compatibility
uniform mat4 transformMat;
attribute vec3 vPos;
attribute vec3 vCol;
varying vec3 fCol;
void main() {
	gl_Position = transformMat * vec4(vPos, 1.0);
	fCol = vCol;
}
)glsl";

static const char* fragment_shader_text = R"glsl(
#version 450 compatibility
varying vec3 fCol;
void main() {
	gl_FragColor = vec4(fCol, 1.0);
}
)glsl";

GLfloat vertices[] = {
	// x,	y,	  z
	// 直接放4个顶点
	-0.25f, -0.25f, 0.0f,						// 第0个顶点,左下角
	 0.25f, -0.25f, 0.0f,						// 第1个顶点,右下角
	 0.25f,  0.25f, 0.0f,						// 第2个顶点,右上角
	-0.25f,  0.25f, 0.0f,						// 第3个顶点,左上角
};

GLfloat colors_1[] = {							// 顶点颜色缓存数据1
	1.0f, 0.0f, 0.0f,							// 第0个顶点颜色
	0.0f, 1.0f, 0.0f,							// 第1个顶点颜色
	1.0f, 1.0f, 0.0f,							// 第2个顶点颜色
	0.0f, 0.0f, 1.0f,							// 第3个顶点颜色
};

GLfloat colors_2[] = {							// 顶点颜色缓存数据2
	1.0f, 1.0f, 0.0f,							// 第0个顶点颜色
	0.0f, 1.0f, 1.0f,							// 第1个顶点颜色
	1.0f, 1.0f, 1.0f,							// 第2个顶点颜色
	1.0f, 0.0f, 1.0f,							// 第3个顶点颜色
};

GLuint indices[] = {							// 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
	0, 1, 3,									// 放置顶点的索引,第一个三角形
	1, 2, 3										// 放置顶点的索引,第二个三角形
};

static void error_callback(int error, const char* description) {
	fprintf(stderr, "ErrorCode : %d(0x%08x), Error: %s\n", error, error, description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { // 当键盘按键ESCAPE按下时,设置该window为:需要关闭
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GLFW_TRUE);
}

int main() {
	glfwSetErrorCallback(error_callback); // 安装glfw内部错误时的回调

	if (!glfwInit()) { // 初始化glfw
		std::cout << "glfwInit FAILURE" << std::endl; // 初始化失败
		exit(EXIT_FAILURE);
	}
	// 设置最低的openGL 版本,major:主版本号,minor:次版本号
	// openGl 太低版本的话是不支持CORE Profile模式的
	// 会报错:ErrorCode: 65540(0x00010004), Error : Context profiles are only defined for OpenGL version 3.2 and above
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
	// 根据上面的错误提示,至少使用3.2才行,这里我们使用4.5
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

	// core profile 下运行有问题,不显示任何内容,但不会报错。
	// 着色器编译、着色器程序链接都没有错误日志信息。
	// 很有可能是因为我参考的学习网站使用的API相对比较老,使用的是3.3的。
	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	// 所以这里我们不设置 major, minor的版本,默认使用本计算机能用的最高版本
	// 使用 compatibility profile 就有内容出现了。

	int width = 800;
	int height = 600;

	// 使用glfw创建窗体
	GLFWwindow* window = glfwCreateWindow(width, height, "jave.lin - Learning OpenGL - 02_01 DrawTriangle_Extensions", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl; // 构建窗体失败
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback); // 安装glfw内部键盘按键的回调

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { // 装载OpenGL的C函数库
		std::cout << "Failed to initialize OpenGL context" << std::endl; // 装载报错
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

#ifdef PRINT_VERSION
	// 打印版本信息
	print_infos(window);
#endif

	int nrAttributes;
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
	std::cout << "Maximum number of vertex attributes supported : " << nrAttributes << std::endl;

	GLint mat_location, vpos_location, vcol_location;
	GLuint vertex_buffer[3], index_buffer, vertex_shader, fragment_shader, program;
	GLuint vertex_array_object[2];
	GLint success, infoLogLen;

	vertex_shader = glCreateShader(GL_VERTEX_SHADER);				// 创建 顶点着色器
	glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);	// 设置 顶点着色器源码
	glCompileShader(vertex_shader);									// 编译 顶点着色器

	glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);		// 获取着色器编译状态
	if (!success) {													// 如果编译不成功
		glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
		GLchar* infoLog = (GLchar*)malloc(infoLogLen);				// 如果编译失败,则将编译日志储存到:infoLog 中
		glGetShaderInfoLog(vertex_shader, infoLogLen, NULL, infoLog);
		std::cout << "Vertex Shader Compiling Error Status: "		// 输出编译错误日志信息
			<< success << ", Infomation Log : " << infoLog << std::endl;
		free(infoLog);
		exit(EXIT_FAILURE);
	}

	fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);				// 创建 片元着色器
	glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);	// 设置 片元着色器源码
	glCompileShader(fragment_shader);									// 编译 片元着色器

	glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);		// 获取着色器编译状态
	if (!success) {														// 如果编译不成功
		glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
		GLchar* infoLog = (GLchar*)malloc(infoLogLen);					// 如果编译失败,则将编译日志储存到:infoLog 中
		glGetShaderInfoLog(fragment_shader, infoLogLen, NULL, infoLog);
		std::cout << "Fragment Shader Compiling Error Status: "			// 输出编译错误日志信息
			<< success << ", Infomation Log : " << infoLog << std::endl;
		free(infoLog);
		exit(EXIT_FAILURE);
	}

	program = glCreateProgram();						// 创建着色器程序
	glAttachShader(program, vertex_shader);				// 附加 顶点着色器
	glAttachShader(program, fragment_shader);			// 附加 片元着色器
	glLinkProgram(program);								// 链接着色器程序

	glGetProgramiv(program, GL_LINK_STATUS, &success);	// 如果链接不成功
	if (!success) {
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
		GLchar* infoLog = (GLchar*)malloc(infoLogLen);	// 如果链接失败,则将链接日志储存到:infoLog 中
		glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
		std::cout << "Program Linking Error Status: "	// 输出链接错误日志信息
			<< success << ", Infomation Log : " << infoLog << std::endl;
		free(infoLog);
		exit(EXIT_FAILURE);
	}

	glDeleteShader(vertex_shader);						// 链接成功后,就可以删掉vertex 子程序了
	glDeleteShader(fragment_shader);					// 链接成功后,就可以删掉fragment 子程序了

	mat_location = glGetUniformLocation(program, "transformMat");	// 获取 着色器程序的 uniform 变量的 location
	vpos_location = glGetAttribLocation(program, "vPos");			// 获取 顶点着色器中的顶点 attribute 属性的 location
	vcol_location = glGetAttribLocation(program, "vCol");			// 获取 顶点着色器中的顶点 attribute 属性的 location

	glGenVertexArrays(2, vertex_array_object);						// 生成两个 VAO

	glGenBuffers(3, vertex_buffer);									// 创建3个 VBO,这里我们因为有一个一样的顶点,两个不同的顶点顔色
	glGenBuffers(1, &index_buffer);									// 创建1个 EBO,因为两个 Quad 的顶点索引顺序都是一样的
	//
	// === VAO[0] ===
	//
	glBindVertexArray(vertex_array_object[0]);						// 绑定 VAO[0],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 VAO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 设置 VBO 坐标数据

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors_1), colors_1, GL_STATIC_DRAW); // 设置 VBO 颜色数据

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 绑定 EBO
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0],因为后面要设置该 VBO 的坐标格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 启用 顶点缓存 location 位置的属性

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[1],因为后面要设置该 VBO 的颜色格式
	glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vCol 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vcol_location);						// 启用 顶点缓存 location 位置的属性

	//
	// === VAO[1] ===
	//
	glBindVertexArray(vertex_array_object[1]);						// 绑定 VAO[1],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 VAO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[1]
	//glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 设置 VBO 坐标数据,这里不用再设置坐标,因为都一样

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[2]);				// 绑定 VBO[2]
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors_2), colors_2, GL_STATIC_DRAW); // 设置 VBO 颜色数据,颜色就需要重新设置了,因为不一样

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 绑定 EBO
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据,这里不用再设置索引值,因为都一样

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0],因为后面要设置该 VBO 的坐标格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 启用 顶点缓存 location 位置的属性

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[2]);				// 绑定 VBO[2],因为后面要设置该 VBO 的颜色格式
	glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vCol 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vcol_location);						// 启用 顶点缓存 location 位置的属性

	//glEnable(GL_CULL_FACE);										// 开启面向剔除
	//glCullFace(GL_BACK);										// 设置剔除背面
	GLboolean cf = glIsEnabled(GL_CULL_FACE);					// 查看是否启用面向剔除
	std::cout << "cull face enabled : " << (cf ? "true" : "false") << std::endl;

	//glFrontFace(GL_CW);											// 顺时针
	//glFrontFace(GL_CCW);										// 逆时针(默认的)ClockWise
	GLint facing;
	glGetIntegerv(GL_FRONT_FACE, &facing);						// 获取正面的顺逆时针 : CW(ClockWise - 顺时针), CCW(Counter ClockWise - 逆时针)
	std::cout << "facing : " << (facing == GL_CW ? "CW" : "CCW") << std::endl;

	mat4x4 rMat, tMat, tranformMat;											// 声明定义一个 mat4x4 用的旋转矩阵

	while (!glfwWindowShouldClose(window)) {					// 检测是否需要关闭窗体

		glfwGetFramebufferSize(window, &width, &height);		// 获取窗口大小

		glViewport(0, 0, width, height);						// 设置Viewport
		glClearColor(0.1f, 0.2f, 0.1f, 0.f);					// 设置清理颜色缓存时,填充颜色值
		glClear(GL_COLOR_BUFFER_BIT);							// 清理颜色缓存

		glUseProgram(program);									// 使用此着色器程序,两个 VAO 的着色都一样,设置一些 uniform 不一样

		glBindVertexArray(vertex_array_object[0]);				// 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
		mat4x4_identity(tMat);									// 给矩阵单位化,消除之前的所有变换
		mat4x4_translate(tMat, -0.5, 0.0f, 0.0f);				// x轴位移-0.5,注意是NDC下的坐标
		glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。

		glBindVertexArray(vertex_array_object[1]);				// 先绘制 VAO[1] 的 VBO,EBO,VAF,ENABLED
		mat4x4_identity(rMat);									// 给矩阵单位化,消除之前的所有变换
		mat4x4_rotate_Z(rMat, rMat, (float)glfwGetTime());		// 先旋转,沿着 z 轴旋转,旋转量为当前 glfw 启用到现在的时间点(秒)
		mat4x4_translate(tMat, +0.5, 0.0f, 0.0f);				// 再位移
		mat4x4_mul(tranformMat, tMat, rMat);					// 将旋转与位移的变换合并
		glUniformMatrix4fv(mat_location, 1, GL_FALSE, (const GLfloat*)tranformMat); // 设置, 着色器中 uniform mat4 rMat; 的矩阵数据
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。

		glfwSwapBuffers(window);								// swap buffer, from backbuffer to front buffer
		glfwPollEvents();										// 处理其他的系统消息
	}

	glfwDestroyWindow(window);									// 销毁之前创建的window对象
	glfwTerminate();											// 清理glfw之前申请的资源
	return 0;
} // int main() {

总结

如果没有 VAO 的话,我们每次绘制都需要绑定VBO,EBO,还有设置顶点格式(规范)。

但有了 VAO 对象来保存这些配置有,我们在绘制前只需要绑定对应的 VAO,就可以使用VAO内绑定的VBO,EBO,还有设置顶点格式(规范)。

以后绘制多个对象的时候,都可以这么使用,也是很方便的。

References

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值