圖形渲染管線指的是對一些原始數據通過一系列的處理變換並最終把這些數據輸出到屏幕上的整個過程。數組
圖形渲染管線的整個處理流程能夠被劃分爲幾個階段,上一個階段的輸出數據做爲下一個階段的輸入數據,是一個串行的,面向過程的執行過程。每個階段分別在GPU上運行各自的數據處理程序,這個程序就是着色器。緩存
部分着色器容許咱們使用着色語言(OpenGL Shading Language)編寫自定義的着色器,這樣就能夠更爲細緻的控制圖像渲染流程中的特定處理過程了,下圖是一個圖形渲染管線每個階段的抽象表示,藍色部分表明容許自定義着色器。ide
頂點數據是一些頂點的集合,頂點通常是3維的點座標組成。函數
基本圖元(Primitives)包括點,線段,三角形等,是構成實體模型的基本單位,須要在傳入頂點數據的同時通知OpenGL這些頂點數據要組成的基本圖元類型。oop
頂點着色器(Vertex Shader)包含對一些頂點屬性(數據)的基本處理。測試
基本圖元裝配(Primitive Assembly)把全部輸入的頂點數據做爲輸入,輸出制定的基本圖元。ui
幾何着色器(Geometry Shader)把基本圖元形式的頂點的集合做爲輸入,能夠經過產生新頂點構造出新的(或是其餘的)基本圖元來生成其餘形狀。spa
細分着色器(Tessellation Shaders)能夠把基本圖元細分爲更多的基本圖形,建立出更加平滑的視覺效果。3d
光柵化(Rasterization)即像素化,把細分着色器輸出的基本圖形映射爲屏幕上網格的像素點,生成供片斷着色器處理的片斷(Fragment),光柵化包含一個剪裁操做,會捨棄超出定義的視窗以外的像素。code
片斷着色器(Fragment Shader)的主要做用是計算出每個像素點最終的顏色,一般片斷着色器會包含3D場景的一些額外的數據,如光線,陰影等。
測試與混合是對每一個像素點進行深度測試,Alpha測試等測試並進行顏色混合的操做,這些測試與混合操做決定了屏幕視窗上每一個像素點最終的顏色以及透明度。
在整個渲染管線中須要自定義處理的主要是頂點着色器和片斷着色器。
頂點緩衝對象VBO是在顯卡存儲空間中開闢出的一塊內存緩存區,用於存儲頂點的各種屬性信息,如頂點座標,頂點法向量,頂點顏色數據等。在渲染時,能夠直接從VBO中取出頂點的各種屬性數據,因爲VBO在顯存而不是在內存中,不須要從CPU傳輸數據,處理效率更高。
因此能夠理解爲VBO就是顯存中的一個存儲區域,能夠保持大量的頂點屬性信息。而且能夠開闢不少個VBO,每一個VBO在OpenGL中有它的惟一標識ID,這個ID對應着具體的VBO的顯存地址,經過這個ID能夠對特定的VBO內的數據進行存取操做。
VBO的建立以及配置
建立VBO的第一步須要開闢(聲明/得到)顯存空間並分配VBO的ID:
//建立vertex buffer object對象 GLuint vboId;//vertex buffer object句柄 glGenBuffers(1, &vboId);
建立的VBO可用來保存不一樣類型的頂點數據,建立以後須要經過分配的ID綁定(bind)一下制定的VBO,對於同一類型的頂點數據一次只能綁定一個VBO。綁定操做經過glBindBuffer來實現,第一個參數指定綁定的數據類型,能夠是GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER或者GL_PIXEL_UNPACK_BUFFER中的一個。
glBindBuffer(GL_ARRAY_BUFFER, vboId);
接下來調用glBufferData把用戶定義的數據傳輸到當前綁定的顯存緩衝區中。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
頂點數據傳入GPU以後,還須要通知OpenGL如何解釋這些頂點數據,這個工做由函數glVertexAttribPointer完成:
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
頂點屬性glVertexAttribPointer默認是關閉的,使用時要以頂點屬性位置值爲參數調用glEnableVertexAttribArray開啓。如glEnableVertexAttribArray(0);
VBO保存了一個模型的頂點屬性信息,每次繪製模型以前須要綁定頂點的全部信息,當數據量很大時,重複這樣的動做變得很是麻煩。VAO能夠把這些全部的配置都存儲在一個對象中,每次繪製模型時,只須要綁定這個VAO對象就能夠了。
VAO是一個保存了全部頂點數據屬性的狀態結合,它存儲了頂點數據的格式以及頂點數據所需的VBO對象的引用。
VAO自己並無存儲頂點的相關屬性數據,這些信息是存儲在VBO中的,VAO至關因而對不少個VBO的引用,把一些VBO組合在一塊兒做爲一個對象統一管理。
VAO的建立和配置
生成一個VAO對象並綁定:
//建立vertex array object對象 GLuint vaoId;//vertext array object句柄 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId);
執行VAO綁定以後其後的全部VBO配置都是這個VAO對象的一部分,能夠說VBO是對頂點屬性信息的綁定,VAO是對不少個VBO的綁定。
OpenGL中全部的圖形都是經過分解成三角形的方式進行繪製,glDrawArrays函數負責把模型繪製出來,它使用當前激活的着色器,當前VAO對象中的VBO頂點數據和屬性配置來繪製出來基本圖形。
glDrawArrays (GLenum mode, GLint first, GLsizei count)
第二個參數定義從緩存中的哪一位開始繪製,通常定義爲0;
第三個參數定義繪製的頂點數量;
索引緩衝對象EBO至關於OpenGL中的頂點數組的概念,是爲了解決同一個頂點多洗重複調用的問題,能夠減小內存空間浪費,提升執行效率。當須要使用重複的頂點時,經過頂點的位置索引來調用頂點,而不是對重複的頂點信息重複記錄,重複調用。
EBO中存儲的內容就是頂點位置的索引indices,EBO跟VBO相似,也是在顯存中的一塊內存緩衝器,只不過EBO保存的是頂點的索引。
建立EBO並綁定,用glBufferData(以GL_ELEMENT_ARRAY_BUFFER爲參數)把索引存儲到EBO中:
GLuint EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
當用EBO綁定頂點索引的方式繪製模型時,須要使用glDrawElements而不是glDrawArrays:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
Talk is cheap,第一個例子是使用VBO,VAO繪製一個矩形圖形:
//使用VAO VBO繪製矩形 #include <GL/glew.h> #include <GL/freeglut.h> void userInit(); //自定義初始化 void reshape(int w, int h); //重繪 void display(void); void keyboardAction(unsigned char key, int x, int y); //鍵盤退出事件 GLuint vboId;//vertex buffer object句柄 GLuint vaoId;//vertext array object句柄 GLuint programId;//shader program 句柄 int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowPosition(100, 100); glutInitWindowSize(512, 512); glutCreateWindow("Rectangle demo"); //使用glew,須要執行glewInit,否則運行過程會報錯 //glewInit要放在glut完成了基本的初始化以後執行 glewInit(); //自定義初始化,生成VAO,VBO對象 userInit(); //重繪函數 glutReshapeFunc(reshape); glutDisplayFunc(display); //註冊鍵盤按鍵退出事件 glutKeyboardFunc(keyboardAction); glutMainLoop(); return 0; } //自定義初始化函數 void userInit() { glClearColor(0.0, 0.0, 0.0, 0.0); //建立頂點數據 const GLfloat vertices[] = { -0.5f,-0.5f,0.0f,1.0f, 0.5f,-0.5f,0.0f,1.0f, 0.5f,0.5f,0.0f,1.0f, -0.5f,0.5f,0.0f,1.0f, }; //建立VAO對象 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId); //建立VBO對象 glGenBuffers(1, &vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId); //傳入VBO數據 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //解除VBO綁定 glBindBuffer(GL_ARRAY_BUFFER, 0); } //調整窗口大小回調函數 void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); } //繪製回調函數 void display(void) { glClear(GL_COLOR_BUFFER_BIT); //綁定VBO glBindBuffer(GL_ARRAY_BUFFER, vboId); glEnableVertexAttribArray(0); //解釋頂點數據方式 glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //繪製模型 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableVertexAttribArray(0); glutSwapBuffers(); } //鍵盤按鍵回調函數 void keyboardAction(unsigned char key, int x, int y) { switch (key) { case 033: // Escape key exit(EXIT_SUCCESS); break; } }
編譯並執行:
第二個例子使用EBO繪製兩個三角形,組成一樣的矩形圖形:
//使用EBO繪製矩形(兩個三角形) #include <GL/glew.h> #include <GL/freeglut.h> void userInit(); //自定義初始化 void reshape(int w, int h); //重繪 void display(void); void keyboardAction(unsigned char key, int x, int y); //鍵盤退出事件 GLuint eboId;//element buffer object句柄 GLuint vboId;//vertext buffer object句柄 GLuint vaoId;//vertext array object句柄 int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowPosition(100, 100); glutInitWindowSize(512, 512); glutCreateWindow("Rectangle demo"); //使用glew,須要執行glewInit,否則運行過程會報錯 //glewInit要放在glut完成了基本的初始化以後執行 glewInit(); //自定義初始化,生成VAO,VBO,EBO userInit(); //重繪函數 glutReshapeFunc(reshape); glutDisplayFunc(display); //註冊鍵盤按鍵退出事件 glutKeyboardFunc(keyboardAction); glutMainLoop(); return 0; } //自定義初始化函數 void userInit() { glClearColor(0.0, 0.0, 0.0, 0.0); //建立頂點數據 const GLfloat vertices[] = { -0.5f,-0.5f,0.0f,1.0f, 0.5f,-0.5f,0.0f,1.0f, 0.5f,0.5f,0.0f,1.0f, -0.5f,0.5f,0.0f,1.0f, }; // 索引數據 GLshort indices[] = { 0, 1, 3, // 第一個三角形 1, 2, 3 // 第二個三角形 }; //建立VAO對象 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId); //建立VBO對象,把頂點數組複製到一個頂點緩衝中,供OpenGL使用 glGenBuffers(1, &vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //建立EBO對象 glGenBuffers(1, &eboId); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); //傳入EBO數據 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //解釋頂點數據方式 glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); //解綁VAO glBindVertexArray(0); //解綁EBO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //解綁VBO glBindBuffer(GL_ARRAY_BUFFER, 0); } //調整窗口大小回調函數 void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); } //繪製回調函數 void display(void) { glClear(GL_COLOR_BUFFER_BIT); //綁定VAO glBindVertexArray(vaoId); //繪製模型 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL); glutSwapBuffers(); } //鍵盤按鍵回調函數 void keyboardAction(unsigned char key, int x, int y) { switch (key) { case 033: // Escape key exit(EXIT_SUCCESS); break; } }