OpenGL 渲染一个三角形

本篇是通过OpenGL库,版本为3.3,渲染出一个三角形,效果如下:
OpenGL 渲染一个三角形

参考链接:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/


渲染流程如下:(GLFW配置正常写,可参考上篇博文:OpenGL 渲染一个窗口

  • 创建顶点着色器程序并编译:glCreateShader(GL_VERTEX_SHADER)
  • 创建片段着色器程序并编译:glCreateShader(GL_FRAGMENT_SHADER)
  • 定义并编译链接程序(将多个着色器合并之后并最终链接完成的版本):glCreateProgram()
  • 定义三角形的三维结构坐标(我们渲染是三角形,所以z轴坐标为0)
  • 定义并绑定与解绑VBO和VAO: 注意VBO与VAO的绑定与解绑顺序(绑定: 先VAO 再VBO;解绑:先VBO 再VAO))
  • 在渲染循环中绘制图形

至于为什么要绑定与解绑(一家之言):这里的绑定与解绑不是对VBO或VAO怎么样,而是把VBO绑定到OpenGL的特定传输通道(不一定是传输通道,可以理解成一种数据结构,如:GL_ARRAY_BUFFER)上。

while循环每循环一次就需要绑定一次(传输一次数据),绑定好理解,要绘制图形,肯定要传数据过去,就绑定,至于解绑,首先不一定要写,测试过将解绑语句注释掉依旧可以运行,此外当你再次绑定时,之前绑定的就被覆盖了,相当于解绑。

所以不要过分纠结绑定与解绑,但一定要注意绑定顺序,先VAO再VBO再EBO

如何绑定:

  • 使用glBindVertexArray绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用
  • 绑定VAO之后,当目标是 GL_ARRAY_BUFFER 或者 GL_ELEMENT_ARRAY_BUFFER 的时候,VAO会储存glBindBuffer的函数调用,也就会自动存储VBO和EBO
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

关于绘图:

// 不使用EBO
/* 
* glDrawArrays(GL_TRIANGLES, 0, 3):
* 第一个参数是我们打算绘制的OpenGL图元的类型
* 第二个参数指定了顶点数组的起始索引,我们这里填0
* 第三个参数指定我们打算绘制多少个顶点
*/
glDrawArrays(GL_TRIANGLES, 0, 3);

// 使用EBO
/*
* glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0):
* 第一个参数指定了我们绘制的模式
* 第二个参数是我们打算绘制顶点的个数
* 第三个参数是索引的类型
* 最后一个参数里我们可以指定EBO中的偏移量
*/
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

源码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>


// 定义回调函数
// --------------
// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
// 窗口按键输入
void processInput(GLFWwindow* window);

// 其他相关变量
// --------------
// 宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

// 顶点着色器
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* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";

int main()
{
	// 1.初始化窗口和设置属性
	// ----------------------
	// 1.1 初始化和配置 glfw
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// 1.2 创建glfw窗口
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Hello world", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 1.3 使用glad加载OpenGL函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to inittalize GLAD" << std::endl;
		return -1;
	}

	// 1.4 确定视口(Viewport)的大小
	glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// 2.设置两个必须的着色器:顶点和片段着色器
	// ----------------------------------------
	// 2.1 设置顶点着色器
	unsigned int vertexShader; 
	vertexShader = glCreateShader(GL_VERTEX_SHADER);            // 把需要创建的着色器类型以参数形式提供给glCreateShader
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // 把这个着色器源码附加到着色器对象上
	glCompileShader(vertexShader);                              // 编译源码

	// 2.2 检测顶点着色器编译结果
	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;
	}

	// 2.3 片段着色器
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);        // 使用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::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}
	// 3.定义并编译链接程序
	// --------------------
	// 3.1 着色器程序:多个着色器合并之后并最终链接完成的版本,称链接
	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::LINK_FAILED\n" << infoLog << std::endl;
	}

	// 3.2 着色器对象链接到程序对象以后,就不再需要它们,可以删除着色器对象
	glDeleteShader(vertexShader); 
	glDeleteShader(fragmentShader);
	
	// 4.定义物体三维结构坐标
	// ---------------------- 
	// 定义三角形3D坐标
	float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f
	};

	// 5.定义并绑定与解绑VBO和VAO: 注意VBO与VAO的绑定与解绑顺序
	// --------------------------------------------------
	// 5.1 设置顶点缓冲对象(Vertex Buffer Objects, VBO)管理内存(坐标点数据)与 VAO
	unsigned int VBO, VAO;
	glGenVertexArrays(1, &VAO);  // 使用glGenVertexArrays函数和一个缓冲ID生成一个VAO对象
	glGenBuffers(1, &VBO);       // 使用glGenBuffers函数和一个缓冲ID生成一个VBO对象

	// 5.2 绑定: 先VAO 再VBO
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);  // 使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
	// 调用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)
	// 
	// 5.3 传入数据:调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	// 5.4 设置顶点属性指针:在渲染前指定OpenGL该如何解释顶点数据
	// 使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	// 使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性,顶点属性默认是禁用的
	glEnableVertexAttribArray(0);

	// 5.5 解绑:先VBO 再VAO
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	// 这是一段很重要的话,阐述VAO的作用:
	// ----------------------------------
	/*
	从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
	当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
	这段代码应该看起来像这样:
		// 1. 绑定VAO
		glBindVertexArray(VAO);
		// 2. 把顶点数组复制到缓冲*OpenGL使用
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
		// 3. 设置顶点属性指针
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);
		// 4. 绘制物体
		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		someOpenGLFunctionThatDrawsOurTriangle();

	注:前面做的一切都是等待这一刻,一个储存了我们顶点属性配置和应使用的VBO的顶点数组对象。
	一般当你打算绘制多个物体时,你首先要生成/配置所有的VAO(和必须的VBO及属性指针),然后储存它们供后面使用。
	当我们打算绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。
	如果乜有VAO,就需要每绘制一个物体时,就需要重复下面这个过程:
		// 0. 复制顶点数组到缓冲*OpenGL使用
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
		// 1. 设置顶点属性指针
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);
		// 2. 当我们渲染一个物体时要使用着色器程序
		glUseProgram(shaderProgram);
		// 3. 绘制物体
		someOpenGLFunctionThatDrawsOurTriangle();
	*/

	// 6.开启渲染循环(Render Loop)
	while (!glfwWindowShouldClose(window))
	{
		// 6.1 输入控制:
		processInput(window);

		// 6.2 渲染指令
		// 修改背景颜色
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		// 绘图
		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		
		// 6.3 检查调用事件,并交换缓冲
		glfwPollEvents();
		glfwSwapBuffers(window);
	}

	// 7.回收资源
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteProgram(shaderProgram);

	glfwTerminate();
	return 0;
}

// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}
// 窗口按键输入
void processInput(GLFWwindow* window)
{
	// 按下esc按键,退出程序
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}
上一篇:OPenGl 引用 wingdi.h报错


下一篇:2021年Android程序员职业规划!大厂面经合集