一、渲染管线
在OpenGL里所有事物都是在3D坐标里的,屏幕视口是一个2D的坐标轴,OpenGL的大部分工作就是把3d坐标装换成可以适应屏幕的2D像素。
opengl的工作主要分成两大部分:
第一大部分,把3d坐标装换成2D坐标
第二大部分,则是把2d坐标转换成2D像素并且输出到屏幕上。
工作流程就是一个图形渲染管线,渲染管线主要分成几个阶段,各个阶段去完成不同的事情。
第一阶段(应用阶段):cpu加载对应的顶点和这些顶点的属性,放到指定的缓冲区里面,然后向gpu发送drawcall命令(命令模式),gpu就会把这些顶点和顶点的属性放到自己的缓存里进行管理。
第二阶段(顶点着色器):这一阶段主要是做一些顶点坐标转换或者是读取一些特定的模型(顶点)属性做一些加工并且输出到下一个阶段里面去,最大的关键是这个阶段是可以编辑的。
第三阶段(曲面着色器,图元装配):这一阶段主要是通过上一阶段输出的顶点来形成不同的图元。在opengl里面,可以通过GL_TRIANGLES、GL_POINTS、GL_LINE_STRIPS等参数告诉曲面着色器形成的图元是什么。该阶段不可编辑。只能通过指令来做修改。
第四阶段(几何着色器):用上一阶段的输出作为输入,会使用新的顶点形成新的图元。(非必要性阶段)
第五阶段(裁剪):主要是对图元进行背面剔除、裁剪,这可以删除大量的图元,让下一阶段的运算开销降低了不少。
第六阶段(屏幕映射):主要是做一些视口变换,把世界坐标里面的坐标装换到视口坐标里。
第七阶段(光删化):根据上一阶段在视口了生成的图元,对这些图元进行像素填充。而这些像素,称为片元,并且输出到下一阶段去。
第八阶段(片元着色器):像素点的颜色或其他属性值的最终计算,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
第九阶段(测试与混合):主要使用片元上的深度值和模板值来判断这个物体是否被遮挡,如果被遮挡则这个片元会被剔除掉。 如果被遮挡且遮挡的物体是透明的,就进行颜色混合。
总结:顶点数据输入->顶点着色器->曲面着色器,图元装配->几何着色器->裁剪->屏幕映射->光栅化->片元着色器->测试与混合。
大部分都是对顶点着色器和片元着色器来进行编辑的
硬编码写入一段顶点着色器和片元着色器的GLSL代码
const char* vertexShaderCode ="#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"
"}\n\0";
const char* fragmentShaderCode ="#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(0.1,0.2,0.5,1.0);\n"
"}\n\0";
这段代码是对输入的顶点数据没有进行任何的加工而输出到下一个阶段的。而上面的location = 0相当于是后面声明VAO属性指针的时候指定的位置。OpengGl里需要声明两个着色器并且进行编译后需要进行链接的。
unsigned int vertextShader; //顶点着色器id
vertextShader = glCreateShader(GL_VERTEX_SHADER); //创建并且返回顶点着色器的id
glShaderSource(vertextShader,1,&vertexShaderCode,NULL); //根据shader的id来绑定这个上面声明的硬代码
glCompileShader(vertexShader); //编译这个着色器
//以下是如果着色器如果着色器编译失败,就打印出失败信息。
int success;
char infoLog[512];
glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(vertexShader,512,NULL,infoLog);
cout << "ERROR COMPILE VERTEX SHADER FAILED#####"<< infoLog;
}
//片元着色器的声明写法也是一样的
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRANGMENT_SHADER);
glShaderSource(frangmentShader,1,&franmentShaderCode,NULL);
glGetShaderiv(frangmentShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(frangmentShader,520,NULL,infoLog);
cout << "ERROR COMPILE FRAGMENTSHADER FAILED#####" << infoLog;
}
//下面是对这两个着色器进行链接
unsigned int shaderProgram; //链接id
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(programShader);
glGetProgramiv(shaderProgram,GL_LINK_STATUS,&success);
if(!success){
glGeProgramInfoLog(shaderProgram,512,NULL,infoLog);
cout<<"ERROR SHADER LINK FAILED####" << infoLog;
}
//删除
gldeteleShader(vertexShader);
gldeteleShader(fragmentShader);
关于OpenGl的顶点输入
学习之前记住的英文词汇:
vertext-顶点
array-数组(我觉得理解成属性会通透一点 attrib-属性)
object-对象;
合起来: vertext array object,顶点数组对象(VAO)
vertext-顶点
buffer-缓存
object-对象;
合起来 vertext buffer object,顶点缓存对象(VBO)
index or element-索引
buffer-缓存
object-对象;
合理来 element buffer object 或者 index buffer object (EBO或者IBO) 索引缓存对象
在OpenGl里,是通过VBO来管理要输入的顶点数据的,但是VBO确实一个数组,全部是数据,GPU完全看不懂VBO里的数据,这个时候就需要VAO告诉GPU在VBO里面的数据如何读取。比如索引从哪里开始,一组顶点数据多少个,一组数据占用字节多少,每次读取从读取的数组偏移量多少。复杂的OpenGl程序里面会有很多个VBO,VAO。而使用多个图元来绘制图形时就要需要使用到EBO,EBO的职责是通过VAO来告诉GPU,顶点的绘制顺序。
这里绘制一个四边形
//搞一个四边形的顶点数据(坐标范围都是在-1到1之间)
//四个顶点的坐标
float vecties[] = {
-0.5f,0.5f,0,
-0.5f,-0.5f,0,
0.5f,-0.5f,0,
0.5f,0.5f,0
};
//不同的索引顺序形成的图元,索引是上面声明数组的行数0开始。一行一个图元
unsigned int[] indexs = {
0,1,3,
1,2,3
};
//分别声明VAO,VBO,EBO
unsigned int VAO,VBO,EBO;
glGenBuffers(1,&VBO);
glBindBuffers(GL_BUFFER_ARRAY,&VBO); //绑定当前的VBO
//GL_STATIC_DRAW是告诉opengl进行静态绘画
glBufferData(GL_BUFFER_ARRAY,sizeof(vecties),vecties,GL_STATIC_DRAW);
glGenBuffers(1,&EBO);
glBindBuffers(GL_ELEMENT_ARRAY,&EBO);
glBundBuffers(GL_ELEMENT_ARRAY,sizeof(indexs),index,GL_STATIC_DRAW);
glGenVertexArray(&VAO);
glBindVertexArray(VAO);
//这里的第一个参数就相当于是顶点着色器glsl代码里的location = 0
glVertexPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0); //启动这个顶点属性
//最后在窗口里进行绘画
while(!glfwWindowShouldClose(window)){
//输入函数 code....
//渲染指令 code....
glUseProgram(shaderProgram); //激活shader链接程序
glBindVertexArray(VAO); //进行VAO再绑定
glDrawElemets(GL_TRIANGLES,6,UNSIGNED_INT,&indexs);
//渲染前后缓冲交互code.....
//释放触发的事件code....
}
完整的demo代码(绘制一个四边形):
#include "hellotriangle.h"
#define WINDOW_WIDTH 800;
#define WINDOW_HEIGHT 600;
#define WINDOW_NAME "LearnOpengl";
using namespace std;
void framebuffer_size_callback(GLFWwindow*, int, int);
void processInput(GLFWwindow* window);
bool initGlad();
void renderOrder();
int hellomain() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef _APPLE_
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
const int width = WINDOW_WIDTH;
const int height = WINDOW_HEIGHT;
const char* wname = WINDOW_NAME;
//创建窗口
GLFWwindow* window = glfwCreateWindow(width, height, wname, NULL, NULL);
if (window == NULL) {
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//注册窗口大小改变回调事件
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
glfwTerminate();
return -1;
}
//============================定义着色器=============================
//顶点着色器glsl代码
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"
"}\0";
//定义片段着色器
const char* fragShaderSource =
"#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";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//如果报错大于错误信息
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 1, NULL, infoLog);
cout << "compile vertex shader fail####" << infoLog;
return -1;
}
unsigned int fragShader;
fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader, 1, &fragShaderSource, NULL);
glCompileShader(fragShader);
glGetShaderiv(fragShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragShader, 520, NULL, infoLog);
cout << "compile fragshader fail#########" << infoLog;
return -1;
}
//连接着色器
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog;
}
//连接完成删掉着色器
glDeleteShader(vertexShader);
glDeleteShader(fragShader);
//vbo vao的定义
float vertecs[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = {
// note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
unsigned int VBO, VAO, EBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertecs), vertecs, GL_STATIC_DRAW);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
while (!glfwWindowShouldClose(window)) {
processInput(window);
renderOrder();
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);
gl_LINE
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, &indices);
glBindVertexArray(0);
glfwSwapBuffers(window);
glfwPollEvents(); //释放事件
}
glDeleteBuffers(1, &VBO);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &EBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
/// <summary>
/// 渲染指令
/// </summary>
void renderOrder() {
glClearColor(0.5f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
/// <summary>
/// 修改窗口大小的时候就会调用这个方法
/// </summary>
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
/// <summary>
/// 编写输入函数
/// </summary>
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GL_TRUE);
}
}