OpenGL--1.入门

目录

[TOC]

一、写在前面

1.学习原因

想要完全掌握 Unity 中的 Shader 的代码编写,学习 OpenGL 将是必不可少的一步,此前已经有过 UnityShader 的学习经历,相信 OpenGL 的学习将不会太难。

2.学习资源

(1)LearnOpenGL

教程出了很多内容了,作者还在更新,有完整中文译版。

难度适合入门

LearnOpenGL(翻译)

LearnOpenGL(原版)

配套Code

(2)OpenGL-Tutorial

OpenGL-Tutorial

还在翻译中,有部分还是英文。

难度适合入门

(3)OGLdev

OGLdev

一步步学OpenGL3.3+

中文还在翻译中,超过一半课程有中文。

难度适合有过基础OpenGL学习的人

(4)Learn Computer Graphics From Scratch!

Learn Computer Graphics From Scratch!

计算机图形学,全英文,适合进阶

3.环境配置前的简要说明

(1)OpenGL

OpenGL 函数库相关的 API 有核心库(gl),实用库(glu),辅助库(aux)、实用工具库(glut),窗口库(glx、agl、wgl)和扩展函数库等。

gl 是核心,glu 是对 gl 的部分封装。glx、agl、wgl 是针对不同窗口系统的函数。glut 是为跨平台的 OpenGL 程序的工具包,比aux功能强大(aux很大程度上已经被 glut 库取代。)。扩展函数库是硬件厂商为实现硬件更新利用OpenGL的扩展机制开发的函数。

(2)GULT(OpenGL工具库–OpenGL Utility Toolkit)

这部分函数以 glut 开头。主要包括窗口操作函数,窗口初始化、窗口大小、窗口位置等函数;回调函数:响应刷新消息、键盘消息、鼠标消息、定时器函数等;创建复杂的三维物体;菜单函数;程序运行函数。gult 对应的开源实现是 freegult。

(3)GLEW

GLUT 或者 FREEGLUT 主要是1.0的基本函数功能;GLEW 是使用 OPENGL2.0 之后的一个工具函数。

不同的显卡公司,也会发布一些只有自家显卡才支 持的扩展函数,你要想用这数涵数,不得不去寻找最新的 glext.h ,有了 GLEW 扩展库,你就再也不用为找不到函数的接口而烦恼,因为 GLEW 能自动识别你的平台所支持的全部 OpenGL 高级扩展函数。也就是说,只要包含一个 glew.h 头文件,你就能使用 gl , glu , glext , wgl , glx 的全部函数。

(4)GLAD

GLAD 为 GLEW 升级版。

(5)GLFW

GLFW 无愧于其号称的 lightweight 的 OpenGL 框架,的确是除了跨平台必要做的事情都没有做,所以一个头文件,很少量的 API,就完成了任务。GLFW 的开发目的是用于替代 glut 的,从代码和功能上来看,我想它已经完全的完成了任务。

一个轻量级的,开源的,跨平台的 library。支持 OpenGL 及 OpenGL ES,用来管理窗口,读取输入,处理事件等。因为 OpenGL 没有窗口管理的功能,所以很多热心的人写了工具来支持这些功能,比如早期的 glut,现在的 freeglut 等。

那么 GLFW 有何优势呢?glut 太老了,最后一个版本还是90年代的。freeglut 完全兼容 glut,算是 glut 的代替品,功能齐全,但是bug太多。稳定性也不好,GLFW 应运而生。

(6)总结

  • 1.OpenGL 只有框架没有实现,换句话说就是 OpenGL 只有函数声明没有实现,类似于接口和虚函数。所有的实现是显卡生产商提供。比如 NVIDIA (英伟达)就要自己实现 OpenGL 函数内容,所以不同的生产商可以对自己的产品提供优化,毕竟代码是自己写的。
  • 2.glfw 是 glut 的升级和改进。glfw 是用来显示窗口和捕捉窗口事件的一套 API。OpenGL 只是一套控制 GPU 的规则,并没有对于跨平台窗口显示和事件进行规定,所以需要一个显示显卡渲染的窗口,这就是 glfw 的作用。
  • 3.glew 包含了OpenGL所需的核心:<补充>第一条已经说过openGL的实现是显卡生产商,那么系统如何才能找到这些实现好的函数呢?而且不同的平台函数存放地方还不同,文件结构也不同。有没有一种方式能够自动找到 OpenGL 的函数?这就是 glew 的作用-->用来找 openGL 的函数,并初始化,这样我们就能直接调用 OpenGL 的函数了。
  • 4.Glad 为 Glew 升级版。

4.环境配置

参照此人的环境配置步骤

配置步骤详尽,亲测运行正常。

VS为2017版

GLFW库下载官网 下载的为Source packageglfw-3.3.1.zip

GLEW库下载官网 下载的为ZIP文件:Windows 32-bit and 64-bit

GLAD库下载官网 下载地址、配置方法见环境配置

CMake 下载为cmake-3.16.2-win64-x64.zip,用于编译 GLFW

首次搭建环境(省略 VS 上 C++ 的环境搭建)

相关配置文件,提供 网盘(提取码:bxnt)下载

一、构建 GLFW

  • Step1.下载 GLFW

    picture0

  • Step2.下载 CMake

    picture1

  • Step3.解压上述文件、启动 CMake(bin 目录下的 cmake-gui.exe

  • Step4.CMake 指定 源码目录存放编译结果的目标文件目录

    源码目录GLFW 源码的根目录

    存放编译结果的目标文件目录 :在 GLFW 源码根目录下新建 build 文件夹,选中作为目标目录

    picture2

  • Step5.点击 Configure(设置)按钮,选择工程的生成器(VS2017)

    picture3

  • Step6.设置完成之后,再次点击 Configure(设置)按钮来保存设置。之后点击 Generate(生成)按钮,生成的文件在之前新建的 build文件夹中。

    picture4

  • Step7. **在 build 文件夹里可以找到 **GLFW.sln 文件,用 Visual Studio 2017 打开。 因为 CMake 已经配置好了项目,所以我们直接 生成解决方案,然后编译的库 glfw3.lib 就会出现在 src/Debug 文件夹内。

  • Step8.建立一个新的目录包含所有的 第三方库文件头文件 ,并且在你的 IDE 或编译器中指定这些文件夹。

    使用一个单独的文件夹,里面包含 libsinclude 文件夹,在这里存放 OpenGL 工程用到的所有第三方库和头文件。

    picture5

  • Step9.将刚刚编译好的 glfw3.lib 文件放在新建的 libs 文件夹下,将之前下载的 glfw 文件中 include 文件夹下的 GLFW 文件复制粘贴到新建的 include 文件夹中。

  • Step10.新建 VS C++ 空项目

  • Step11.GLFW 库链接进工程

    设置 包含目录库目录

    picture6

    要链接一个库我们必须告诉链接器它的文件名。库名字是 glfw3.lib,把它加到附加依赖项字段中,这样 GLFW 在编译的时候就会被链接进来了。

    picture7

二、配置 GLAD 库

  • Step1.下载 GLAD

    如下进行在线配置:

    picture8

    配置之后,点击下图压缩包进行下载:

    picture9

  • Step2.解压文件,会得到 includesrc 两个文件夹。

    将解压后的 include 中的文件(gladKHR)移动到之前新增的 include 文件中:

    picture10

    将解压得到的 src 文件夹移动到和之前新增的 include 同级的目录中;将 src 文件夹中的 glad.c 添加到工程项目源文件中:

    picture11

    picture12

    picture13

二、

基础实验配置

包含目录:

E:\Work\OpenGl\OpenGLResources\glfw-3.3.1.bin.WIN32\include E:\Work\OpenGl\OpenGLResources\glad\include

库目录:

E:\Work\OpenGl\OpenGLResources\glfw-3.3.1.bin.WIN32\lib-vc2015

实验中添加新项目后:(重复,每次新实验均需要下列步骤)

Step1:手动添加 glad.c 文件到 源文件 中;

Step2:项目–右键–属性–链接器–输入:

附加依赖项:(添加)
opengl32.lib
glfw3.lib
assimp-vc140-mt.lib

忽略特定默认库:(添加)
MSVCRT.lib

基础概念

(1)EBO

EBO:(Element Buffer Object)索引缓冲区对象,用于存储顶点的索引信息。

(2)VBO

VBO:(Vertex Buffer Object)顶点缓冲对象,用于存储顶点的各种信息。

将模型点的顶点信息存储到 GPU 显存中,如此一来每次画模型时,取数据的效率大大提高。

每次绘制模型时均需要绑定 VBO ,比较繁琐,于是引出 VAO:

(3)VAO

VAO:(Vertex Array Object)顶点数组对象。保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的 VBO 对象的引用。(存储的 VBO 的引用)

1.绘制渲染窗口

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

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

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//查看GLFW版本号:
	int major, minor, rev;
	glfwGetVersion(&major, &minor, &rev);
	printf("GLFW %d.%d.%d initialized\n", major, minor, rev);
	//创建 GLFW 窗口:
	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();//释放资源
		return -1;
	}
	glfwMakeContextCurrent(window);//将 window 注入当前主线程上下文
	//初始化 GLAD(用于管理 OpenGL 函数指针):
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		glfwTerminate();//释放资源
		return -1;
	}
	//创建渲染窗口:(Viewport 视口)
	glViewport(0, 0, 800, 600);
	//注册回调:(Window 改变时改变 Viewport)
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	//渲染循环:(Render Loop)
	while (!glfwWindowShouldClose(window))
	{
		//清空颜色缓冲:
		glClearColor(0.0f, 0.34f, 0.57f, 1.0f);//设置清空屏幕的颜色值
		glClear(GL_COLOR_BUFFER_BIT);//清空颜色缓冲

		//输入:
		processInput(window);

		//渲染指令:

		//检查调用事件 并 交换颜色缓冲
		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);
	}
}

2.在窗口中绘制三角形

/***
 * 例程  绘制三角形
 * 步骤:
 * 1-初始化:   GLFW窗口,GLAD。
 * 2-数据处理: 给定顶点数据,生成并绑定VAO&VBO(准备在GPU中进行处理),设置顶点属性指针(本质上就是告诉OpenGL如何处理数据)。
 * 3-着色器:   给出顶点和片段着色器,然后链接为着色器程序,渲染时使用着色器程序。
 * 4-渲染:     清空缓冲,绑定纹理,使用着色器程序,绘制三角形,交换缓冲区检查触发事件后释放资源
 */

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

int screen_width = 1280;
int screen_height = 720;

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

int main()
{
	//Step1:初始化 OpenGL
	//  初始化 GLFW:
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	int major, minor, rev;
	glfwGetVersion(&major, &minor, &rev);
	printf("GLFW %d.%d.%d initialized\n", major, minor, rev);
	//  创建 window:
	GLFWwindow* window = glfwCreateWindow(screen_width, screen_height, "Draw Triangle", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create OpenGL contex" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	//  初始化 GLAD:(加载 OpenGL 函数指针地址的函数)
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		glfwTerminate();
		return -1;
	}
	std::cout << "GLAD initialize done" << std::endl;
	//  设置Viewport 尺寸:
	glViewport(0, 0, screen_width, screen_height);
	//  注册 framebuffer_size_callback 回调(Window 改变时改变 Viewport)
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	std::cout << "OpenGL initialize done" << std::endl;


	//Step2:顶点输入
	//  创建三角形顶点数据:
	const float triangle[] = {
		-0.5f,0.5f,0.0,
		0.5f,0.0f,0.0f,
		0.0f,-0.5f,0.0f
	};


	//Step3:数据处理
	//  生成并绑定 VBO:
	GLuint vertex_buffer_object;
	glGenBuffers(1, &vertex_buffer_object); //一个缓冲 ID 生成一个 VBO 对象
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	//  将顶点数据绑定至当前默认的缓冲中:
	glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);
	//  生成并绑定 VAO:
	GLuint vertex_array_object;
	glGenVertexArrays(1, &vertex_array_object);
	glBindVertexArray(vertex_array_object);
	//  设置顶点属性指针:(向 OpenGL 解释顶点数据)
	//    参数1:顶点着色器的位置值
	//    参数2:顶点属性是一个三分量的向量
	//    参数3:顶点属性类型
	//    参数4:顶点数据是否需要标准化
	//    参数5:步长--连续顶点属性之间的间隔
	//    参数6:数据的偏移量(此处位置属性在数组的开头,所以是0)
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//  开启 0 的这个通道:
	glEnableVertexAttribArray(0);
	//  解绑 VAO 和 VBO:
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);


	//Step4:顶点着色器与片段着色器
	//  顶点着色器源码:
	const char* vertex_shader_source =
		"#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"    //  location = 0,0 为位置变量的属性位置值
		"void main()\n"
		"{\n"
		"    gl_Position = vec4(aPos , 1.0);\n"
		"}\n\0";
	//  片段着色器源码:
	const char* fragment_shader_source =
		"#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";


	//Step5:生成并编译着色器
	//  顶点着色器:
	int vertex_shader = glCreateShader(GL_VERTEX_SHADER);//创建
	glShaderSource(vertex_shader, 1, &vertex_shader_source, nullptr);//赋值
	glCompileShader(vertex_shader);//编译
	//  检查是否成功编译:
	int success;
	char info_log[512];
	glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex_shader, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::VERTEX::COMPLATION_FAILED\n" << info_log << std::endl;
	}
	//  片段着色器:
	int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);//创建
	glShaderSource(fragment_shader, 1, &fragment_shader_source, nullptr);//赋值
	glCompileShader(fragment_shader);//编译
	//  检查是否成功编译:
	glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragment_shader, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPLATION_FAILED\n" << info_log << std::endl;
	}
	//  链接顶点、片段着色器至一个着色器程序:
	int shader_program = glCreateProgram();//创建
	glAttachShader(shader_program, vertex_shader);
	glAttachShader(shader_program, fragment_shader);
	glLinkProgram(shader_program);//链接
	//  检查是否链接成功:
	glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(shader_program, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info_log << std::endl;
	}
	//  删除着色器:
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);


	//    线框模式:(绘制线条)
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);


	//Step6:渲染循环(Render Loop)
	while (!glfwWindowShouldClose(window))
	{
		//  清空颜色缓冲:
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//  输入:
		processInput(window);
		//  使用着色器程序:
		glUseProgram(shader_program);
		//  绘制三角形:
		glBindVertexArray(vertex_array_object);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindVertexArray(0);
		//  交换缓冲并检查是否有触发事件(鼠标、键盘输入等)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}


	//Step7:收尾工作
	//  删除 VAO 和 VBO
	glDeleteVertexArrays(1, &vertex_array_object);
	glDeleteBuffers(1, &vertex_buffer_object);
	//  清理资源、退出程序
	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);
	}
}

3.绘制四边形

**使用 EBO **:

/***
 * 例程  绘制四边形
 * 步骤:
 * 1-初始化: GLFW 窗口,GLAD。
 * 2-顶点输入: 四边形的顶点数据、顶点索引
 * 3-数据处理: 生成并绑定 VAO & VBO (准备在 GPU 中进行处理),
					  生成并绑定 EBO
					  设置顶点属性指针(本质上就是告诉 OpenGL 如何处理数据)。
 * 4-着色器: 给出顶点和片段着色器,然后链接为着色器程序,渲染时使用着色器程序。
 * 5-渲染: 清空缓冲,使用着色器程序,绘制四边形,交换缓冲区检查触发事件后释放资源
 */

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

const int screen_width = 800;
const int screen_height = 600;

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

int main()
{
	//Step1:初始化 OpenGL
	//  初始化 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(screen_width, screen_height, "Draw a quad.", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create OpenGL context" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	//  初始化 GLAD,加载 OpenGL 函数指针的地址函数
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		glfwTerminate();
		return -1;
	}
	//  指定当前视口尺寸
	glViewport(0, 0, screen_width, screen_height);
	//  绑定视口改变时候的回调
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	std::cout << "OpenGL initialize done" << std::endl;

	//Step2:顶点输入
	//  四边形顶点数据
	const float vertices[] = {
		-0.2f,0.5f,0.0f,
		0.9f,0.5f,0.0f,
		0.0f,-0.3f,0.0f,
		-0.7f,-0.3f,0.0f
	};
	//  顶点索引数据(从 0 开始)
	unsigned int indices[] = {
		//第一个三角形
		0,1,2,
		//第二个三角形
		0,2,3
	};

	//Step3:数据处理
	//  生成并绑定 VBO
	GLuint vertex_buffer_object;
	glGenBuffers(1, &vertex_buffer_object);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	//  将顶点数据绑定至当前默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//  生成并绑定 VAO
	GLuint vertex_array_object;
	glGenVertexArrays(1, &vertex_array_object);
	glBindVertexArray(vertex_array_object);
	//  生成并绑定 EBO
	GLuint element_buffer_object;
	glGenBuffers(1, &element_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_object);
	//  将顶点索引数据绑定至当前默认的缓冲中
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//  设置顶点属性指针:(向 OpenGL 解释顶点数据)
	//    参数1:顶点着色器的位置值
	//    参数2:顶点属性是一个三分量的向量
	//    参数3:顶点属性类型
	//    参数4:顶点数据是否需要标准化
	//    参数5:步长--连续顶点属性之间的间隔
	//    参数6:数据的偏移量(此处位置属性在数组的开头,所以是0)
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//  开启 0 的这个通道
	glEnableVertexAttribArray(0);
	//  解绑 VAO(防止之后再继续绑定 VAO 的时候会影响当前的 VAO)
	glBindVertexArray(0);
	//  解绑 VBO
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	//Step4:顶点和片段着色器
	const char* vertex_shader_source =
		"#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"    //  location = 0,0 为位置变量的属性位置值
		"void main()\n"
		"{\n"
		"    gl_Position = vec4(aPos,1.0f);\n"
		"}\n\0";
	const char* fragment_shader_source =
		"#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";

	//Step5:生成并编译着色器
	//  顶点着色器
	int vertex_shader = glCreateShader(GL_VERTEX_SHADER);//  创建
	glShaderSource(vertex_shader, 1, &vertex_shader_source, nullptr);//  赋值
	glCompileShader(vertex_shader);//  编译
	//  检查是否成功编译
	int success;
	char info_log[512];
	glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex_shader, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::VERTEX::COMPLATION_FAILED\n" << std::endl;
	}
	//  片段着色器
	int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);//  创建
	glShaderSource(fragment_shader, 1, &fragment_shader_source, nullptr);//  赋值
	glCompileShader(fragment_shader);//  编译
	//  检查是否成功编译
	glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragment_shader, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPLATION_FAILED\n" << std::endl;
	}
	//  链接顶点、片段着色器到一个着色器程序中
	int shader_program = glCreateProgram();//  创建
	glAttachShader(shader_program, vertex_shader);
	glAttachShader(shader_program, fragment_shader);
	glLinkProgram(shader_program);//  链接
	//  检查是否链接成功
	glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(shader_program, 512, nullptr, info_log);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << std::endl;
	}
	//  删除着色器
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);

	//    线框模式
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	//Step6:渲染循环(Render Loop)
	while (glfwWindowShouldClose(window) == false)
	{
		//  清空颜色缓冲
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//  输入
		processInput(window);

		//渲染相关:
		//  使用着色器程序
		glUseProgram(shader_program);
		//  绘制四边形
		glBindVertexArray(vertex_array_object);//  使用 VAO
		//glDrawArrays(GL_TRIANGLES, 0, 6);//  不使用 EBO
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)indices[0]);//  使用 EBO
		glBindVertexArray(0);//  使用完之后,解绑 VAO

		//  交换缓冲,并检查是否有事件触发
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//Step7:收尾工作
	//  删除 VAO、VBO
	glDeleteVertexArrays(1, &vertex_array_object);
	glDeleteBuffers(1, &vertex_buffer_object);
	//  清理资源、退出程序
	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);
	}
}

封装 Shader 工具类

需要配置 glm (OpenGL 的数学库):从 官网网盘(提取码:bxnt) 中下载,将下载好的 glm 文件夹放入之前配置的 include 文件夹中,如下图所示:

picture14

#include "Shader.h"
#include "fstream"
#include "sstream"
#include "iostream"

Shader::Shader(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path)
{
	std::string vertex_shader_code;
	std::string fragment_shader_code;
	if (GetShaderFromFile(vertex_shader_path, fragment_shader_path, &vertex_shader_code, &fragment_shader_code))
	{
		return;
	}
	if (LinkShader(vertex_shader_code.c_str(), fragment_shader_code.c_str()))
	{
		return;
	}
}

Shader::~Shader()
{

}

void Shader::Use()
{
	glUseProgram(ID);
}

void Shader::SetBool(const std::string &name, bool value) const
{
	SetInt(name, (int)value);
}

void Shader::SetInt(const std::string &name, int value) const
{
	glUniform1i(GetUniform(name), value);
}

void Shader::SetFloat(const std::string &name, float value) const
{
	glUniform1f(GetUniform(name), value);
}

void Shader::SetVec2(const std::string &name, float x, float y) const
{
	glUniform2f(GetUniform(name), x, y);
}

void Shader::SetVec2(const std::string &name, const glm::vec2 &value) const
{
	SetVec2(name, value.x, value.y);
}

void Shader::SetVec3(const std::string &name, float x, float y, float z) const
{
	glUniform3f(GetUniform(name), x, y, z);
}

void Shader::SetVec3(const std::string &name, const glm::vec3 &value) const
{
	SetVec3(name, value.x, value.y, value.z);
}

void Shader::SetVec4(const std::string &name, float x, float y, float z, float w) const
{
	glUniform4f(GetUniform(name), x, y, z, w);
}

void Shader::SetVec4(const std::string &name, const glm::vec4 &value) const
{
	SetVec4(name, value.x, value.y, value.z, value.w);
}

void Shader::SetMat2(const std::string &name, const glm::mat2 &value) const
{
	glUniformMatrix2fv(GetUniform(name), 1, GL_FALSE, &value[0][0]);
}

void Shader::SetMat3(const std::string &name, const glm::mat3 &value) const
{
	glUniformMatrix3fv(GetUniform(name), 1, GL_FALSE, &value[0][0]);
}

void Shader::SetMat4(const std::string &name, const glm::mat4 &value) const
{
	glUniformMatrix4fv(GetUniform(name), 1, GL_FALSE, &value[0][0]);
}

int Shader::GetShaderFromFile(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path, std::string *vertex_shader_code, std::string *fragment_shader_code)
{
	std::ifstream vertex_shader_file;
	std::ifstream fragment_shader_file;
	vertex_shader_file.exceptions(std::ifstream::badbit | std::ifstream::failbit);
	fragment_shader_file.exceptions(std::ifstream::badbit | std::ifstream::failbit);
	try
	{
		vertex_shader_file.open(vertex_shader_path);
		fragment_shader_file.open(fragment_shader_path);
		std::stringstream vertex_shader_stream, fragment_shader_stream;
		vertex_shader_stream << vertex_shader_file.rdbuf();
		fragment_shader_stream << fragment_shader_file.rdbuf();
		vertex_shader_file.close();
		fragment_shader_file.close();
		*vertex_shader_code = vertex_shader_stream.str();
		*fragment_shader_code = fragment_shader_stream.str();
	}
	catch (std::ifstream::failure e)
	{
		std::cout << "Load Shader File Error!" << std::endl;
		return -1;
	}
	return 0;
}

int Shader::LinkShader(const char* vertex_shader_code, const char* fragment_shader_code)
{
	int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, &vertex_shader_code, NULL);
	glCompileShader(vertex_shader);
	CheckCompileErrors(vertex_shader, "VERTEX");

	int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, &fragment_shader_code, NULL);
	glCompileShader(fragment_shader);
	CheckCompileErrors(fragment_shader, "FRAGMENT");

	this->ID = glCreateProgram();
	glAttachShader(ID, vertex_shader);
	glAttachShader(ID, fragment_shader);
	glLinkProgram(ID);
	CheckCompileErrors(ID, "PROGRAM");

	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);
	return 0;
}

int Shader::GetUniform(const std::string &name) const
{
	int position = glGetUniformLocation(ID, name.c_str());
	if (position == -1)
	{
		std::cout << "uniform " << name << " set failed!" << std::endl;
	}
	return position;
}

void Shader::CheckCompileErrors(GLuint shader, std::string type)
{
	GLint success;
	GLchar infoLog[512];
	if (type == "PROGRAM")
	{
		glGetProgramiv(shader, GL_LINK_STATUS, &success);
		if (!success)
		{
			glGetProgramInfoLog(shader, 512, NULL, infoLog);
			std::cout << "ERROR::PROGRAM_LINKING_ERROR!\n" << infoLog << std::endl;
		}
	}
	else
	{
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(shader, 512, NULL, infoLog);
			std::cout << "ERROR::SHADER::" << type << "::COMPILATION_FAILED\n" << infoLog << std::endl;
		}
	}
}
#ifndef __SHADER_H__
#define __SHADER_H__

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "string"

class Shader
{
public:
	unsigned int ID;

	Shader(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path);
	~Shader();

	void Use();
	void SetBool(const std::string &name, bool value) const;
	void SetInt(const std::string &name, int value) const;
	void SetFloat(const std::string &name, float value) const;
	void SetVec2(const std::string &name, const glm::vec2 &value) const;
	void SetVec2(const std::string &name, float x, float y) const;
	void SetVec3(const std::string &name, const glm::vec3 &value) const;
	void SetVec3(const std::string &name, float x, float y, float z) const;
	void SetVec4(const std::string &name, const glm::vec4 &value) const;
	void SetVec4(const std::string &name, float x, float y, float z, float w) const;
	void SetMat2(const std::string &name, const glm::mat2 &value) const;
	void SetMat3(const std::string &name, const glm::mat3 &value) const;
	void SetMat4(const std::string &name, const glm::mat4 &value) const;

private:
	int GetShaderFromFile(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path,
		std::string *vertex_shader_code, std::string *fragment_shader_code);
	int LinkShader(const char* vertex_shader_code, const char* fragment_shader_code);
	int GetUniform(const std::string &name) const;
	void CheckCompileErrors(GLuint shader, std::string type);
};

#endif // !__SHADER_H__

4.绘制球体

绘制效果:(顶点+线框+背面剔除)

picture15

/***
* 例程  绘制球体
* 步骤:
* 1-初始化: GLFW窗口,GLAD。
* 2-计算球体顶点: 通过数学方法计算球体的每个顶点坐标
* 3-数据处理: 通过球体顶点坐标构造三角形网格,
			生成并绑定VAO&VBO&EBO(准备在GPU中进行处理),
			设置顶点属性指针(本质上就是告诉OpenGL如何处理数据)。
* 4-着色器: 给出顶点和片段着色器,然后链接为着色器程序,渲染时使用着色器程序。
* 5-渲染: 使用画线模式画圆,开启面剔除,剔除背面,使用线框模式画球
* 6-结束: 清空缓冲,交换缓冲区检查触发事件后释放资源
*/

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "Shader.h"
#include <iostream>
#include <math.h>
#include <vector>

const GLfloat PI = 3.14159265358979323846f;
//窗口宽高
const int SCREEN_WIDTH = 880;
const int SCREEN_HEIGHT = 880;
//将球体横纵划分为 20 x 20 的网格
const int Y_SEGMENTS = 20;
const int X_SEGMENTS = 20;
//球的半径
const float R = 0.5f;

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

int main()
{
	//    1.初始化 GLFW
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, false);//不可改变窗口大小

	auto window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Draw a Sphere", nullptr, nullptr);//创建窗口
	if (window == nullptr)//窗口创建失败
	{
		std::cout << "Failed to create OpenGL context!" << 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, SCREEN_WIDTH, SCREEN_HEIGHT);
	//    绑定视口改变时候的回调
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	std::cout << "OpenGL initialize done." << std::endl;

	//    2.计算球体顶点
	std::vector<float> sphereVertices;//顶点坐标数组
	std::vector<int>sphereIndices;//顶点索引数组
	for (int y = 0; y <= Y_SEGMENTS; y++)
	{
		for (int x = 0; x <= X_SEGMENTS; x++)
		{
			float xSegment = (float)x / (float)X_SEGMENTS;
			float ySegment = (float)y / (float)Y_SEGMENTS;
			float xPos = R * std::sin(ySegment*PI)*std::cos(xSegment*2.0f*PI);
			float yPos = R * std::cos(ySegment*PI);
			float zPos = R * std::sin(ySegment*PI)*std::sin(xSegment*2.0f*PI);
			sphereVertices.push_back(xPos);
			sphereVertices.push_back(yPos);
			sphereVertices.push_back(zPos);
		}
	}
	//    生成顶点索引
	for (int i = 0; i < Y_SEGMENTS; i++)
	{
		for (int j = 0; j < X_SEGMENTS; j++)
		{
			sphereIndices.push_back(i*(X_SEGMENTS + 1) + j);
			sphereIndices.push_back((i + 1)*(X_SEGMENTS + 1) + j);
			sphereIndices.push_back((i + 1)*(X_SEGMENTS + 1) + j + 1);
			sphereIndices.push_back(i*(X_SEGMENTS + 1) + j);
			sphereIndices.push_back((i + 1)*(X_SEGMENTS + 1) + j + 1);
			sphereIndices.push_back(i*(X_SEGMENTS + 1) + j + 1);
		}
	}

	//    3.生成并绑定VAO&VBO&EBO
	GLuint vertex_buffer_object, vertex_array_object;
	glGenBuffers(1, &vertex_buffer_object);//VBO
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	glGenVertexArrays(1, &vertex_array_object);//VAO
	glBindVertexArray(vertex_array_object);
	//    将顶点数据绑定至当前默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), &sphereVertices[0], GL_STATIC_DRAW);
	//    生成并绑定 EBO
	GLuint element_buffer_object;
	glGenBuffers(1, &element_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_object);
	//    将顶点索引数据绑定至当前默认的缓冲中
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphereIndices.size() * sizeof(int), &sphereIndices[0], GL_STATIC_DRAW);
	//    设置顶点属性指针:(向 OpenGL 解释顶点数据)
	//    参数1:顶点着色器的位置值
	//    参数2:顶点属性是一个三分量的向量
	//    参数3:顶点属性类型
	//    参数4:顶点数据是否需要标准化
	//    参数5:步长--连续顶点属性之间的间隔
	//    参数6:数据的偏移量(此处位置属性在数组的开头,所以是0)
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	//  开启 0 的这个通道
	glEnableVertexAttribArray(0);
	//    解绑
	glBindVertexArray(0);//解绑 VAO
	glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑 VBO

	//    4.着色器
	//    加载 shader
	Shader shader("res/shader/task4.vs", "res/shader/task4.fs");

	//    5.渲染
	while (!glfwWindowShouldClose(window))
	{
		// 清空颜色缓冲
		glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//输入
		processInput(window);
		//使用着色器程序
		shader.Use();

		//绘制球
		//开启面剔除(只需要展示一个面,否则会有重合)
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glBindVertexArray(vertex_array_object);
		//使用线框模式绘制
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glDrawElements(GL_TRIANGLES, X_SEGMENTS*Y_SEGMENTS * 6, GL_UNSIGNED_INT, 0);
		//点阵模式绘制
		glPointSize(5);
		glDrawElements(GL_POINTS, X_SEGMENTS*Y_SEGMENTS * 6, GL_UNSIGNED_INT, 0);
		//交换缓冲并且检查是否有触发事件(比如键盘输入、鼠标移动等)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// 删除VAO、VBO、EBO
	glDeleteVertexArrays(1, &vertex_array_object);
	glDeleteBuffers(1, &vertex_buffer_object);
	glDeleteBuffers(1, &element_buffer_object);

	// 清理所有的资源并正确退出程序
	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_Q) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
}

顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
	gl_Position =vec4(aPos, 1.0);
}

片元着色器:

#version 330 core
out vec4 FragColor;

void main()
{
	FragColor = vec4(1.0f,0.5f,0.2f,1.0f);
}

5.模型导入

步骤1.配置模型加载库 Assimp

配置步骤详见此篇博文:Assimp的安装编译及使用过程全纪录

步骤2.配置单头文件图像加载库 stb_image.h

github 上下载一个头文件,放入项目文件夹内(项目文件夹在工程文件夹下),然后在 VS 中手动添加该文件;新建一个 c++ 文件,输入以下代码:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

6.立方体旋转

//总体流程
//1. 初始化并创建窗口
//2. 加载立方体顶点VAOVBO以及着色器并开启深度测试
//3. 进入主循环清除缓冲
//4. 使用立方体着色器,构造并传入pvm矩阵,绘制
//5. 循环结束,释放VAOVBO

#include <iostream>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "Shader.h"

//    立方体数组(顶点三维坐标+顶点RGB颜色值)
const float vertices[] = {
	-0.5f, -0.5f, -0.5f, 1.0f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  1.0f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	-0.5f, -0.5f, -0.5f,  1.0f,0.0f,0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,

	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,0.0f,1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,1.0f,

	0.5f,  0.5f,  0.5f,  0.5f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f,  0.5f,  0.5f,0.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.5f,0.0f,0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,

	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f
};

float SCREEN_WIDTH = 800.0f;    //    窗口宽度
float SCREEN_HEIGHT = 800.0f;    //    窗口高度
//    相机参数
glm::vec3 camera_position = glm::vec3(0.0f, 0.0f, 3.0f);    //    相机位置
glm::vec3 camera_front = glm::vec3(0.0f, 0.0f, -1.0f);    //    相机方向
glm::vec3 camera_up = glm::vec3(0.0f, 1.0f, 0.0f);    //    相机上向量
float fov = 45.0f;    //    相机视野

int main() {
	//    初始化 GLFW
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, false);    //    不可改变窗口大小

	auto window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Cube rotate", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create OpenGL context!" << 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, SCREEN_WIDTH, SCREEN_HEIGHT);

	//    加载着色器
	Shader shader("res/shader/task6.vs", "res/shader/task6.fs");

	//    生成并绑定 VBO VAO
	GLuint vertex_buffer_object;
	glGenBuffers(1, &vertex_buffer_object);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);

	GLuint vertex_array_object;
	glGenVertexArrays(1, &vertex_array_object);
	glBindVertexArray(vertex_array_object);

	//    将顶点数据绑定至当前默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//    设置顶点属性指针:(向 OpenGL 解释顶点数据)
	//    参数1:顶点着色器的位置值
	//    参数2:顶点属性是一个三分量的向量
	//    参数3:顶点属性类型
	//    参数4:顶点数据是否需要标准化
	//    参数5:步长--连续顶点属性之间的间隔
	//    参数6:数据的偏移量(此处位置属性在数组的开头,所以是0)
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);    //    顶点坐标
	//    开启 0 的这个通道
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));    //    顶点颜色
	//    开启 1 的这个通道
	glEnableVertexAttribArray(1);

	glBindVertexArray(0);//解绑 VAO
	glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑 VBO

	//    开启深度测试
	glEnable(GL_DEPTH_TEST);

	//    渲染循环
	while (!glfwWindowShouldClose(window))
	{
		//    清理颜色缓冲、深度缓冲
		glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		//    使用着色器程序
		shader.Use();

		//    Transform 坐标变换矩阵
		glm::mat4 model(1);    //    model 矩阵:局部坐标=>世界坐标
		model = glm::translate(model, glm::vec3(0.0, 0.0, 0.0));    //    平移变换(矩阵,平移向量)
		model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.1f, 1.0f, 0.0f));    //    旋转变换(矩阵,旋转角度,旋转轴)
		float currentTime = (float)glfwGetTime();
		float scaleNum = 2.0f*(currentTime - floor(currentTime)) - 1.0f;
		scaleNum = pow(scaleNum, 2);
		model = glm::scale(model, scaleNum*glm::vec3(1.0f, 1.0f, 1.0f));    //    缩放变换(矩阵,缩放向量)

		glm::mat4 view(1);    //    view 矩阵:世界坐标=>观察坐标
		view = glm::lookAt(camera_position, camera_position + camera_front, camera_up);    //    lookAt 函数计算 view 矩阵(相机位置,物体中心点位置,相机上向量)

		glm::mat4 projection(1);    //    projection 矩阵:观察坐标=>观察坐标(裁剪空间)
		projection = glm::perspective(glm::radians(fov), (float)SCREEN_WIDTH / SCREEN_HEIGHT, 0.1f, 100.0f);    //    投影变换(视野,屏幕宽高比,近平面距离,远平面距离)

		//    向着色器中传入参数
		int model_location = glGetUniformLocation(shader.ID, "model");    //    获取着色器内某个参数的位置
		glUniformMatrix4fv(model_location, 1, GL_FALSE, glm::value_ptr(model));    //    写入参数值
		int view_location = glGetUniformLocation(shader.ID, "view");
		glUniformMatrix4fv(view_location, 1, GL_FALSE, glm::value_ptr(view));
		int projection_location = glGetUniformLocation(shader.ID, "projection");
		glUniformMatrix4fv(projection_location, 1, GL_FALSE, glm::value_ptr(projection));

		//    绘制
		glBindVertexArray(vertex_array_object);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		glBindVertexArray(0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	//    释放 VBO VAO
	glDeleteBuffers(1, &vertex_buffer_object);
	glDeleteVertexArrays(1, &vertex_array_object);

	//    清理资源,退出程序
	glfwTerminate();
	return 0;
}

顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 PosColor;
out vec3 positionColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0);
	positionColor=PosColor;
}

片元着色器:

#version 330 core
out vec4 FragColor;

in vec3 positionColor;

void main()
{
	FragColor = vec4(positionColor,1.0);
}

7.摄像机

封装的摄像机类:

#include "Camera.h"

Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch)
	: Forward(glm::vec3(0.0f, 0.0f, -1.0f))
	, MovementSpeed(SPEED)
	, Mouse_Sensiticity(SENSITIVITY)
	, Zoom(ZOOM)
{
	this->Position = position;
	this->World_up = up;
	this->Yaw = yaw;
	this->Pitch = pitch;
	UpdateCameraVectors();
}

Camera::Camera(float pos_x, float pos_y, float pos_z, float up_x, float up_y, float up_z, float yaw, float pitch)
	: Forward(glm::vec3(0.0f, 0.0f, -1.0f))
	, MovementSpeed(SPEED)
	, Mouse_Sensiticity(SENSITIVITY)
	, Zoom(ZOOM)
{
	this->Position = glm::vec3(pos_x, pos_y, pos_z);
	this->World_up = glm::vec3(up_x, up_y, up_z);
	this->Yaw = yaw;
	this->Pitch = pitch;
	UpdateCameraVectors();
}

Camera::~Camera()
{

}

glm::mat4 Camera::GetViewMatrix()
{
	return glm::lookAt(Position, Position + Forward, Up);
}

//对应键盘移动事件
void Camera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
	float velocity = MovementSpeed * deltaTime;
	if (direction == FORWARD)
		Position += Forward * velocity;
	if (direction == BACKWARD)
		Position -= Forward * velocity;
	if (direction == LEFT)
		Position -= Right * velocity;
	if (direction == RIGHT)
		Position += Right * velocity;
}
//对应鼠标移动事件
void Camera::ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch)
{
	xoffset *= Mouse_Sensiticity;
	yoffset *= Mouse_Sensiticity;

	Yaw += xoffset;
	Pitch += yoffset;

	if (constrainPitch)
	{
		if (Pitch > 89.0f)
			Pitch = 89.0f;
		if (Pitch < -89.0f)
			Pitch = -89.0f;
	}

	UpdateCameraVectors();
}
//对应鼠标滚轮事件
void Camera::ProcessMouseScroll(float yoffset)
{
	if (Zoom >= 1.0f && Zoom <= 45.0f)
		Zoom -= yoffset;
	if (Zoom <= 1.0f)
		Zoom = 1.0f;
	if (Zoom >= 45.0f)
		Zoom = 45.0f;
}


void Camera::UpdateCameraVectors()
{
	glm::vec3 front;
	front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
	front.y = sin(glm::radians(Pitch));
	front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
	Forward = glm::normalize(front);
	Right = glm::normalize(glm::cross(Forward, World_up));
	Up = glm::normalize(glm::cross(Right, Forward));
}
/***
 * 例程  摄像机类的添加
 * 步骤:
 * 加载摄像机类进行操作即可
 */

#include <iostream>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "Shader.h"
#include "Camera.h"

 //鼠标键盘响应函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);

const float vertices[] = {                  //立方体数组
	-0.5f, -0.5f, -0.5f, 1.0f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  1.0f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f,0.0f,0.0f,
	-0.5f, -0.5f, -0.5f,  1.0f,0.0f,0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f,1.0f,0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,1.0f,0.0f,

	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.0f,1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,0.0f,1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,1.0f,

	0.5f,  0.5f,  0.5f,  0.5f,0.0f,0.0f,
	0.5f,  0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f, -0.5f,  0.5f,0.0f,0.0f,
	0.5f, -0.5f,  0.5f,  0.5f,0.0f,0.0f,
	0.5f,  0.5f,  0.5f,  0.5f,0.0f,0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f,0.5f,0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,0.5f,0.0f,

	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	-0.5f,  0.5f,  0.5f,  0.0f,0.0f,0.5f,
	-0.5f,  0.5f, -0.5f,  0.0f,0.0f,0.5f
};

float screen_width = 1280.0f;          //窗口宽度
float screen_height = 720.0f;          //窗口高度
//摄像机相关参数

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = screen_width / 2.0f;
float lastY = screen_height / 2.0f;
bool firstMouse = true;

float deltaTime = 0.0f;
float lastFrame = 0.0f;

int main() {
	// 初始化GLFW
	glfwInit();                                                     // 初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);                  // OpenGL版本为3.3,主次版本号均设为3
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 使用核心模式(无需向后兼容性)
	glfwWindowHint(GLFW_RESIZABLE, false);						    // 不可改变窗口大小

	// 创建窗口(宽、高、窗口名称)
	auto window = glfwCreateWindow(screen_width, screen_height, "Camera", nullptr, nullptr);
	if (window == nullptr) {
		std::cout << "Failed to Create OpenGL Context" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwSetCursorPosCallback(window, mouse_callback);
	glfwSetScrollCallback(window, scroll_callback);
	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

	// 初始化GLAD,加载OpenGL函数指针地址的函数
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// 指定当前视口尺寸(前两个参数为左下角位置,后两个参数是渲染窗口宽、高)
	glViewport(0, 0, screen_width, screen_height);
	Shader shader("res/shader/task7.vs", "res/shader/task7.fs");//加载着色器
	GLuint vertex_array_object; // == VAO
	glGenVertexArrays(1, &vertex_array_object);
	glBindVertexArray(vertex_array_object);

	GLuint vertex_buffer_object; // == VBO
	glGenBuffers(1, &vertex_buffer_object);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	// 将顶点数据绑定至当前默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// 设置顶点属性指针
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);


	glEnable(GL_DEPTH_TEST);

	// 渲染循环
	while (!glfwWindowShouldClose(window)) {

		//计算每帧的时间差
		float currentFrame = glfwGetTime();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;
		processInput(window);

		glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清理颜色缓冲和深度缓冲

		shader.Use();

		// Transform坐标变换矩阵
		glm::mat4 model(1);//model矩阵,局部坐标变换至世界坐标
		model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
		glm::mat4 view(1);//view矩阵,世界坐标变换至观察坐标系
		view = camera.GetViewMatrix();
		glm::mat4 projection(1);//projection矩阵,投影矩阵
		projection = glm::perspective(glm::radians(camera.Zoom), screen_width / screen_height, 0.1f, 100.0f);

		// 向着色器中传入参数
		shader.SetMat4("model", model);
		shader.SetMat4("view", view);
		shader.SetMat4("projection", projection);

		//绘制
		glBindVertexArray(vertex_array_object);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		glBindVertexArray(0);


		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//释放VAOVBO
	glDeleteVertexArrays(1, &vertex_array_object);
	glDeleteBuffers(1, &vertex_buffer_object);

	// 清理所有的资源并正确退出程序
	glfwTerminate();
	return 0;
}

//键盘输入
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);

	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.ProcessKeyboard(FORWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.ProcessKeyboard(BACKWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.ProcessKeyboard(LEFT, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.ProcessKeyboard(RIGHT, deltaTime);
}

//窗口分辨率自适应
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}

//鼠标移动响应
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
	if (firstMouse)
	{
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float xoffset = xpos - lastX;
	float yoffset = lastY - ypos;

	lastX = xpos;
	lastY = ypos;

	camera.ProcessMouseMovement(xoffset, yoffset);
}
//鼠标滚轮响应
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
	camera.ProcessMouseScroll(yoffset);
}

[Reference]

LearnOpenGL

[OpenGL+VS2017 环境配置(亲测好使)<附带必要知识点>](https://blog.csdn.net/AvatarForTest/article/details/79199807)