原文地址:http://ogldev.atspace.co.uk/www/tutorial02/tutorial02.html
通常在寫OpenGL程序時候,我們都需要glew庫,該庫包裝了OpenGL的各種擴展,便於我們使用。 我們可以在main函數中調用glew初始化函數,之后就可以查詢OpenGL各種擴展能否使用了,對於能夠使用的函數,可以動態的加載。
在這篇教程中,我們首先了解一下頂點緩沖對象(VBO,vertex buffer object)的用法。在計算機圖形學的3D世界中,三維物體對象都是由一系列頂點組成的,比如武士模型、城堡模型等等(注意下面兩副圖),這些頂點相連接,組成mesh(三角形)。
顧名思義,頂點緩沖就是保存頂點的內存對象,通常VBO是GPU顯存中的一塊區域,使用VBO可以加速頂點的讀取速度。
在本篇教程和下一篇教程中,我們僅使用OpenGL中固定管線(相對固定管線的是可編程管線)的編程方法分別來畫一個點和一個三角形。我們並沒有對所渲染的物體(就是一個點或一個三角形)進行平移、縮放、旋轉等操作,實際上我們只是在歸一化的裁剪空間中來安排我們的物體,可以把歸一化的裁剪空間看成一個正方體,坐標原點在正方形的中心,x, y,z坐標的范圍都是[-1.0,1.0],我們把物體映射到屏幕空間(可以看作立方體中的物體先投影到z=-1的面上,然后該面的四邊形映射到屏幕空間),比如屏幕空間是1024*768,則x坐標-1,映射到0,+1映射到1023,x和y等於0的點,則被映射到屏幕中心,最終根據draw函數中指定的體元語義,通過光柵化操作,把物體在屏幕空間畫出來。
下面我們看下程序的部分源代碼實現:
#include <GL/glew.h>
首先我們要包含glew頭文件,注意我們要把這個頭文件放在freee_glut.h的前面,否則的話,程序編譯有可能出錯。
#include "math_3d.h"
在這篇教程中,我們畫一個頂點,頂點結構在math_3d.h中定義。
GLenum res = glewInit();
if (res != GLEW_OK)
{
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return 1;
}
上面是glew初始化的代碼,如果初始化失敗,則會輸出錯誤信息,注意:glew初始化代碼必須在glut初始化之后。
Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);
我們創建了一個頂點數組,該數組只有一個元素,其頂點坐標X,Y,Z都為0,后面我們將會看到這個點在屏幕中心位置顯示。
GLuint VBO;
我們定義一個GLuint類型的全局變量VBO來表示頂點緩沖,通常情況下,OpenGL對象都是用一個GLuint類型的變量表示。
glGenBuffers(1, &VBO);
在OpenGL中,都是通過glGen*類型的函數產生各種各樣的對象,這類函數有2個參數,第一個參數指定你要創建對象的數量,第二個參數是一個GLuint類型數組的地址,該地址中存放着driver分配給你的各種對象句柄。driver會保證將來再調用該函數時不會產生相同的對象句柄,除非你調用函數glDeleteBuffers顯示刪除對象句柄。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
在OpenGL中,通常把一個對象綁定到一個target name,比如把VBO對象綁定到GL_ARRAY_BUFFER(表示緩沖是頂點數組,另一個常用的target name是GL_ELEMENT_ARRAY_BUFFER,表示索引數組),然后在這個target name上執行命令,這些命令將會一直影響綁定的對象,除非我們把該target name綁定到一個新的對象,此時,在target name上執行命令,將會影響新綁定的對象。
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
綁定對象后,我們開始准備頂點緩沖數據,上面的函數中,我們用Vertices中的數據填充頂點緩沖,其中GL_STATIC_DRAW表示,這些頂點數據渲染過程中不會改變,相對應的是GL_DYNAMIC_DRAW,driver會根據這些狀態對程序進行一定的優化。
glEnableVertexAttribArray(0);
在后面的shader程序中,我們將看到更詳細的頂點屬性介紹,在本教程的例子中,我們沒有使用shader,但是我們在頂點緩沖中裝入了頂點位置,位置屬性就作為頂點屬性的index 0(注意:由於沒有shader,我們此時用的是固定管線渲染),所以必須打開它,否則不能使用,因為所有的頂點屬性在使用前都必須打開。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
再一次綁定頂點緩沖,當然也可以不綁定,因為我們只有一個緩沖,前面已經綁定過了,但是在大型3D程序中,可能有很多頂點緩沖,就需要在渲染前,根據實際需要,切換綁定不同的頂點緩沖對象。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
這個函數告訴管線,怎么解釋頂點緩沖中的數據。第一個參數指定屬性的索引,第二個參數是屬性的數量(3,表示,x,y,z),第三個參數是屬性的數據類型,第四個參數是屬性是否是歸一化的,第五個參數是stride,表示兩個連續屬性實例之間的字節數目,如果值為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。最后一個參數offset,就是該屬性從第幾個字節開始,如果有2個屬性,那么對於第一個屬性,該值為0,沒有偏移,對於第二個屬性為12,因為第一個屬性有12byte,偏移12byte后的位置即為第二個屬性。
OpenGL函數。 void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,const GLvoid * pointer); 參數: index 指定要修改的頂點屬性的索引值 size 指定每個頂點屬性的組件數量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a)) type 指定數組中每個組件的數據類型。可用的符號常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值為GL_FLOAT。 normalized 指定當被訪問時,固定點數據值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE)。 stride 指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。 pointer 指定第一個組件在數組的第一個頂點屬性中的偏移量。該數組與GL_ARRAY_BUFFER綁定,儲存於緩沖區中。初始值為0;
glDrawArrays(GL_POINTS, 0, 1);
最后,我們調用draw函數,該函數是GPU開始工作的起始點,GPU將綁定draw函數的參數以及狀態等數據,然后通過driver傳遞到GPU。OpenGL有幾種形式的draw函數,通常它們可以划分為ordered draws和indexed draws。ordered draw函數就是把指定的頂點按語義順序畫一遍,比如你指定GL_TRIANGLES ,則0-2頂點為第一個三角形,3-5頂點為第二個三角形等等。而Index draws要通過索引緩沖來索引頂點數據(實際上,在硬件層次,沒有無索引的draw實現,如果沒有指定索引緩沖,硬件會按照頂點的順序,生成一個和頂點順序對應的索引),這樣可以重復利用頂點數據,比如我們可以用四個點來表示一個四邊形(2個三角形組成),其中2個點是2個三角形共享的,索引緩沖中的數據為頂點在頂點緩沖中的位置。
我們調用DrawArrays函數畫一個點,第一個參數指定體元語義,畫的是點,第二個參數是第一個頂點的索引位置,第三個參數是要渲染的頂點的數量。
glDisableVertexAttribArray(0);
不使用頂點屬性的時候,記得要關閉它,以免引起未知的一些錯誤。
程序執行后,界面如下,在窗口中心,有一個小的幾乎看不見的白點。
我們也可以加入下面的代碼,設置點的大小以及設置點為圓點。
//下面四行代碼,設置點的大小,以及啟動alpha blend以及多采樣,這樣可以畫圓形的點
glEnable( GL_POINT_SMOOTH );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glPointSize(8.0f);