在LearnOpenGL网站学习,记录一下学习笔记
文章目录
GLFW与GLAD
GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入。
GLAD:OpenGL的驱动版本有很多,它的大多数函数的位置都无法在编译时确认下来,需要找到指向函数位置的指针,取得地址的方法因为平台不同而不同。但是GLAD可以简化这个过程。
创建窗口
创建窗口主要有以下几个步骤:
1 初始化GLFW
2 创建窗口并检测是否窗口创建是否成功
3 初始化GLAD,管理GLFW函数的指针.
4 创建视口
5 渲染引擎
初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
创建窗口并检测是否窗口创建是否成功
glfwCreateWindow(800,600,"Hello World",NULL,NULL):创建窗口的命令,参数以此是窗口宽高、窗口名字、
glfwMakeContextCurrent(window):通知GLFW将我们窗口的上下文设置为当前线程的主上下文了
GLFWwindow* window = glfwCreateWindow(800,600,"Hello Triangle",NULL,NULL);
if (window == NULL) {
cout << "Failed to Create Window" << endl;
return -1;
}
glfwMakeContextCurrent(window);
初始化GLAD,管理GLFW函数的指针.
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "Failed to Init GLAD" << endl;
return -1;
}
创建视口
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0,0,width,height);
}
glViewport(0,0,800,600);
//每当窗口大小发生变化时,调用framebuffer_size_callback函数
glfwSetFramebufferSizeCallback(window,framebuffer_size_callback);
渲染引擎
glClearColor(0.2f,0.3f,0.3f,1.0f):用括号内的RGBA值来清空屏幕
glClear(GL_COLOR_BUFFER_BIT):清空屏幕的颜色缓冲,
它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲。
缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT
和GL_STENCIL_BUFFER_BIT,分别为:颜色缓冲、深度缓冲、模板缓冲
glfwPollEvents():检查并调用事件
glfwSwapBuffers(window):交换缓冲,原理:双缓冲渲染
while (!glfwWindowShouldClose(window)) {
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
绘制三角形
在了解绘制三角形之前,需要了解渲染管线。
绘制三角形的过程,我大致将其分为以下几个步骤:
1 顶点数据的输入
2 顶点数据、VBO、VAO、EBO之间的作用原理
3 顶点着色器
4 片段着色器
5 着色程序
顶点数据的输入
在OpenGL中,顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据。其中常见的数据有位置、纹理、法线。
下面是一个三角形平面的输出的顶点数据:v:位置、vt:纹理坐标、vn:法线、f:位置/纹理坐标/法线
顶点数据、VBO、VAO、EBO之间的作用原理
根据CPU和GPU之间的数据传输来理解。
在CPU中,对应着顶点数据,有各种值
在GPU中,首先将顶点数据复制一份得到VBO(顶点缓冲对象)
之后,对VBO中的数据进行分类索引得到VAO(顶点数组对象),一般情况下一个模型一个VAO。一个VAO也可以同时对应另外一个缓冲EBO(索引缓冲对象)
着色器
在OpenGL中,我们能够编写代码控制的着色器有vertexShader和FragmentShader。
VertexShader的主要作用是处理顶点相关的信息,比如:position、normal、texture等等,在顶点着色器中处理的主要变换是顶点的位置信息,比如说MVP 变换,也可以进行光照信息的处理,比如:Gouraud着色。
FragmentShader主要是颜色、光照、纹理、材质方面的处理
简单对比: 顶点着色器的处理效率比较高,因为只处理顶点,但是效果较差。比说说Gourand着色效果比在片段着色器的处理效果差。片元着色器的处理效率可能相对较低,因为,片元着色器会对顶点之间的颜色进行插值处理,但是处理的效果较好。对比Gouraud着色与Phong模型。
Uniform
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
自定义着色器类
了解上述一定概念之后,编写着色器类,用来实现读取vertexShader、FragmentShader着色器、Linked Shader、Uniform的使用
功能设计
主要功能有:
1.构造函数读取shader文件、构造着色器、连接着色器
2.use()函数:使用着色器
3.setInt()函数:设置Uniform值
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h>; // 包含glad来获取所有的必须OpenGL头文件
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
// 程序ID
unsigned int ID;
// 构造器读取并构建着色器
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// 使用/激活程序
void use();
// uniform工具函数
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;
};
#endif
C++读取文件
简单记录一下C++读取文件步骤:
1.std::ifstream 打开文件,读取文件的缓冲内容,打开文件之前要先进行异常抛出检测
2.std::stringstream:将文件的缓冲内容读取到数据流中
3.std::ifstream:关闭文件处理器
4.std::string:转换数据流到string
5.char* :转换为char类型
Shader(const char* vertexPath, const char* fragmentPath)
{
// 1. 从文件路径中获取顶点/片段着色器
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// 保证ifstream对象可以抛出异常:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// 读取文件的缓冲内容到数据流中
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 转换数据流到string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch(std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
[...]
编译、链接着色器
主要流程:
1.glCraeteShader():创建着色器
2.glShaderSource():写入shader文本
3.glCompileShader():编译着色器
… …
n-2:glCreateProgram():创建着色器程序
n-1:glAttachShader():绑定顶点、片段着色器
n:glLinkProgram(): 链接着色器
// 2. 编译着色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 打印编译错误(如果有的话)
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
};
// 片段着色器也类似
[...]
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
// 打印连接错误(如果有的话)
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
use( )函数
glUseProgram():使用着色器程序
void use()
{
glUseProgram(ID);
}
Uniform 传值
glGetUniformLocation( ):获取着色器程序中Uniform的位置
glUniform1i( ):传值
void setBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
着色器类的使用
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...) //渲染器
{
ourShader.use();
ourShader.setFloat("someUniform", 1.0f);
DrawStuff();
}