目录
[TOC]
一、写在前面
1.学习原因
想要完全掌握 Unity 中的 Shader 的代码编写,学习 OpenGL 将是必不可少的一步,此前已经有过 UnityShader 的学习经历,相信 OpenGL 的学习将不会太难。
2.学习资源
(1)LearnOpenGL
教程出了很多内容了,作者还在更新,有完整中文译版。
难度适合入门
(2)OpenGL-Tutorial
还在翻译中,有部分还是英文。
难度适合入门
(3)OGLdev
中文还在翻译中,超过一半课程有中文。
难度适合有过基础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 package:glfw-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
-
Step2.下载 CMake
-
Step3.解压上述文件、启动 CMake(bin 目录下的 cmake-gui.exe)
-
Step4.为 CMake 指定 源码目录 和 存放编译结果的目标文件目录
源码目录 :GLFW 源码的根目录
存放编译结果的目标文件目录 :在 GLFW 源码根目录下新建 build 文件夹,选中作为目标目录
-
Step5.点击 Configure(设置)按钮,选择工程的生成器(VS2017)
-
Step6.设置完成之后,再次点击 Configure(设置)按钮来保存设置。之后点击 Generate(生成)按钮,生成的文件在之前新建的 build文件夹中。
-
Step7. **在 build 文件夹里可以找到 **GLFW.sln 文件,用 Visual Studio 2017 打开。 因为 CMake 已经配置好了项目,所以我们直接 生成解决方案,然后编译的库 glfw3.lib 就会出现在 src/Debug 文件夹内。
-
Step8.建立一个新的目录包含所有的 第三方库文件 和 头文件 ,并且在你的 IDE 或编译器中指定这些文件夹。
使用一个单独的文件夹,里面包含 libs 和 include 文件夹,在这里存放 OpenGL 工程用到的所有第三方库和头文件。
-
Step9.将刚刚编译好的 glfw3.lib 文件放在新建的 libs 文件夹下,将之前下载的 glfw 文件中 include 文件夹下的 GLFW 文件复制粘贴到新建的 include 文件夹中。
-
Step10.新建 VS C++ 空项目
-
Step11.将 GLFW 库链接进工程
设置 包含目录 和 库目录:
要链接一个库我们必须告诉链接器它的文件名。库名字是 glfw3.lib,把它加到附加依赖项字段中,这样 GLFW 在编译的时候就会被链接进来了。
二、配置 GLAD 库
-
Step1.下载 GLAD
如下进行在线配置:
配置之后,点击下图压缩包进行下载:
-
Step2.解压文件,会得到 include 和 src 两个文件夹。
将解压后的 include 中的文件(glad 和 KHR)移动到之前新增的 include 文件中:
将解压得到的 src 文件夹移动到和之前新增的 include 同级的目录中;将 src 文件夹中的 glad.c 添加到工程项目源文件中:
二、
基础实验配置
包含目录:
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 文件夹中,如下图所示:
#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.绘制球体
绘制效果:(顶点+线框+背面剔除)
/***
* 例程 绘制球体
* 步骤:
* 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]
[OpenGL+VS2017 环境配置(亲测好使)<附带必要知识点>](https://blog.csdn.net/AvatarForTest/article/details/79199807)附带必要知识点>