OpenGL创建三角形的流程
- 基础概念
- 顶点数据
- VAO,VBO及EBO
- 顶点着色器及片元着色器
- 创建流程
- 创建窗口
- 创建窗口的核心函数:
- 设置窗口大小
- 分配/释放资源:
- 创建顶点/片元着色器
- 顶点着色器
- 片元着色器
- 创建着色器程序
- 销毁着色器程序,创建完着色器程序之后便可销毁了
- 将数据传给顶点着色器
- 完整代码
基础概念
顶点数据
我们在创建一个最基本的几何体时必须要告诉GPU要渲染的几何体的基本信息,包括顶点的坐标,颜色,法线,贴图等等,这些信息全部保存至数组当中,在使用的时候需要将这些数据传入顶点着色器。示例如下:
//顶点信息
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
两个三角形,有两个公共顶点,这时用到EBO
float vertices[] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
带有颜色信息
float vertices[] = {
// 位置 // 颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
};
VAO,VBO及EBO
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
使用说明
顶点数组对象即为顶点数据,顶点缓冲对象用来管理顶点数据的,需要VBO存在的原因如下:
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。简而言之就是为了通过它加快数据传输速度。
索引缓冲对象存在的原因是当顶点数据发生重合时会造成不必要的内存浪费,而当引入索引缓冲对象时就可以大量减少这类空间的浪费,节约内存。
例如,当我们要绘制两个三角形,但是这两个三角形有公共顶点时:
//顶点信息
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形,对应数组的第1,2,4行
1, 2, 3 // 第二个三角形,对应数组的第2,3,4行
};
使用indices数组就是索引缓冲对象,减少了两组顶点数据的空间,当有大量三角形时,这个节省的空间将非常巨大,占用更少的显存。
顶点着色器及片元着色器
要想理解着色器是什么,就必须理解渲染管线
- 顶点着色器
顶点着色器接收从CPU传入的数据,就是上面创建的顶点数组数据,经过顶点着色器处理计算之后,会有对应的输出数据作为片元着色器的输入数据,当然在这个传值过程中需要经过图元装配和插值光栅化计算,将顶点坐标转化成对应的像素坐标信息。
在OpenGL中的创建过程如下:
//VertexShader创建顶点着色器
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
- 片元着色器
像素信息作为片元着色器的传入数据做对应的计算后,经过几次测试(模板测试和深度测试等)进入frontBuffer,利用双缓冲技术从frontBuffer传入frameBuffer中,显示到屏幕上。
在OpenGL中的创建过程如下:
//片元着色器
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
图片显示全部过程:
创建流程
创建窗口
在创建窗口之前需要配置开发环境
代码及API不用死记硬背,只要理解其作业就行了
glfwInit();//初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//配置glfw,glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。
创建窗口的核心函数:
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
设置窗口大小
(OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。):
glViewport(0, 0, 800, 600);
分配/释放资源:
glfwTerminate();
创建顶点/片元着色器
顶点着色器
//VertexShader创建顶点着色器
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
片元着色器
//片元着色器
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
创建着色器程序
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
销毁着色器程序,创建完着色器程序之后便可销毁了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
将数据传给顶点着色器
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
完整代码
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
const char *vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(0.0f, 1f, 0f, 1.0f);\n"
"}\n";
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//VertexShader创建顶点着色器
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//Info获取编译出错信息
int success;
char info[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, info);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << info << std::endl;
}
//片元着色器
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//编译信息
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, info);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << info << std::endl;
}
//shaderProgramshader程序
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, info);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info << std::endl;
}
//没用了,直接删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//顶点信息
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);//1代表数组长度为1
glGenBuffers(1, &VBO);//1代表数组长度为1
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//索引,管理重复数据
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//赋值数据到顶点着色器
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//传入location值
//glBindBuffer(GL_ARRAY_BUFFER, 0); //取消绑定
//glBindVertexArray(0); //取消绑定
// int nrAttributes;
// glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
// std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
while (!glfwWindowShouldClose(window)) {
glClearColor(1, 0, 0, 1);//设置背景颜色
glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓存
//glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(shaderProgram);//使用着色器程序
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//当使用EBO索引时使用该函数,当不用索引时使用glDrawArrays
glfwSwapBuffers(window);
glfwPollEvents();
}
//没用了,销毁
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
运行截图: