目录
序言——OpenGL在是什么?为什么?做什么?
OpenGL实现了什么
OpenGL内模型数据的本质——顶点数据
我们需要研究什么——三角形,一个图形基元
MVP变换
OpenGL渲染流程的关键——摄像机变换
OpenGL渲染管线概览
准备——项目配置
项目初始代码框架及注释
初识——三角形绘制
OpenGL中的顶点数据格式——float数组
OpenGL中shader如何从CPU中获取数据——layout(锚点)
Shader
VBO:Vertex Buffer Object
VAO:解决锚点问题,记录了VBO的锚点信息
编译shader
设定VAO并进行渲染
整体源码
序言——OpenGL在是什么?为什么?做什么?
OpenGL实现了什么
将三维物体映射到视线方向上的一个裁剪空间(屏幕)上
OpenGL内模型数据的本质——顶点数据
我们需要研究什么——三角形,一个图形基元
MVP变换
OpenGL渲染流程的关键——摄像机变换
OpenGL渲染管线概览
准备——项目配置
GLFW
Download | GLFW
GLAD
https://glad.dav1d.de
下载后,进行相应配置。
项目初始代码框架及注释
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
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);
}
}
int main() {
//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态
glfwInit();
//,3版本以上
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//用OpenGL核心开发模式
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
//创建窗体
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Failed to create GLFW window" << 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, 800, 600);
//当Frame大小变动,调用回调函数调整视口大小
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//防止窗口结束退出
while (!glfwWindowShouldClose(window)) {
processInput(window);
//擦除画布,用定义的颜色填充
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//双缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
//结束,释放资源
glfwTerminate();
return 0;
}
运行结果如下:
初识——三角形绘制
OpenGL中的顶点数据格式——float数组
看向-Z方向
OpenGL中shader如何从CPU中获取数据——layout(锚点)
- CPU将float顶点数据数组传入GPU
- CPU告诉GPU如何解析这个数组
- 调用渲染指令进行绘制
GPU显存中的布局:layout;可以理解为“锚点”,指明在这一锚点代表的区域,存放了什么样的数据。
Shader
直白来说,Shader就是跑在GPU上的一种语言,用来操作GPU。
我们先写好两个shader的内容,先大致了解一番:
vertexShader:
#version 330 core
//在layout=0,这块区域放置了一个vec3
layout (location = 0 ) in vec3 aPos;
//操作
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
- vertexShader中的数据gl_Position,会自动流入下一个阶段中,也就是fragmentShader
- vertexShader会被调用多少次?有多少顶点就会调用多少次
fragmentShader:
#version 330 core
out vec4 FragColor;
void main(){
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
- fragmentShader的目的是为了输出一个数据,这里是vec4 FragColor,被定义为out类型,会被输出到下一个管线流程中。
- fragmentShader会被调用多少次?简单来说有多少像素就会调用多少次
流程:
- 将顶点数据转入到vertexShader,进行空间变换等操作(注意是并行的)
- 数据从vertexShader传入到fragmentShader,进行像素插值等操作(处理一堆像素点)
VBO:Vertex Buffer Object
在上面那个图中,其中的“GPU shader”就是所谓的VBO,也就是我们开辟的一块区域。
在开辟的这块空间,存储顶点数据。
那么在OpenGL中如何做这件事?
- 获取VBO的index(由OpenGL状态机分配的index)
- 绑定VBO的index
- 给VBO分配显存空间,并传输数据
- 告诉shader数据的解析方式
- 激活锚点,按照解析方式取读取数据
具体代码如下,我们在mian.cpp中添加如下函数:
//构建模型数据:VBO,
void initModel() {
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
glGenBuffers(1, &VBO);
//绑定哪一种buffer,
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//打开锚点:激活
glEnableVertexAttribArray(0);
//解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
每个函数的作用和参数意义,这里我用注释详细标明。方便后时查阅复习。
VAO:解决锚点问题,记录了VBO的锚点信息
编译shader
VAO是与shader密切相关的一个内容,所以在此之前需要进行shader的一系列操作:
首先声明一个全局变量:
unsigned int shaderProgram = 0;
初始化Shader,并进行编译链接。
void initShader(const char* _vertexPath, const char* _fragPath) {
//shader的代码读取
std::string _vertexCode("");
std::string _fragCode("");
std::ifstream _vShaderFile;
std::ifstream _fShaderFile;
_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
_vShaderFile.open(_vertexPath);
_fShaderFile.open(_fragPath);
std::stringstream _vShaderStream, _fShaderStream;
_vShaderStream << _vShaderFile.rdbuf();
_fShaderStream << _fShaderFile.rdbuf();
_vertexCode = _vShaderStream.str();
_fragCode = _fShaderStream.str();
}
catch(std::ifstream::failure e) {
std::string errStr = "read shader fail";
std::cout << errStr << ": " << e.what() << std::endl;
}
const char* _vShaderStr = _vertexCode.c_str();
const char* _fShaderStr = _fragCode.c_str();
//shader的编译链接
unsigned int _vertexID = 0, _fragID = 0;
char _infoLog[512];
int _successFlag = 0;
//编译
_vertexID = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(_vertexID, 1, &_vShaderStr, nullptr);
glCompileShader(_vertexID);
//捕捉编译过程中的状态信息
glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(_vertexID, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
_fragID = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(_fragID, 1, &_vShaderStr, nullptr);
glCompileShader(_fragID);
//捕捉编译过程中的状态信息
glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(_fragID, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
//链接
//创建一个程序
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, _vertexID);
glAttachShader(shaderProgram, _fragID);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(shaderProgram, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
//删除中间文件
glDeleteShader(_vertexID);
glDeleteShader(_fragID);
}
设定VAO并进行渲染
//构建模型数据:VBO,VAO
void initModel() {
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//之后的VBO便属于了VAO的管理范围
glGenBuffers(1, &VBO);
//绑定哪一种buffer,
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//打开锚点:激活
glEnableVertexAttribArray(0);
//解绑
//glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
//渲染
void render() {
glBindVertexArray(VAO);
glUseProgram(shaderProgram);
//以三角形模式绘制,从第0个顶点开始,起作用的有3个点
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
}
渲染结果:
整体源码
main.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void initModel();
void initShader(const char* _vertexPath, const char* _fragPath);
void render();
unsigned int VBO = 0;
unsigned int VAO = 0;
unsigned int shaderProgram = 0;
int main() {
//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态
glfwInit();
//,3版本以上
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//用OpenGL核心开发模式
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
//创建窗体
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Failed to create GLFW window" << 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, 800, 600);
//当Frame大小变动,调用回调函数调整视口大小
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
initModel();
initShader("vertexShader.glsl", "fragmentShader.glsl");
//防止窗口结束退出
while (!glfwWindowShouldClose(window)) {
processInput(window);
//擦除画布,用定义的颜色填充
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
render();
//双缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
//结束,释放资源
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);
}
}
//渲染
void render() {
glBindVertexArray(VAO);
glUseProgram(shaderProgram);
//以三角形模式绘制,从第0个顶点开始,起作用的有3个点
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
}
//构建模型数据:VBO,VAO
void initModel() {
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//之后的VBO便属于了VAO的管理范围
glGenBuffers(1, &VBO);
//绑定哪一种buffer,
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//打开锚点:激活
glEnableVertexAttribArray(0);
//解绑
//glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
//
void initShader(const char* _vertexPath, const char* _fragPath) {
//shader的代码读取
std::string _vertexCode("");
std::string _fragCode("");
std::ifstream _vShaderFile;
std::ifstream _fShaderFile;
_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
_vShaderFile.open(_vertexPath);
_fShaderFile.open(_fragPath);
std::stringstream _vShaderStream, _fShaderStream;
_vShaderStream << _vShaderFile.rdbuf();
_fShaderStream << _fShaderFile.rdbuf();
_vShaderFile.close();
_fShaderFile.close();
_vertexCode = _vShaderStream.str();
_fragCode = _fShaderStream.str();
}
catch(std::ifstream::failure e) {
std::string errStr = "read shader fail";
std::cout << errStr << ": " << e.what() << std::endl;
}
const char* _vShaderStr = _vertexCode.c_str();
const char* _fShaderStr = _fragCode.c_str();
//shader的编译链接
unsigned int _vertexID = 0, _fragID = 0;
char _infoLog[512];
int _successFlag = 0;
//编译
_vertexID = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(_vertexID, 1, &_vShaderStr, nullptr);
glCompileShader(_vertexID);
//捕捉编译过程中的状态信息
glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(_vertexID, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
_fragID = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(_fragID, 1, &_fShaderStr, nullptr);
glCompileShader(_fragID);
//捕捉编译过程中的状态信息
glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(_fragID, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
//链接
//创建一个程序
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, _vertexID);
glAttachShader(shaderProgram, _fragID);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);
if (!_successFlag) {
glGetShaderInfoLog(shaderProgram, 512, nullptr, _infoLog);
std::string errStr(_infoLog);
std::cout << errStr << std::endl;
}
//删除中间文件
glDeleteShader(_vertexID);
glDeleteShader(_fragID);
}
vertexShader.glsl
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
};
fragmentShader.glsl
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
};