OpenGL入門學習(十四)

OpenGL入門學習[十四]


OpenGL從推出到現在,已經有相當長的一段時間了。其間,OpenGL不斷的得到更新。到今天爲止,正式的OpenGL已經有九個版本。(1.0, 1.1, 1.2, 1.2.1, 1.3, 1.4, 1.5, 2.0, 2.1)
每個OpenGL版本的推出,都增加了一些當時流行的或者迫切需要的新功能。同時,到現在爲止,OpenGL是向下兼容的,就是說如果某個功能在一個低版本中存在,則在更高版本中也一定存在。這一特性也爲我們編程提供了一點方便。
當前OpenGL的最新版本是OpenGL 2.1,但是並不是所有的計算機系統都有這樣最新版本的OpenGL實現。舉例來說,Windows系統如果沒有安裝顯卡驅動,或者顯卡驅動中沒有附帶OpenGL,則Windows系統默認提供一個軟件實現的OpenGL,它沒有使用硬件加速,因此速度可能較慢,版本也很低,僅支持1.1版本(聽說Windows Vista默認提供的OpenGL支持到1.4版本,我也不太清楚)。nVidia和ATI這樣的顯卡巨頭,其主流顯卡基本上都提供了對OpenGL 2.1的支持。但一些舊型號的顯卡因爲性能不足等原因,只能支持到OpenGL 2.0或者OpenGL 1.5。Intel的集成顯卡,很多都只提供了OpenGL 1.4(據說目前也有更高版本的了,但是我沒有見到)。
OpenGL 2.0是一次比較大的改動,也因此升級了主版本號。可以認爲OpenGL 2.0版本是一個分水嶺,是否支持OpenGL 2.0版本,直接關係到運行OpenGL程序時的效果。如果要類比一下的話,我覺得OpenGL 1.5和OpenGL 2.0的差距,就像是DirectX 8.1和DirectX 9.0c的差距了。
檢查自己的OpenGL版本
可以很容易的知道自己系統中的OpenGL版本,方法就是調用glGetString函數。 

const char* version = (const char*)glGetString(GL_VERSION);
printf("OpenGL 版本:%s\n", version);



glGetString(GL_VERSION);會返回一個表示版本的字符串,字符串的格式爲X.X.X,就是三個整數,用小數點隔開,第一個數表示OpenGL主版本號,第二個數表示OpenGL次版本號,第三個數表示廠商發行代號。比如我在運行時得到的是"2.0.1",這表示我的OpenGL版本爲2.0(主版本號爲2,次版本號爲0),是廠商的第一個發行版本。
通過sscanf函數,也可以把字符串分成三個整數,以便詳細的進行判斷。 

int main_version, sub_version, release_version;
const char* version = (const char*)glGetString(GL_VERSION);
sscanf(version, "%d.%d.%d", &main_version, &sub_version, &release_version);
printf("OpenGL 版本:%s\n", version);
printf("主版本號:%d\n", main_version);
printf("次版本號:%d\n", sub_version);
printf("發行版本號:%d\n", release_version);



glGetString還可以取得其它的字符串。
glGetString(GL_VENDOR); 返回OpenGL的提供廠商。
glGetString(GL_RENDERER); 返回執行OpenGL渲染的設備,通常就是顯卡的名字。
glGetString(GL_EXTENSIONS); 返回所支持的所有擴展,每兩個擴展之間用空格隔開。詳細情況參見下面的關於“OpenGL擴展”的敘述。
版本簡要歷史
版本不同,提供功能的多少就不同。這裏列出每個OpenGL版本推出時,所增加的主要功能。當然每個版本的修改並不只是下面的內容,讀者如果需要知道更詳細的情形,可以查閱OpenGL標準。
OpenGL 1.1
頂點數組。把所有的頂點數據(顏色、紋理座標、頂點座標等)都放到數組中,可以大大的減少諸如glColor*, glVertex*等函數的調用次數。雖然顯示列表也可以減少這些函數的調用次數,但是顯示列表中的數據是不可以修改的,頂點數組中的數據則可以修改。
紋理對象。把紋理作爲對象來管理,同一時間OpenGL可以保存多個紋理(但只使用其中一個)。以前沒有紋理對象時,OpenGL只能保存一個“當前紋理”。要使用其它紋理時,只能拋棄當前的紋理,重新載入。原來的方式非常影響效率。
OpenGL 1.2
三維紋理。以前的OpenGL只支持一維、二維紋理。
像素格式。新增加了GL_BGRA等原來沒有的像素格式。允許壓縮的像素格式,例如GL_UNSIGNED_SHORT_5_5_5_1格式,表示兩個字節,存放RGBA數據,其中R, G, B各佔5個二進制位,A佔一個二進制位。
圖像處理。新增了一個“圖像處理子集”,提供一些圖像處理的專用功能,例如卷積、計算柱狀圖等。這個子集雖然是標準規定,但是OpenGL實現時也可以選擇不支持它。
OpenGL 1.2.1
沒有加入任何新的功能。但是引入了“ARB擴展”的概念。詳細情況參見下面的關於“OpenGL擴展”的敘述。
OpenGL 1.3
壓縮紋理。在處理紋理時,使用壓縮後的紋理而不是紋理本身,這樣可以節省空間(節省顯存)和傳輸帶寬(節省從內存到顯存的數據流量)
多重紋理。同時使用多個紋理。
多重採樣。一種全屏抗鋸齒技術,使用後可以讓畫面顯示更加平滑,減輕鋸齒現象。對於nvidia顯卡,在設置時有一項“3D平滑處理設置”,實際上就是多重採樣。通常可以選擇2x, 4x,高性能的顯卡也可以選擇8x, 16x。其它顯卡也幾乎都有類似的設置選項,但是也有的顯卡不支持多重採樣,所以是0x。
OpenGL 1.4
深度紋理。可以把深度值像像素值一樣放到紋理中,在繪製陰影時特別有用。
輔助顏色。頂點除了有顏色外還有輔助顏色。在使用光照時可以表現出更真實的效果。
OpenGL 1.5
緩衝對象。允許把數據(主要指頂點數據)交由OpenGL保存到較高性能的存儲器中,提高繪製速度。比頂點數組有更多優勢。頂點數組只是減少函數調用次數,緩衝對象不僅減少函數調用次數,還加快數據訪問速度。
遮擋查詢。可以計算一個物體有幾個像素會被繪製到屏幕上。如果物體沒有任何像素會被繪製,則不需要加載相關的數據(例如紋理數據)。
OpenGL 2.0
可編程着色。允許編寫一小段代碼來代替OpenGL原來的頂點操作/片段操作。這樣提供了巨大的靈活性,可以實現各種各樣的豐富的效果。
紋理大小不再必須是2的整數次方。
點塊紋理。把紋理應用到一個點(大小可能不只一個像素)上,這樣比繪製一個矩形可能效率更高。
OpenGL 2.1
可編程着色,編程語言由原來的1.0版本升級爲1.2版本。
緩衝對象,原來僅允許存放頂點數據,現在也允許存放像素數據。
獲得新版本的OpenGL
要獲得新版本OpenGL,首先應該登陸你的顯卡廠商網站,並查詢相關的最新信息。根據情況,下載最新的驅動或者OpenGL軟件包。
如果自己的顯卡不支持高版本的OpenGL,或者自己的操作系統根本就沒有提供OpenGL,怎麼辦呢?有一個被稱爲MESA的開源項目,用C語言編寫了一個OpenGL實現,最新的mesa 7.0已經實現了OpenGL 2.1標準中所規定的各種功能。下載MESA的代碼,然後編譯,就可以得到一個最新版本的OpenGL了。呵呵,不要高興的太早。MESA是軟件實現的,就是說沒有用到硬件加速,因此運行起來會較慢,尤其是使用新版本的OpenGL所規定的一些高級特性時,慢得幾乎無法忍受。MESA不能讓你用舊的顯卡玩新的遊戲(很可能慢得沒法玩),但是如果你只是想學習或嘗試一下新版本OpenGL的各種功能,MESA可以滿足你的一部分要求。
OpenGL擴展
OpenGL版本的更新並不快。如果某種技術變得流行起來,但是OpenGL標準中又沒有相關的規定對這種技術提供支持,那就只能通過擴展來實現了。
廠商在發行OpenGL時,除了遵照OpenGL標準,提供標準所規定的各種功能外,往往還提供其它一些額外的功能,這就是擴展。
擴展的存在,使得各種新的技術可以迅速的被應用到OpenGL中。比如“多重紋理”,它是在OpenGL 1.3中才被加入到標準中的,在OpenGL 1.3出現以前,很多OpenGL實現都通過擴展來支持“多重紋理”。這樣,即使OpenGL版本不更新,只要增加新的擴展,也可以提供新的功能了。這也說明,即使OpenGL版本較低,也不一定不支持一些高版本OpenGL才提供的功能。實際上某些OpenGL 1.5的實現,也可能提供了最新的OpenGL 2.1版本所規定的大部分功能。
當然擴展也有缺點,那就是程序在運行的時候必須檢查每個擴展功能是否被支持,導致編寫程序代碼複雜。

擴展的名字
每個OpenGL擴展,都必須向OpenGL的網站註冊,確認後才能成爲擴展。註冊後的擴展有編號和名字。編號僅僅是一個序號,名字則與擴展所提供的功能相關。
名字用下劃線分爲三部分。舉例來說,一個擴展的名字可能爲:GL_NV_half_float,其意義如下:
第一部分爲擴展的目標。比如GL表示這是一個OpenGL擴展。如果是WGL則表示這是一個針對Windows的OpenGL擴展,如果是GLX則表示這是一個針對linux的X Window系統的OpenGL擴展。
第二部分爲提供擴展的廠商。比如NV表示這是nVidia公司所提供的擴展。相應的還有ATI, IBM, SGI, APPLE, MESA等。
剩下的部分就表示擴展所提供的內容了。比如half_float,表示半精度的浮點數,每個浮點數的精度只有單精度浮點數的一半,因此只需要兩個字節就可以保存。這種擴展功能可以節省內存空間,也節省從內存到顯卡的數據傳輸量,代價就是精確度有所降低。
EXT擴展和ARB擴展
最初的時候,每個廠商都提供自己的擴展。這樣導致的結果就是,即使是提供相同的功能,不同的廠商卻提供不同的擴展,這樣在編寫程序的時候,使用一種功能就需要依次檢查每個可能支持這種功能的擴展,非常繁瑣。
於是出現了EXT擴展和ARB擴展。
EXT擴展是由多個廠商共同協商後形成的擴展,在擴展名字中,“提供擴展的廠商”一欄將不再是具體的廠商名,而是EXT三個字母。比如GL_EXT_bgra,就是一個EXT擴展。
ARB擴展不僅是由多個廠商共同協商形成,還需要經過OpenGL體系結構審覈委員會(即ARB)的確認。在擴展名字中,“提供擴展的廠商”一欄不再是具體的廠商名字,而是ARB三個字母。比如GL_ARB_imaging,就是一個ARB擴展。
通常,一種功能如果有多個廠商提出,則它成爲EXT擴展。在以後的時間裏,如果經過了ARB確認,則它成爲ARB擴展。再往後,如果OpenGL的維護者認爲這種功能需要加入到標準規定中,則它不再是擴展,而成爲標準的一部分。
例如point_parameters,就是先有GL_EXT_point_parameters,再有GL_ARB_point_parameters,最後到OpenGL 1.4版本時,這個功能爲標準規定必須提供的功能,不再是一個擴展。
在使用OpenGL所提供的功能時,應該按照標準功能、ARB擴展、EXT擴展、其它擴展這樣的優先順序。例如有ARB擴展支持這個功能時,就不使用EXT擴展。
在程序中,判斷OpenGL是否支持某個擴展
前面已經說過,glGetString(GL_EXTENSIONS)會返回當前OpenGL所支持的所有擴展的名字,中間用空格分開,這就是我們判斷是否支持某個擴展的依據。 

#include <string.h>
// 判斷OpenGL是否支持某個指定的擴展
// 若支持,返回1。否則返回0。
int hasExtension(const char* name) {
    const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
    const char* end = extensions + strlen(extensions);
    size_t name_length = strlen(name);
    while( extensions < end ) {
        size_t position = strchr(extensions, ' ') - extensions;
        if( position == name_length &&
                strncmp(extensions, name, position) == 0 )
            return 1;
         extensions += (position + 1);
     }
    return 0;
}



上面這段代碼,判斷了OpenGL是否支持指定的擴展,可以看到,判斷時完全是靠字符串處理來實現的。循環檢測,找到第一個空格,然後比較空格之前的字符串是否與指定的名字一致。若一致,說明擴展是被支持的;否則,繼續比較。若所有內容都比較完,則說明擴展不被支持。
編寫程序調用擴展的功能
擴展的函數、常量,在命名時與通常的OpenGL函數、常量有少許區別。那就是擴展的函數、常量將以廠商的名字作爲後綴。
比如ARB擴展,所有ARB擴展的函數,函數名都以ARB結尾,常量名都以_ARB結尾。例如:
glGenBufferARB(函數)
GL_ARRAY_BUFFER_ARB(常量)
如果已經知道OpenGL支持某個擴展,則如何調用擴展中的函數?大致的思路就是利用函數指針。但是不幸的是,在不同的操作系統中,取得這些函數指針的方法各不相同。爲了能夠在各個操作系統中都能順利的使用擴展,我向大家介紹一個小巧的工具:GLEE。
GLEE是一個開放源代碼的項目,可以從網絡上搜索並下載。其代碼由兩個文件組成,一個是GLee.c,一個是GLee.h。把兩個文件都放到自己的源代碼一起編譯,運行的時候,GLee可以自動的判斷所有擴展是否被支持,如果支持,GLEE會自動讀取對應的函數,供我們調用。
我們自己編寫代碼時,需要首先包含GLee.h,然後才包含GL/glut.h(注意順序不能調換),然後就可以方便的使用各種擴展功能了。

#include "GLee.h"
#include <GL/glut.h> // 注意順序,GLee.h要在glut.h之前使用



GLEE也可以幫助我們判斷OpenGL是否支持某個擴展,因此有了GLEE,前面那個判斷是否支持擴展的函數就不太必要了。
示例代碼
讓我們用一段示例代碼結束本課。
我們選擇一個目前絕大多數顯卡都支持的擴展GL_ARB_window_pos,來說明如何使用GLEE來調用OpenGL擴展功能。通常我們在繪製像素時,需要用glRasterPos*函數來指定繪製的位置。但是,glRasterPos*函數使用的不是屏幕座標,例如指定(0, 0)不一定是左下角,這個座標需要經過各種變換(參見第五課,變換),最後纔得到屏幕上的窗口位置。
通過GL_ARB_window_pos擴展,我們可以直接用屏幕上的座標來指定繪製的位置,不再需要經過變換,這樣在很多場合會顯得簡單。 

#include "GLee.h"
#include <GL/glut.h>

void display(void) {
     glClear(GL_COLOR_BUFFER_BIT);

    if( GLEE_ARB_window_pos ) { // 如果支持GL_ARB_window_pos
                                 // 則使用glWindowPos2iARB函數,指定繪製位置
        printf("支持GL_ARB_window_pos\n");
        printf("使用glWindowPos函數\n");
         glWindowPos2iARB(100, 100);
     } else {                     // 如果不支持GL_ARB_window_pos
                                 // 則只能使用glRasterPos*系列函數
                                 // 先計算出一個經過變換後能夠得到
                                 //    (100, 100)的座標(x, y, z)
                                 // 然後調用glRasterPos3d(x, y, z);
         GLint viewport[4];
         GLdouble modelview[16], projection[16];
         GLdouble x, y, z;

        printf("不支持GL_ARB_window_pos\n");
        printf("使用glRasterPos函數\n");

         glGetIntegerv(GL_VIEWPORT, viewport);
         glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
         glGetDoublev(GL_PROJECTION_MATRIX, projection);
         gluUnProject(100, 100, 0.5, modelview, projection, viewport,
             &x, &y, &z);
         glRasterPos3d(x, y, z);
     }

     { // 繪製一個5*5的像素塊
         GLubyte pixels[5][5][4];
         // 把像素中的所有像素都設置爲紅色
        int i, j;
        for(i=0; i<5; ++i)
            for(j=0; j<5; ++j) {
                 pixels[i][j][0] = 255; // red
                 pixels[i][j][1] = 0;    // green
                 pixels[i][j][2] = 0;    // blue
                 pixels[i][j][3] = 255; // alpha
             }
         glDrawPixels(5, 5, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
     }

     glutSwapBuffers();
}

int main(int argc, char* argv[]) {
     glutInit(&argc, argv);
     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
     glutInitWindowPosition(100, 100);
     glutInitWindowSize(512, 512);
     glutCreateWindow("OpenGL");
     glutDisplayFunc(&display);
     glutMainLoop();
}



可以看到,使用了擴展以後,代碼會簡單得多了。不支持GL_ARB_window_pos擴展時必須使用較多的代碼才能實現的功能,使用GL_ARB_window_pos擴展後即可簡單的解決。
如果把代碼修改一下,不使用擴展而直接使用else裏面的代碼,可以發現運行效果是一樣的。
工具軟件
在課程的最後我還向大家介紹一個免費的工具軟件,這就是OpenGL Extension Viewer(各大軟件網站均有下載,請自己搜索之),目前較新的版本是3.0。
這個軟件可以查看自己計算機系統的OpenGL信息。包括OpenGL版本、提供廠商、設備名稱、所支持的擴展等。
軟件可以查看的信息很詳細,比如查看允許的最大紋理大小、最大光源數目等。
在查看擴展時,可以在最下面一欄輸入擴展的名字,按下回車後即可連接到OpenGL官方網站,查找關於這個擴展的詳細文檔,非常不錯。
可以根據電腦的配置情況,自動連接到對應的官方網站,方便下載最新驅動。(比如我是nVidia的顯卡,則連接到nVidia的驅動下載頁面)
可以進行OpenGL測試,看看運行起來性能如何。
可以給出總體報告,如果一些比較重要的功能不被支持,則會用粗體字標明。
軟件還帶有一個數據庫,可以查詢各廠商、各型號的顯卡對OpenGL各種擴展的支持情況。
小結

本課介紹了OpenGL版本和OpenGL擴展。
OpenGL從誕生到現在,經歷了1.0, 1.1, 1.2, 1.2.1, 1.3, 1.4, 1.5, 2.0, 2.1這些版本。
每個系統中的OpenGL版本可能不同。使用glGetString(GL_VERSION);可以查看當前的OpenGL版本。
新版本的OpenGL將兼容舊版本的OpenGL,同時提供更多的新特性和新功能。
OpenGL在實現時可以通過擴展,來提供額外的功能。
OpenGL擴展有廠家擴展、EXT擴展、ARB擴展。通常應該儘量使用標準功能,其次纔是ARB擴展、EXT擴展、廠家擴展。
GLEE是一個可以免費使用的工具,使用它可以方便的判斷當前的OpenGL是否支持某擴展,也可以方便的調用擴展。
OpenGL Extension Viewer是一個軟件,可以檢查系統所支持OpenGL的版本、支持的擴展、以及很多的詳細信息。


轉自http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章