OpenGL从入门到精通--你好三角形
三角形
github源碼倉庫
opengl環境準備
opengl編程從入門到精通-hello,window
OpenGL從入門到精通–你好三角形
OpenGL從入門到精通–著色器的使用
繪圖中需要牢記下面這幾個單詞
- 頂點數組對象:Vertex Array Object, VAO
- 頂點緩沖對象:Vertex Buffer Object, VBO
- 索引緩沖區:Element Buffer Object, EBO或Index Buffer Object, IBO
在OpenGL中,任何事物都在3D空間中,而屏幕和窗口卻是2D像素數組,這導致OpenGL的大部分工作都是關于把3D坐標轉變為適應你屏幕的2D像素。3D坐標轉為2D坐標的處理過程是由OpenGL的圖形渲染管線(Graphics Pipeline,大多譯為管線,實際上指的是一堆原始圖形數據途經一個輸送管道,期間經過各種變化處理最終出現在屏幕的過程)管理的。圖形渲染管線可以被劃分為兩個主要部分:第一部分把你的3D坐標轉換為2D坐標,第二部分是把2D坐標轉變為實際的有顏色的像素。這個教程里,我們會簡單地討論一下圖形渲染管線,以及如何利用它創建一些漂亮的像素。
圖形渲染管線接受一組3D坐標,然后把它們轉變為你屏幕上的有色2D像素輸出。圖形渲染管線可以被劃分為幾個階段,每個階段將會把前一個階段的輸出作為輸入。所有這些階段都是高度專門化的(它們都有一個特定的函數),并且很容易并行執行。正是由于它們具有并行執行的特性,當今大多數顯卡都有成千上萬的小處理核心,它們在GPU上為每一個(渲染管線)階段運行各自的小程序,從而在圖形渲染管線中快速處理你的數據。這些小程序叫做著色器(Shader)。
OpenGL著色器是用OpenGL著色器語言(OpenGL Shading Language, GLSL)寫成的,在下一節中我們再花更多時間研究它。
頂點的輸入
想繪制一個圖形,肯定得給出圖像的坐標點,這里我們直接給出的坐標點就是標準化之后的坐標點,所以在設置向量屬性的時候,設置的也是GL_FALSE不需要再進一步的進行標準化。
頂點信息
float vertices[] = {-0.5f, -0.5f, 0.0f, // left0.5f, -0.5f, 0.0f, // right0.0f, 0.5f, 0.0f // top };設置頂點屬性也選擇GL_FALSE
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);因為OpenGL想要快速的繪制一張圖形,一般都是走的GPU因此這里繪制圖形也是要用到GPU關于GPU著色器的介紹可以參考:
OpenGL-你好三角形
下面要做的就是把頂點信息放到緩存中去
unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). /* 要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用 */ /* // ..:: 初始化代碼(只運行一次 (除非你的物體頻繁改變)) :: ..// 1. 綁定VAO */ glBindVertexArray(VAO); // 2. 把頂點數組復制到緩沖中供OpenGL使用 glBindBuffer(GL_ARRAY_BUFFER, VBO); // 把之前定義的頂點,復制到緩沖的內存中去 /*GL_STATIC_DRAW :數據不會或幾乎不會改變。GL_DYNAMIC_DRAW:數據會被改變很多。GL_STREAM_DRAW :數據每次繪制時都會改變。 */ /*三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW。如果,比如說一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分。 */ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);/*位置數據被儲存為32位(4字節)浮點值。每個位置包含3個這樣的值。在這3個值之間沒有空隙(或其他值)。這幾個值在數組中緊密排列(Tightly Packed)。數據中第一個值在緩沖開始的位置 */ // 告訴GPU數據怎樣取 /*第一個參數指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0。我們會在后面詳細解釋這個參數。 */ // 設置頂點屬性指針 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); /*現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性;頂點屬性默認是禁用的。 */ glEnableVertexAttribArray(0);// 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 glBindBuffer(GL_ARRAY_BUFFER, 0);// You can unbind the VAO afterwards so other VAO calls //won't accidentally modify this VAO, but this rarely happens. Modifying other // VAOs requires a call to glBindVertexArray anyways //so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. glBindVertexArray(0);著色器
著色器創建需要創建頂點著色器和片段著色器,然后使用鏈接程序把兩個著色器鏈接,需要注意的是鏈接程序鏈接的著色器前一個程序的輸出,必須要和后面一個程序的輸入對應,否則就會報錯。
因為著色器是運行在GPU上的一個個小程序,因此需要動態編譯,需要時將編譯好的程序交給GPU運行,使用的語言是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"; // 將著色器源碼附加到著色器對象上,然后使用glCompileShader進行編譯 // param1 著色器ID, param2 源碼字符串數量 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader);因為傳入的是程序片段,我們需要知道程序是運行時出錯還是根本就編譯不過,可以使用glGetShaderiv函數獲取上述程序編譯的狀態
// check for shader compile errors int success; // 獲取程序編譯的狀態,0成功非0失敗 char infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) {// 程序編譯失敗的信息glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; }用同樣的方法處理片段著色器
// fragment shader // 片段著色器 int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); // check for shader compile errors glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) {glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; }使用連接器鏈接兩個著色器
// link shaders/*當鏈接著色器至一個程序的時候,它會把每個著色器的輸出鏈接到下個著色器的輸入。當輸出和輸入不匹配的時候,你會得到一個連接錯誤。*/int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// check for linking errorsglGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}在Render里面渲染三角形
// draw our first triangle // 2. 當我們渲染一個物體時要使用著色器程序 glUseProgram(shaderProgram); 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 // 3. 繪制物體 GL_TRIANGLES 繪制三角形,從0起點開始繪制,繪制3個點 glDrawArrays(GL_TRIANGLES, 0, 3);完整代碼實現
// // Created by andrew on 2021/1/17. // #include "glad/glad.h" #include <GLFW/glfw3.h>#include <iostream>using namespace std;void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;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 *fragmentShaderSource = "#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";int main() {// 對glfw進行初始化glfwInit();// 打印出glfw的版本信息// int* major, int* minor, int* revint major, minor, rev;glfwGetVersion(&major, &minor, &rev);cout << "major = " << major << " minor = " << minor << " rev = " << rev << endl;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// glfw創建窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);if (window == nullptr){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// 為當前window設置上下文,每個線程只能設置一個,并且線程之間共用時,需要將當前線程設置為 non-currentglfwMakeContextCurrent(window);// 設置窗口大小的回調函數,當窗口大小改變時,會調用該函數調整串口的大小// 注冊窗口大小改變回調函數glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// glad 會加載所有openGL函數指針,在調用任何opengl函數之前需要先初始化gladif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------// vertex shader// 創建一個著色器對象, 返回出著色器的ID// 頂點著色器int vertexShader = glCreateShader(GL_VERTEX_SHADER);// 將著色器源碼附加到著色器對象上,然后使用glCompileShader進行編譯// param1 著色器ID, param2 源碼字符串數量glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// check for shader compile errorsint success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// fragment shader// 片段著色器int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// check for shader compile errorsglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// link shaders/*當鏈接著色器至一個程序的時候,它會把每個著色器的輸出鏈接到下個著色器的輸入。當輸出和輸入不匹配的時候,你會得到一個連接錯誤。*/int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// check for linking errorsglGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {-0.5f, -0.5f, 0.0f, // left0.5f, -0.5f, 0.0f, // right0.0f, 0.5f, 0.0f // top};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s)./* 要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用 *//* // ..:: 初始化代碼(只運行一次 (除非你的物體頻繁改變)) :: ..// 1. 綁定VAO */glBindVertexArray(VAO);// 2. 把頂點數組復制到緩沖中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);// 把之前定義的頂點,復制到緩沖的內存中去/*GL_STATIC_DRAW :數據不會或幾乎不會改變。GL_DYNAMIC_DRAW:數據會被改變很多。GL_STREAM_DRAW :數據每次繪制時都會改變。 *//*三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW。如果,比如說一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分。 */glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);/*位置數據被儲存為32位(4字節)浮點值。每個位置包含3個這樣的值。在這3個值之間沒有空隙(或其他值)。這幾個值在數組中緊密排列(Tightly Packed)。數據中第一個值在緩沖開始的位置 */// 告訴GPU數據怎樣取/*第一個參數指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0。我們會在后面詳細解釋這個參數。 */// 設置頂點屬性指針glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);/*現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性;頂點屬性默認是禁用的。 */glEnableVertexAttribArray(0);// 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 unbindglBindBuffer(GL_ARRAY_BUFFER, 0);// You can unbind the VAO afterwards so other // VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways // so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);/*就這么多了!前面做的一切都是等待這一刻,一個儲存了我們頂點屬性配置和應使用的VBO的頂點數組對象。一般當你打算繪制多個物體時,你首先要生成/配置所有的VAO(和必須的VBO及屬性指針),然后儲存它們供后面使用。當我們打算繪制物體的時候就拿出相應的VAO,綁定它,繪制完物體后,再解綁VAO。*/// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------// 北背景glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// draw our first triangle// 2. 當我們渲染一個物體時要使用著色器程序glUseProgram(shaderProgram);// 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 organizedglBindVertexArray(VAO);// 3. 繪制物體glDrawArrays(GL_TRIANGLES, 0, 3);// glBindVertexArray(0); // no need to unbind it every time// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// 正確的釋放之前分配的所有資源glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {// 用戶按下 esc鍵,就設置退出串口為真if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// opengl渲染串口大小,每次調整窗口cout << "view port call back" << endl;//glViewport(0, 0, width, height); }如果沒有出問題,你將得到下面這幅三角形繪制的圖:
如果順利得到這副圖,那么恭喜你,你已經學完了opengl的入門,也學完了opengl的難點,能夠掌握三角形的繪制以及原理,后面的學習將會簡單很多
github源碼倉庫
opengl環境準備
opengl編程從入門到精通-hello,window
總結
以上是生活随笔為你收集整理的OpenGL从入门到精通--你好三角形的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【操作系统】死锁
- 下一篇: 【2016年第1期】农业大数据给商品交易