openGL学习<三>、图形渲染管道(显示三角形)
参考:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
- 1、图形渲染管线
- 2、顶点渲染器
- 3、片段渲染器
- 4、生成着色器程序
- 5、
1、图形渲染管线
图形渲染管线分为几个阶段小任务,对于每一个阶段任务程序(例如给10亿个像素着色),GPU都可以并行执行,这些小程序叫做着色器(Shader)。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)。
图形渲染管线包含:
- a:3D坐标转换为2D坐标,
- b:2D坐标转变为有颜色的像素
说白了,点 = 顶点(Vertex) + 顶点属性(Vertex Attribute),例如:点 = 坐标 + 颜色,3D点的渲染过程就是不断在修改其坐标、属性的过程。如下图,是图形渲染管线流程,它的每个流程不是在修改点的位置,就是修改点的属性。
图1 图形渲染管线
对于大多数场合,我们只需要配置顶点和片段着色器就行了
2、顶点渲染器
2.1、顶点输入
1 //顶点输入: 2 float vertices[] = { 3 -0.5f, -0.5f, 0.0f, 4 0.5f, -0.5f, 0.0f, 5 0.0f, 0.5f, 0.0f 6 }; 7 unsigned int VBO; // ID 8 glGenBuffers(1, &VBO);// 生成缓冲 9 glBindBuffer(GL_ARRAY_BUFFER, VBO); // 将缓冲绑定到 GL_ARRAY_BUFFER 类型目标上 10 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将vertices复制到缓冲中
2.2、定义顶点着色器
1 #GLSL顶点着色器的源代码: 2 #version 330 core 3 layout (location = 0) in vec3 aPos; 4 5 void main() 6 { 7 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 8 }
2.3、编译顶点着色器
1 //运行时动态编译它的源代码: 2 const char *vertexShaderSource = "#version 330 core\n" 3 "layout (location = 0) in vec3 aPos;\n" 4 "void main()\n" 5 "{\n" 6 " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" 7 "}\0"; 8 9 //创建顶点着色器: 10 unsigned int vertexShader;// 着色器ID 11 vertexShader = glCreateShader(GL_VERTEX_SHADER); 12 //着色器源码附加到着色器对象 13 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); 14 glCompileShader(vertexShader); // 着色器源码编译
最后还要验证下编译是否成功
3、片段渲染器
和上一节流程一致。
3.1、定义片段着色器
1 #version 330 core 2 out vec4 FragColor; 3 4 void main() 5 { 6 FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); 7 }
3.2、编译片段着色器
1 //运行时动态编译它的源代码: 2 const char *fragmentShaderSource = "#version 330 core\n" 3 "out vec4 FragColor;\n" 4 "uniform vec4 ourColor;\n" 5 "void main()\n" 6 "{\n" 7 " FragColor = ourColor;\n" 8 "}\n\0"; 9 //创建片段着色器: 10 unsigned int fragmentShader;// 着色器ID 11 fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 12 //着色器源码附加到着色器对象 13 glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); 14 glCompileShader(fragmentShader);// 着色器源码编译
最后还要验证下编译是否成功
4、生成着色器程序
将上述顶点、片段着色器链接为着色器程序。比较简单,没什么JB好说的。
1 // 创建着色器程序 2 unsigned int shaderProgram; 3 shaderProgram = glCreateProgram(); 4 5 // 将顶点、片段着色器链接为:着色器程序 6 glAttachShader(shaderProgram, vertexShader); 7 glAttachShader(shaderProgram, fragmentShader); 8 glLinkProgram(shaderProgram); 9 10 //验证是否链接成功 11 //... 12 13 // 释放顶点、片段着色器 14 glDeleteShader(vertexShader); 15 glDeleteShader(fragmentShader); 16 17 18 // ... 19 20 // while循环中使用 21 glUseProgram(shaderProgram);
绘制三角形
1 // 2 // Created by sry on 2021/7/6.1 3 // 4 #include <glad/glad.h> 5 #include <GLFW/glfw3.h> 6 #include<iostream> 7 using namespace std; 8 static int p = 1; 9 void framebuffer_size_callback(GLFWwindow* window, int width, int height) 10 { 11 glViewport(0, 0, width, height); // opengl_frame glfw_frame new_frame,知道为啥有这么多JB帧了吧 12 } 13 /* 14 * 15 * 这里我们检查用户是否按下了返回键(Esc)(如果没有按下,glfwGetKey将会返回GLFW_RELEASE。 16 * 如果用户的确按下了返回键,我们将通过glfwSetwindowShouldClose使用把WindowShouldClose属性设置为 true的方法关闭GLFW。 17 * 18 * */ 19 void processInput(GLFWwindow *window) 20 { 21 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) 22 glfwSetWindowShouldClose(window, true); 23 if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) // 按下1变为线框显示 24 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 25 else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) // 按下A变为填充显示 26 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 27 } 28 29 // glsl 渲染语言,这和C++调用Matlab差不多 30 const char *vertexShaderSource = "#version 330 core\n" 31 "layout (location = 0) in vec3 aPos;\n" 32 "void main()\n" 33 "{\n" 34 " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" 35 "}\0"; 36 37 const char *fragmentShaderSource = "#version 330 core\n" 38 "out vec4 FragColor;\n" 39 "void main()\n" 40 "{\n" 41 " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" 42 "}\n\0"; 43 44 int main() 45 { 46 // <1>、初始化GLFW 47 glfwInit(); 48 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // opengl3.3 49 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 50 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 只用核心通用模块 51 52 // <2>、glfw窗口创建 53 GLFWwindow* window = glfwCreateWindow(800, 600, "shiruiyu", NULL, NULL); 54 if (window == NULL) 55 { 56 cout << "fuck" << endl; 57 glfwTerminate(); // Release 58 return -1; 59 } 60 glfwMakeContextCurrent(window); // 通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。 61 /* 62 * 告诉OpenGL渲染窗口的尺寸大小 63 * OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。 64 * */ 65 // 对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用 66 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 67 /* 68 * <3>、利用glad加载所有OpenGL函数地址 69 * GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD 70 * */ 71 if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) 72 { 73 cout << "Failed to init GLAD" << endl; 74 return -1; 75 } 76 // <4>、构建、编译shader program 77 // 创建顶点渲染器,vertex shader 78 unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); 79 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); 80 glCompileShader(vertexShader); 81 // 验证渲染器是否正常 82 int success = 0; 83 char infoLog[512]; 84 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); 85 if (!success) 86 { 87 glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); 88 std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; 89 } 90 // 创建线段渲染器,fragment shader 91 unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 92 glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); 93 glCompileShader(fragmentShader); 94 // 验证渲染器是否正常 95 success = 0; 96 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); 97 if (!success) 98 { 99 glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); 100 std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; 101 } 102 // 链接渲染器,link shaders 103 unsigned int shaderProgram = glCreateProgram(); 104 // 将两个着色器加到shaderProgram中,所以下面释放这两个着色器。 105 glAttachShader(shaderProgram, vertexShader); 106 glAttachShader(shaderProgram, fragmentShader); 107 glLinkProgram(shaderProgram); 108 // 检测链接错误 109 success = 0; 110 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 111 if (!success) 112 { 113 glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); 114 std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; 115 } 116 // 释放两个着色器 117 glDeleteShader(vertexShader); 118 glDeleteShader(fragmentShader); 119 120 // set up vertex data (and buffer(s)) and configure vertex attributes 121 // ------------------------------------------------------------------ 122 float vertices[] = { 123 -0.5f, -0.5f, 0.0f, // left 124 0.5f, -0.5f, 0.0f, // right 125 0.0f, 0.5f, 0.0f // top 126 }; 127 // VAO:顶点数组对象,存储调用VBO的历史过程 128 unsigned int VBO, VAO; 129 glGenVertexArrays(1, &VAO); 130 glBindVertexArray(VAO); // 绑定VAO,// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 131 132 // VBO: 顶点缓冲对象 133 glGenBuffers(1, &VBO); // 利用缓冲区发送数据,效率更高 134 glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO 135 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 将数据传送到VBO 136 137 138 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); 139 glEnableVertexAttribArray(0); 140 141 // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind 142 glBindBuffer(GL_ARRAY_BUFFER, 0); 143 // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other 144 // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. 145 glBindVertexArray(0); // 解绑???? 146 147 // 循环渲染 148 while(!glfwWindowShouldClose(window)) // 检查一次GLFW是否被要求退出 149 { 150 // 输入 151 processInput(window); 152 153 // 渲染指令 154 glClearColor(1.f, 0.3f, 0.3f, 1.0f); 155 glClear(GL_COLOR_BUFFER_BIT); 156 157 // 画第一个三角形 158 glUseProgram(shaderProgram); 159 glBindVertexArray(VAO); 160 glDrawArrays(GL_TRIANGLES, 0, 3);// Primitive:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP 161 162 163 164 165 166 // 检查并调用事件,交换缓冲 167 glfwPollEvents(); // 检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。 168 glfwSwapBuffers(window); // 函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。 169 170 } 171 172 glDeleteVertexArrays(1, &VAO); 173 glDeleteBuffers(1, &VBO); 174 glDeleteProgram(shaderProgram); 175 176 // release 177 glfwTerminate(); 178 179 return 0; 180 }
绘制矩形
1 #include <glad/glad.h> 2 #include <GLFW/glfw3.h> 3 4 #include <iostream> 5 6 void framebuffer_size_callback(GLFWwindow* window, int width, int height); 7 void processInput(GLFWwindow *window); 8 9 // settings 10 const unsigned int SCR_WIDTH = 800; 11 const unsigned int SCR_HEIGHT = 600; 12 13 const char *vertexShaderSource = "#version 330 core\n" 14 "layout (location = 0) in vec3 aPos;\n" 15 "void main()\n" 16 "{\n" 17 " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" 18 "}\0"; 19 const char *fragmentShaderSource = "#version 330 core\n" 20 "out vec4 FragColor;\n" 21 "void main()\n" 22 "{\n" 23 " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" 24 "}\n\0"; 25 26 int main() 27 { 28 // glfw: initialize and configure 29 // ------------------------------ 30 glfwInit(); 31 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 32 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 33 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 34 35 #ifdef __APPLE__ 36 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 37 #endif 38 39 // glfw window creation 40 // -------------------- 41 GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); 42 if (window == NULL) 43 { 44 std::cout << "Failed to create GLFW window" << std::endl; 45 glfwTerminate(); 46 return -1; 47 } 48 glfwMakeContextCurrent(window); 49 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 50 51 // glad: load all OpenGL function pointers 52 // --------------------------------------- 53 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) 54 { 55 std::cout << "Failed to initialize GLAD" << std::endl; 56 return -1; 57 } 58 59 60 // build and compile our shader program 61 // ------------------------------------ 62 // vertex shader 63 unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); 64 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); 65 glCompileShader(vertexShader); 66 // check for shader compile errors 67 int success; 68 char infoLog[512]; 69 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); 70 if (!success) 71 { 72 glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); 73 std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; 74 } 75 // fragment shader 76 unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 77 glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); 78 glCompileShader(fragmentShader); 79 // check for shader compile errors 80 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); 81 if (!success) 82 { 83 glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); 84 std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; 85 } 86 // link shaders 87 unsigned int shaderProgram = glCreateProgram(); 88 glAttachShader(shaderProgram, vertexShader); 89 glAttachShader(shaderProgram, fragmentShader); 90 glLinkProgram(shaderProgram); 91 // check for linking errors 92 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 93 if (!success) { 94 glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); 95 std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; 96 } 97 glDeleteShader(vertexShader); 98 glDeleteShader(fragmentShader); 99 100 // set up vertex data (and buffer(s)) and configure vertex attributes 101 // ------------------------------------------------------------------ 102 float vertices[] = { 103 0.5f, 0.5f, 0.0f, // top right 104 0.5f, -0.5f, 0.0f, // bottom right 105 -0.5f, -0.5f, 0.0f, // bottom left 106 -0.5f, 0.5f, 0.0f // top left 107 }; 108 unsigned int indices[] = { // note that we start from 0! 109 0, 1, 3, // first Triangle 110 1, 2, 3 // second Triangle 111 }; 112 unsigned int VBO, VAO, EBO; 113 glGenVertexArrays(1, &VAO); 114 glGenBuffers(1, &VBO); 115 glGenBuffers(1, &EBO); 116 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 117 glBindVertexArray(VAO); 118 119 glBindBuffer(GL_ARRAY_BUFFER, VBO); 120 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 121 122 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 123 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 124 125 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); 126 glEnableVertexAttribArray(0); 127 128 // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind 129 glBindBuffer(GL_ARRAY_BUFFER, 0); 130 131 // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. 132 //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 133 134 // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other 135 // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. 136 glBindVertexArray(0); 137 138 139 // uncomment this call to draw in wireframe polygons. 140 // 画成线框 141 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 142 143 // render loop 144 // ----------- 145 while (!glfwWindowShouldClose(window)) 146 { 147 // input 148 // ----- 149 processInput(window); 150 151 // render 152 // ------ 153 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 154 glClear(GL_COLOR_BUFFER_BIT); 155 156 // draw our first triangle 157 glUseProgram(shaderProgram); 158 glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized 159 //glDrawArrays(GL_TRIANGLES, 0, 6); 160 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 161 // glBindVertexArray(0); // no need to unbind it every time 162 163 // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) 164 // ------------------------------------------------------------------------------- 165 glfwSwapBuffers(window); 166 glfwPollEvents(); 167 } 168 169 // optional: de-allocate all resources once they've outlived their purpose: 170 // ------------------------------------------------------------------------ 171 glDeleteVertexArrays(1, &VAO); 172 glDeleteBuffers(1, &VBO); 173 glDeleteBuffers(1, &EBO); 174 glDeleteProgram(shaderProgram); 175 176 // glfw: terminate, clearing all previously allocated GLFW resources. 177 // ------------------------------------------------------------------ 178 glfwTerminate(); 179 return 0; 180 } 181 182 // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly 183 // --------------------------------------------------------------------------------------------------------- 184 void processInput(GLFWwindow *window) 185 { 186 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) 187 glfwSetWindowShouldClose(window, true); 188 } 189 190 // glfw: whenever the window size changed (by OS or user resize) this callback function executes 191 // --------------------------------------------------------------------------------------------- 192 void framebuffer_size_callback(GLFWwindow* window, int width, int height) 193 { 194 // make sure the viewport matches the new window dimensions; note that width and 195 // height will be significantly larger than specified on retina displays. 196 glViewport(0, 0, width, height); 197 }
CV&DL