原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027ios
《OpenGL學習筆記》系列博客目錄地址:http://blog.csdn.net/qq21497936/article/category/7315532
數組
OpenGL學習筆記(九):索引緩衝器(EBO /IBE)的理解與使用,引入線框/填充模式
前話
專業名詞
EBO(Element Buffer Object)、EBO(Index Buffer Object):索引緩衝對象。緩存
索引緩衝對象
索引緩衝對象專門儲存索引,OpenGL調用這些頂點的索引來決定該繪製哪一個頂點,使用索引可重複使用同一個頂點。函數
不使用索引緩衝器繪製
首先咱們先看六邊形的六個頂點座標:工具
在不使用索引的狀況下,咱們的頂點數據以下:oop
// 7.頂點數據 float vertices[] = { -0.866, -0.5, 0.0, -0.866, 0.5, 0.0, 0.0 , 1.0, 0.0, // 三角形1 -0.866, 0.5, 0.0, 0.0 , 1.0, 0.0, 0.866, 0.5, 0.0, // 三角形2 0.0 , 1.0, 0.0, 0.866, 0.5, 0.0, 0.866, -0.5, 0.0, // 三角形3 0.866, 0.5, 0.0, 0.866, -0.5, 0.0, 0.0 , -1.0, 0.0, // 三角形4 0.866, -0.5, 0.0, 0.0 , -1.0, 0.0, -0.866, -0.5, 0.0, // 三角形5 0.0 , -1.0, 0.0, -0.866, -0.5, 0.0, -0.866, 0.5, 0.0, // 三角形6 };
繪製的頂點數量,是18個,因此修改程序的繪製頂點數量:學習
while (!glfwWindowShouldClose(window)) { …… // 繪製三角形:三角形 數組起始索引 繪製多少個頂點 // glDrawArrays(GL_TRIANGLES, 0, 3); glDrawArrays(GL_TRIANGLES, 0, 18); …… }
使用索引緩衝器繪製
使用索引緩衝器,其實咱們全部的三角形只使用到了六個頂點(須要重複使用的經過索引可重複使用),VAO中就保存該6個頂點能夠了:ui
// 7.0索引緩衝期 增長的索引緩衝器頂點索引 unsigned int indices[] = { // 注意索引從0開始! 0, 1, 2, // 第一個三角形 1, 2, 3, // 第二個三角形 2, 3, 4, // 第三個三角形 3, 4, 5, // 第四個三角形 4, 5, 0, // 第五個三角形 5, 0, 1, // 第六個三角形 };
使用EBO和VBO同樣,首先是使用過程是同樣的,其次是使用的注意點也是同樣的,就是綁定EBO/VBO到解綁EBO/VBO(須要生效的)的過程必定要在綁定頂點數據和解綁頂點數據之間,否該繪製該頂點,緩存未被生效。url
如下是在原來的VAO,VBO代碼部分新增了EBO的代碼:spa
// 8.使用VAO和VBO unsigned int VBO, VAO; // glGenVertexArrays() 建立一個頂點數組對象 // 第一個參數:須要建立的緩存數量 // 第二個參數:存儲單一ID或多個ID的GLuint變量或數組的地址。 glGenVertexArrays(1, &VAO); // glGenBuffers() 建立一個緩存對象而且返回緩存對象的標示符。 glGenBuffers(1, &VBO); // 頂點對象建立以後,在使用緩存對象以前,須要將緩存對象鏈接到相應的緩存上。 // glBindBuffer()有1個參數:buffer。(綁定和解綁的順序很重要,勿更改) glBindVertexArray(VAO); // 緩存對象建立以後,在使用緩存對象以前,須要將緩存對象鏈接到相應的緩存上。 // glBindBuffer()有2個參數:target與buffer。(綁定和解綁的順序很重要,勿更改) glBindBuffer(GL_ARRAY_BUFFER, VBO); // 當緩存初始化以後,使用glBufferData()將頂點數據拷貝到緩存對象 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); #if 1 // 索引緩衝器-添加代碼 // 8.0 與VBO相似,先建立EBO對象,再先綁定EBO // 而後用glBufferData把索引複製到緩衝裏 // 一樣,和VBO相似,把這些函數調用放在綁定和解綁函數調用之間 // 區別在於,只不過此次咱們把緩衝的類型定義爲GL_ELEMENT_ARRAY_BUFFER。 unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); #endif // 設置頂點屬性指針,glVertexAttribPointer()函數告訴OpenGL該如何解析頂點數據 // 頂點屬性位置 頂點屬性大小 數據的類型 是否被標準化 步長 偏移 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0); // glEnableVertexAttribArray()以頂點屬性位置值做爲參數,啓用頂點屬性;頂點屬性默認是禁用的 glEnableVertexAttribArray(0); // 解綁緩存着色器(綁定和解綁的順序很重要,勿更改) glBindBuffer(GL_ARRAY_BUFFER, 0); // 解綁頂點着色器(綁定和解綁的順序很重要,勿更改) glBindVertexArray(0);
修改後的繪製循環代碼:
while (!glfwWindowShouldClose(window)) { // 捕捉輸入 processInput(window); // 清空顏色緩衝 // 添加渲染指令,背景RGBA的A不生效, 更改成白色 glClearColor(1.0f, 1.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); /* 新增代碼 第二段/共三段----------------------------------START */ // 當咱們渲染一個物體時要使用着色器程序 glUseProgram(shaderProgram); // 激活VAO表示的頂點緩存 glBindVertexArray(VAO); // 每次從新更新視口,若是不這樣,拉昇窗口後,會變形。 // 你把你要觀察的視景體映射到你的視口就沒有問題,可是若是你不設置視景體, // opengl默認會以屏幕座標,就是屏幕中心爲原點,到四周的距離爲1的這個範圍 // 做爲視景體的觀察部分映射到你設置的視口上。 // 能夠嘗試,屏蔽此句,而後運行程序拉昇窗口試試效果,更好理解 // glViewport(0, 0, 600, 600); // 繪製三角形:三角形 數組起始索引 繪製多少個頂點 // 不使用索引緩衝器繪製頂點 #if 1 // 繪製六邊的兩兩鏈接的三角形 // glDrawArrays(GL_TRIANGLES, 0, 18); #endif #if 1 // 索引緩衝器-添加代碼 18個索引點(三角形,3個因此組成一個三角形) // 這裏18表明是18個索引,頂點數座標也是18,只是巧合罷了 glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0); #endif // 繪製三角形:線條 數組起始索引 繪製多少個頂點 // glDrawArrays(GL_LINES, 0 , 2); /* 新增代碼 第二段/共三段----------------------------------END */ // 檢查並調用事件,交換緩衝(執行交換緩存纔會更新新數據到界面) glfwSwapBuffers(window); glfwPollEvents(); }
運行結果

源代碼
#include <iostream> #include <process.h> #include <GLFW/glfw3.h> #include <glad/glad.h> void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } // 在GLFW中實現一些輸入控制,使用GLFW的glfwGetKey函數 void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } int main() { glfwInit(); /* 請確認您的系統支持OpenGL3.3或更高版本,不然此應用有可能會 崩潰或者出現不可預知的錯誤。若是想要查看OpenGL版本的話, 在Linux上運行glxinfo,或者在Windows上使用其它的工具(例如 OpenGL Extension Viewer)。若是你的OpenGL版本低於3.3,檢 查一下顯卡是否支持OpenGL 3.3+(不支持的話你的顯卡真的太老 了),並更新你的驅動程序,有必要的話請更新顯卡。 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 若是使用的是Mac OS X系統,你還須要加下面這行代碼到你的 // 初始化代碼中這些配置才能起做用 //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 建立一個窗口對象,這個窗口對象存放了全部和窗口相關的數據, // 並且會被GLFW的其餘函數頻繁地用到 // glfwCreateWindow函數須要窗口的寬和高做爲它的前兩個參數。 // 第三個參數表示這個窗口的名稱(標題),最後兩個參數暫時忽略。 // 函數會返回一個GLFWwindow對象,咱們會在其它的GLFW操做中使用到。 GLFWwindow* window = glfwCreateWindow(800, 600, "QQ:21497936", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // GLAD是用來管理OpenGL的函數指針的,因此在調用任何OpenGL的函數 // 以前咱們須要初始化GLAD。 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // 還須要註冊這個函數,告訴GLFW咱們但願每當窗口調整大小的時候調用這個函數。 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 咱們必須告訴OpenGL渲染窗口的尺寸大小,即視口(Viewport), // 這樣OpenGL才只能知道怎樣根據窗口大小顯示數據和座標。 // 能夠經過調用glViewport函數來設置窗口的維度(Dimension)。 // OpenGL幕後使用glViewport中定義的位置和寬高進行2D座標的轉 // 換,將OpenGL中的位置座標轉換爲你的屏幕座標。例如,OpenGL // 中的座標(-0.5, 0.5)有可能(最終)被映射爲屏幕中的座標(200,450)。 // 注意,處理過的OpenGL座標範圍只爲-1到1,所以咱們事實上 // 將(-1到1)範圍內的座標映射到(0, 800)和(0, 600)。 glViewport(0, 0, 600, 600); // 在咱們主動關閉它以前不斷繪製圖像並可以接受用戶輸入。所以, // 須要在程序中添加一個while循環,咱們能夠把它稱之爲渲染循環(Render Loop), // 它能在咱們讓GLFW退出前一直保持運行。 /* 新增代碼 第一段/共三段----------------------------------START */ // 1.頂點着色器 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" "}"; // 2.片斷着色器 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" "}"; // 3.建立頂點着色器並編譯 int success; char infoLog[512]; int vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } // 4.建立片斷着色器並編譯 int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } // 5.鏈接着色器 int shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if(!success) { glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n" << infoLog << std::endl; } // 6.刪除已經使用的着色器(由於後面再也不須要綁定了) glDeleteShader(vertexShader); glDeleteShader(fragmentShader); #if 0 // 7.頂點數據 float vertices[] = { -0.866, -0.5, 0.0, -0.866, 0.5, 0.0, 0.0 , 1.0, 0.0, // 三角形1 -0.866, 0.5, 0.0, 0.0 , 1.0, 0.0, 0.866, 0.5, 0.0, // 三角形2 0.0 , 1.0, 0.0, 0.866, 0.5, 0.0, 0.866, -0.5, 0.0, // 三角形3 0.866, 0.5, 0.0, 0.866, -0.5, 0.0, 0.0 , -1.0, 0.0, // 三角形4 0.866, -0.5, 0.0, 0.0 , -1.0, 0.0, -0.866, -0.5, 0.0, // 三角形5 0.0 , -1.0, 0.0, -0.866, -0.5, 0.0, -0.866, 0.5, 0.0, // 三角形6 }; #endif #if 1 // 索引緩衝器-添加代碼 // 7.頂點數據 float vertices[] = { -0.866, -0.5, 0.0, // 1 三角形5 -0.866, 0.5, 0.0, // 1 2 三角形6 0.0 , 1.0, 0.0, // 三角形1 2 3 0.866, 0.5, 0.0, // 三角形2 3 4 0.866, -0.5, 0.0, // 三角形3 4 5 0.0 , -1.0, 0.0, // 三角形4 5 6 }; // 7.0索引緩衝期 增長的索引緩衝器頂點索引 unsigned int indices[] = { // 注意索引從0開始! 0, 1, 2, // 第一個三角形 1, 2, 3, // 第二個三角形 2, 3, 4, // 第三個三角形 3, 4, 5, // 第四個三角形 4, 5, 0, // 第五個三角形 5, 0, 1 // 第六個三角形 }; #endif // 8.使用VAO和VBO unsigned int VBO, VAO; // glGenVertexArrays() 建立一個頂點數組對象 // 第一個參數:須要建立的緩存數量 // 第二個參數:存儲單一ID或多個ID的GLuint變量或數組的地址。 glGenVertexArrays(1, &VAO); // glGenBuffers() 建立一個緩存對象而且返回緩存對象的標示符。 glGenBuffers(1, &VBO); // 頂點對象建立以後,在使用緩存對象以前,須要將緩存對象鏈接到相應的緩存上。 // glBindBuffer()有1個參數:buffer。(綁定和解綁的順序很重要,勿更改) glBindVertexArray(VAO); // 緩存對象建立以後,在使用緩存對象以前,須要將緩存對象鏈接到相應的緩存上。 // glBindBuffer()有2個參數:target與buffer。(綁定和解綁的順序很重要,勿更改) glBindBuffer(GL_ARRAY_BUFFER, VBO); // 當緩存初始化以後,使用glBufferData()將頂點數據拷貝到緩存對象 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); #if 1 // 索引緩衝器-添加代碼 // 8.0 與VBO相似,先建立EBO對象,再先綁定EBO // 而後用glBufferData把索引複製到緩衝裏 // 一樣,和VBO相似,把這些函數調用放在綁定和解綁函數調用之間 // 區別在於,只不過此次咱們把緩衝的類型定義爲GL_ELEMENT_ARRAY_BUFFER。 unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); #endif // 設置頂點屬性指針,glVertexAttribPointer()函數告訴OpenGL該如何解析頂點數據 // 頂點屬性位置 頂點屬性大小 數據的類型 是否被標準化 步長 偏移 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0); // glEnableVertexAttribArray()以頂點屬性位置值做爲參數,啓用頂點屬性;頂點屬性默認是禁用的 glEnableVertexAttribArray(0); // 解綁緩存着色器(綁定和解綁的順序很重要,勿更改) glBindBuffer(GL_ARRAY_BUFFER, 0); // 解綁頂點着色器(綁定和解綁的順序很重要,勿更改) glBindVertexArray(0); /* 新增代碼 第一段/共三段----------------------------------END */ while (!glfwWindowShouldClose(window)) { // 捕捉輸入 processInput(window); // 清空顏色緩衝 // 添加渲染指令,背景RGBA的A不生效, 更改成白色 glClearColor(1.0f, 1.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); /* 新增代碼 第二段/共三段----------------------------------START */ // 當咱們渲染一個物體時要使用着色器程序 glUseProgram(shaderProgram); // 激活VAO表示的頂點緩存 glBindVertexArray(VAO); // 每次從新更新視口,若是不這樣,拉昇窗口後,會變形。 // 你把你要觀察的視景體映射到你的視口就沒有問題,可是若是你不設置視景體, // opengl默認會以屏幕座標,就是屏幕中心爲原點,到四周的距離爲1的這個範圍 // 做爲視景體的觀察部分映射到你設置的視口上。 // 能夠嘗試,屏蔽此句,而後運行程序拉昇窗口試試效果,更好理解 // glViewport(0, 0, 600, 600); // 繪製三角形:三角形 數組起始索引 繪製多少個頂點 // 不使用索引緩衝器繪製頂點 #if 1 // 繪製六邊的兩兩鏈接的三角形 // glDrawArrays(GL_TRIANGLES, 0, 18); #endif #if 1 // 索引緩衝器-添加代碼 18個索引點(三角形,3個因此組成一個三角形) // 這裏18表明是18個索引,頂點數座標也是18,只是巧合罷了 glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0); #endif // 繪製三角形:線條 數組起始索引 繪製多少個頂點 // glDrawArrays(GL_LINES, 0 , 2); /* 新增代碼 第二段/共三段----------------------------------END */ // 檢查並調用事件,交換緩衝(執行交換緩存纔會更新新數據到界面) glfwSwapBuffers(window); glfwPollEvents(); } /* 新增代碼 第三段/共三段----------------------------------END */ glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); /* 新增代碼 第三段/共三段----------------------------------END */ // 當渲染循環結束後咱們須要正確釋放/刪除以前的分配的全部資源。 glfwTerminate(); return 0; }
拓展
只畫邊框(不填充,相似於畫圖的畫刷),加上以下代碼:#if 1 // 使用xiankuan線框模式 隨便放哪裏,只要再glad以後(由於glad以後才能準確找到gl函數地址) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endi
線框模式(Wireframe Mode)
想用線框模式繪製圖形,可知填充模式,經過glPolygonMode(GL_FRONT_AND_BACK,GL_LINE)函數配置OpenGL如何繪製圖元。第一個參數表示咱們打算將其應用到全部的三角形的正面和背面,第二個參數告訴用線來繪製。以後的繪製調用會一直以線框模式繪製三角形,直到咱們用glPolygonMode(GL_FRONT_AND_BACK,GL_FILL)將其設置回默認模式。
拓展效果
原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027
本文同步分享在 博客「紅胖子(AAA紅模仿)」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。