先了解一下一些基础概念
图形渲染管线(graphics pipeline)
- 指一堆图形数据输入到一个管道中,经过管道中一些列的处理后将结果展现到屏幕上的过程
简单来说可以认为有以下过程,每个阶段的输出都是下一个阶段的输入
- 顶点数据输入————就是输入一些顶点的位置数据,如三角形的顶点之类的
- 顶点着色器————跑在GPU上,以顶点数据为输入,可以对顶点数据进行变换(位移、旋转、比例转换等)
- 图元装配————将顶点着色器的输出组成图元(线段、三角形、矩形等)
- 几何着色器————以图元顶点集合为输入,可以通过生成新的顶点产生新的图元(例如再生成一个三角形之类的)
- 光栅化————把图元映射为屏幕上的像素,生成片段,执行裁切掉弃视图外的像素
- 片段着色器————计算一个像素的颜色
- 测试与混合————进行Alpha测试与混合(半透明的颜色需要和后面的颜色混合,非透明的颜色如果是直接遮挡后面的颜色,后面的颜色掉弃)
OpenGL的基本概念
- 顶点缓冲对象(VBO, vertex buffer object)
用来管理顶点的数据,顶点缓冲对象的数据位于显存中//triangle vertices float vertices[] = { -0.5, -0.5, 0, 0.5, -0.5, 0, 0, 0.5, 0 }; //create buffer GLuint vbo; glGenBuffers(1, &vbo); //bind VBO glBindBuffer(GL_ARRAY_BUFFER, vbo); //copy data from cpu mem to gpu mem glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //set vertex attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), 0); glEnableVertexAttribArray(0); //unbind VBO glBindBuffer(GL_ARRAY_BUFFER, 0);
- 顶点数组对象(VAO,vertex array object)
用来管理一堆VBO的属性的对象GLuint VAO; glGenVertexArrays(1, &VAO); //bind VAO glBindVertexArray(VAO); //...省略VBO的属性设置 如上面代码所示 //unbind VAO glBindVertexArray(0);
- 元素缓冲对象(EBO, element buffer object;或者叫IBO, index buffer object索引缓冲对象)
简单来说就是通过索引定位顶点,例如绘制两个三角形,他们之间有两个顶点位置是一样的,这样就只需要储存四个顶点,通过索引位置复用两个顶点的数据。 - 标准化设备坐标(x、y、z坐标轴的范围都是[-1.0,1.0]之间)
在OpenGL下经过顶点着色器处理后的坐标都应该为标准化设备坐标 - OpenGL最少需要编写两个着色器,包括顶点着色器和片段着色器
创建着色器程序的步骤(以顶点着色器为例)- 定义着色器的代码
const char *vShaderSrc = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main() {\n" " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}";
- 创建着色器对象并绑定着色器代码
GLuint vShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vShader, 1, &vShaderSrc, NULL);
- 编译着色器代码并检查编译错误
glCompileShader(vShader); //error check GLint compileStatus; glGetShaderiv(vShader, GL_COMPILE_STATUS, &compileStatus); if (!compileStatus) { char info[512]; glGetShaderInfoLog(vShader, sizeof(info), NULL, info); std::cout << "compile vShader error: " << info << '\n'; }
- 定义片段着色器(省略步骤和顶点着色器的一致)
- 创建着色器程序并将顶点着色器及片段着色器关联上去
GLuint program = glCreateProgram(); glAttachShader(program, vShader); glAttachShader(program, fShader);
- 链接着色器程序并检查错误
GLint linkStatus; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (!linkStatus) { char info[512]; glGetProgramInfoLog(program, sizeof(info), NULL, info); std::cout << "link error: " << info << '\n'; }
- 清理顶点着色器及片段着色器
glDeleteShader(vShader); glDeleteShader(fShader);
绘制两个三角形的程序结构
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebufferSizeChanged(GLFWwindow *glfwWindow, int width, int height);
void processInput(GLFWwindow *glfwWindow);
GLuint createShaderProgram() {
//vShader
const char *vShaderSrc = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main() {\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}";
//compile vShader
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vShader, 1, &vShaderSrc, NULL);
glCompileShader(vShader);
//error check
GLint compileStatus;
glGetShaderiv(vShader, GL_COMPILE_STATUS, &compileStatus);
if (!compileStatus) {
char info[512];
glGetShaderInfoLog(vShader, sizeof(info), NULL, info);
std::cout << "compile vShader error: " << info << '\n';
}
const char *fShaderSrc = "#version 330 core\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vec4(1.0, 1.0, 0.0, 1.0);\n"
"}";
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fShader, 1, &fShaderSrc, NULL);
glCompileShader(fShader);
glGetShaderiv(fShader, GL_COMPILE_STATUS, &compileStatus);
if (!compileStatus) {
char info[512];
glGetShaderInfoLog(vShader, sizeof(info), NULL, info);
std::cout << "compile fShader error: " << info << '\n';
}
GLuint program = glCreateProgram();
glAttachShader(program, vShader);
glAttachShader(program, fShader);
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (!linkStatus) {
char info[512];
glGetProgramInfoLog(program, sizeof(info), NULL, info);
std::cout << "link error: " << info << '\n';
}
glDeleteShader(vShader);
glDeleteShader(fShader);
return program;
}
int main() {
//初始化glfw
if (!glfwInit()) {
return -1;
}
//创建窗口
GLFWwindow *glfwWindow = glfwCreateWindow(640, 480, "glfw_window", NULL, NULL);
if (!glfwWindow) {
glfwTerminate();
return -1;
}
//将当前的context关联到glfwWindow
glfwMakeContextCurrent(glfwWindow);
glfwSetFramebufferSizeCallback(glfwWindow, framebufferSizeChanged);
//glad需要要在glfwMakeContextCurrent后才能初始化
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
glfwTerminate();
return -1;
}
//triangle vertices
float vertices[] = {
-0.5, -0.5, 0,
0.5, -0.5, 0,
-0.5, 0.5, 0,
0.5, 0.5, 0
};
unsigned int indices[] = {
0, 1, 2,
1, 2, 3
};
GLuint VAO;
glGenVertexArrays(1, &VAO);
//create buffer
GLuint VBO, EBO;
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//bind VAO
glBindVertexArray(VAO);
//bind VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//copy data from cpu mem to gpu mem
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//bind EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//set vertex attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), 0);
glEnableVertexAttribArray(0);
//unbind VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
//do not unbind EBO, EBO store in VAO
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//unbind VAO
glBindVertexArray(0);
GLuint program = createShaderProgram();
//消息循环
while(!glfwWindowShouldClose(glfwWindow)) {
processInput(glfwWindow);
glClearColor(0.0, 0.0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(glfwWindow);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(program);
glfwTerminate();
return 0;
}
void framebufferSizeChanged(GLFWwindow *glfwWindow, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow *glfwWindow) {
if (glfwGetKey(glfwWindow, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(glfwWindow, true);
}
}