使用 glew
glew 全稱是 OpengGL Extension Wrangler Library,它能夠幫忙解決 OpengGL 不斷擴充的問題。初始化 glew 之後,它将查詢系統上所有可用的擴充功能并自動加載它們,然後提供一個頭檔案作為接口,我們直接通過頭檔案就可以使用這些擴充功能。
glew 的下載下傳位址
http://glew.sourceforge.net/
下載下傳之後進行解壓,得到的目錄結構
|-glew-2.1.0
|-bin
|-lib
|-include
|-doc
接下來開始配置環境,配置的方式有兩種,第一種方式跟配置 glut 一樣,系統級的配置,配置之後所有的 opengl 項目都不需要配置
- 第一步,把 include 目錄下的頭檔案放在 %VISUAL_STUDIO%\VC\include\gl 目錄下
- 第二步,把 lib 目錄下的 lib 檔案放在 %VISUAL_STUDIO%\VC\lib 目錄下,然後在項目屬性的 連結器 –> 輸入 添加相應庫的引用
- 第三步,把 bin 目錄下的 dll 檔案放在 system32 目錄下,同樣的 64 位系統要放在 sysWOW64 目錄下
這種方式雖然能夠一勞永逸,但因為 win32 和 x64 的庫檔案名一樣,是以無法做到相容,即無法同時把 32 位的庫檔案和 64 位的庫拷到系統路徑;另外過多地把庫檔案拷到系統路徑也不好,是以推薦使用第二種方式,即給每個建立的項目配置環境
- 把頭檔案放在 $PROJECT_ROOT%\include\GL 目錄下,然後在項目屬性的 C/C++ –> 附加包含目錄 中添加 .\include
- 把靜态庫 lib 檔案放在 $PROJECT_ROOT%\lib 目錄下,然後在項目屬性的 連結器 –> 正常 –> 附加庫目錄 中添加 .\lib,在 連結器 –> 輸入 中添加相應庫的引用
- 把動态庫 dll 檔案放在可執行程式 exe 同級目錄下
如果一個項目想同時編譯 32 位和 64 位,則可以分别把 32 位的 lib 檔案和 64 位的 lib 檔案放在 .\lib\win32 和 .\lib\x64 目錄下,然後分别修改附加庫目錄,再把相應的 dll 檔案拷到編譯後的 32 位程式和 64 位程式目錄下。
GL context
在開始繪制圖形之前,我們必須先了解 GL context 和 GL objects 這兩個重要概念,參考文檔 https://www.khronos.org/opengl/wiki/Main_Page
上一篇文章講到 OpenGL 渲染是基于狀态(state)的。OpenGL context 是一個重要的概念的,隻有建立了 context,OpenGL 才存在,context 一旦被銷毀了,OpenGL 就不存在了。context 存儲了一個 OpenGL 執行個體的所有狀态,類似于一個程式開辟的所有記憶體空間。context 可以看作程序在作業系統中的一個執行過程,一個程序可以建立多個 context,每一個 context 代表一個可視面,就像一個應用程式的一個界面一樣。
簡單來講,context 儲存了一個 OpenGL 執行個體的所有狀态,在使用 OpenGL 之前必須先建立一個 context。
int main(int argc, char **argv)
{
char *GL_version=(char *)glGetString(GL_VERSION);
char *GL_vendor=(char *)glGetString(GL_VENDOR);
char *GL_renderer=(char *)glGetString(GL_RENDERER);
return ;
}
這裡是想擷取一些系統資訊,但得到的結果卻全是空,這裡因為此時還沒有建立 context,OpenGL 相當于不存在。
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("Create Dot");
//擷取 OpenGL 版本号
char *GL_version = (char *)glGetString(GL_VERSION);
//擷取本機提供 GL 支援的公司
char *GL_vendor = (char *)glGetString(GL_VENDOR);
//擷取渲染器的名稱
char *GL_renderer = (char *)glGetString(GL_RENDERER);
//擷取着色器的版本号
char* GL_shader_version = (char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
//擷取本機硬體支援的最大頂點屬性數
GLint max_vertex_attrib;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attrib);
return ;
}
這樣就可以正常擷取資訊了,因為
glutCreateWindow
建立一個視窗的同時就相當于已經建立了一個 context。
GL objects
再重申一遍,OpenGL 基于狀态來渲染,可以說 OpenGL 被定義成“狀态機”,所有的 API 都是修改狀态、查詢狀态或者使用狀态來渲染。GL Object 是一些狀态的集合,這點看起來跟 GL Context 有點像,也可以這樣類比,但要知道 object 和 context 的 state 是互相獨立的,context 有一套狀态,每個 object 也會有自己的一些狀态。隻有把 object 綁定到 context 上,它的狀态都會映射到 context 上,綁定之後修改 context 的狀态,object 也會受影響;相反基于 context 狀态的函數也可以使用 object 的狀态。
對象可以分成兩大類,regular objects 和 container objects。
regular object 包括 Buffer Objects,Query Objects,Renderbuffer Objects,Sampler Objects,Texture Objects。
container objects 包括 Vertex Array Objects,Framebuffer Objects,Program Pipeline Objects,Transform Feedback Objects。
對象建立、銷毀和綁定
使用 glGen* 函數給對象生成一個名字,就是建立對象了。
void glGen*(GLsizei n, GLuint *objects);
這個函數可以同時建立多個對象,隻需要給定一個對象數組的指針;對象名是 Glsizei 類型,也就是一個 32 位無符号整型,這個整數并不是一個指針,隻是對象的一個引用,用于辨別這個對象。另外,整數 0 這個名字用于特殊對象,是以我們給對象起名時隻能從 1 開始。
void glDelete*(GLsizei n, const GLuint *objects);
這個函數用于銷毀對象,同樣如果傳進來的 object 是一個數組的話,可以批量銷毀多個對象。關于對象銷毀有幾點要注意的
- 如果對象已經綁定到 context ,則對象銷毀後會自動解綁;但如果對象附加到另一個對象上,則這我種附加關系不會解除
- 對象被 delete 之後并不會立即删除,它的名字還可以使用,但請不要用
void glBind*(GLenum target, GLuint object);
這個函數用于把對象綁定到 context。target 指明了對象的類型,有些對象可以綁定為多個類型,比如一個 buffer obejct 可以綁定為 array buffer,index buffer,pixel buffer,transform buffer 或者其它。
VAO && VBO
我們知道了 OpenGL 是一個狀态機,使用 object 來儲存資料和狀态,钴綁定到 context,将 object 的狀态和 context 的狀态關聯起來,然後使用 context 的狀态進行渲染。接下來就着手準備繪制一個點,這裡需要用到兩個對象 VBO 和 VAO。
VAO 即 Vertex Array Object,它儲存了所有頂點資料的狀态,并沒有儲存頂點資料,而是儲存了頂點資料的格式和所需 buffer 對象的引用。每一個狀态屬性都是可以開啟和關閉的,使用下面兩個函數
void glEnableVertexAttribArray(GLuint index);
void glDisableVertexAttribArray(GLuint index);
VBO,即 Vertex Buffer Object,在緩存區儲存的就是頂點資料了。為了讓 VAO 能使用 VBO 的資料,我們需要告訴 OpenGL,編号為 index 屬性使用目前綁定到 GL_ARRAY_BUFFER 的 VBO
index 是第幾個屬性,像頂點的第 0 個屬性就是位置;size 指定構成屬性的分量個數,像頂點位置由 x,y,z 三個分量組成,是以 size=3;type 指定屬性值的類型,像頂點位置為 GL_FLOAT;normalized 指屬性在管線中使用之前是否需要被規範化;stride 指兩個相同屬性值之間間隔的位元組數,隻有一個屬性時間隔為 0;pointer 指存儲資料的偏移值,同樣隻有一個屬性時偏移值為 0。
繪制一個點
#include <gl/glew.h>
#include <gl/glut.h>
#include <math3d.h>
#include <iostream>
using namespace std;
void init();
void renderPerFrame();
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("Create Dot");
char *GL_version = (char *)glGetString(GL_VERSION);
char *GL_vendor = (char *)glGetString(GL_VENDOR);
char *GL_renderer = (char *)glGetString(GL_RENDERER);
GLenum res = glewInit();
if (GLEW_OK != res)
{
cout << "glewInit failed: " << glewGetErrorString(res) << endl;
system("pause");
return ;
}
init();
glutDisplayFunc(renderPerFrame);
glClearColor(, , , );
glutMainLoop();
}
void init()
{
//建立 buffer 對象
GLuint VBO;
glGenBuffers(, &VBO);
//綁定 buffer 對象
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//定義資料
M3DVector3f vertices[];
vertices[][] = ;
vertices[][] = ;
vertices[][] = ;
//填充 buffer 的值
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
void renderPerFrame()
{
glClear(GL_COLOR_BUFFER_BIT);
//開啟頂點屬性
glEnableVertexAttribArray();
//指定屬性使用的 buffer
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , );
//繪制頂點
glDrawArrays(GL_POINTS, , );
//關閉頂點屬性
glDisableVertexAttribArray();
glutSwapBuffers();
}
總結一下這個過程:
首先,建立頂點緩沖對象,即 VBO;然後将頂點緩沖對象綁定到 context;最後給頂點緩沖對象賦頂點資料。
經過上面三步之後,在 context 中就已經有了一個頂點緩沖對象,這個對象儲存了頂點資料。接下來就是在主回調函數中取頂點資料進行繪制,這裡使用的是固定管線,是以不用 VAO,後面主要使用可程式設計管線。
繪制的時候是基于狀态的,要繪制頂點,必須知道每個狀态屬性從哪裡取資料,即把頂點對象(VAO)和頂點緩沖對象(VBO)關聯起來。首先,要開啟頂點對象的某個屬性;然後指定該屬性使用的 buffer 對象;然後開始繪制;最後關閉頂點對象屬性。