1. 程式人生 > >OpenGL入門學習(十三) 【轉】

OpenGL入門學習(十三) 【轉】

 前一段時間裡,論壇有位朋友問什麼是狀態機。按我的理解,狀態機就是一種存在於理論中的機器,它具有以下的特點:
1. 它有記憶的能力,能夠記住自己當前的狀態。
2. 它可以接收輸入,根據輸入的內容和自己的狀態,修改自己的狀態,並且可以得到輸出。
3. 當它進入某個特殊的狀態(停機狀態)的時候,它不再接收輸入,停止工作。


理論說起來很抽象,但實際上是很好理解的。
首先,從本質上講,我們現在的電腦就是典型的狀態機。可以對照理解:
1. 電腦的儲存器(記憶體、硬碟等等),可以記住電腦自己當前的狀態(當前安裝在電腦中的軟體、儲存在電腦中的資料,其實都是二進位制的值,都屬於當前的狀態)。
2. 電腦的輸入裝置接收輸入(鍵盤輸入、滑鼠輸入、檔案輸入),根據輸入的內容和自己的狀態(主要指可以執行的程式程式碼),修改自己的狀態(修改記憶體中的值),並且可以得到輸出(將結果顯示到螢幕)。
3. 當它進入某個特殊的狀態(關機狀態)的時候,它不再接收輸入,停止工作。

OpenGL也可以看成這樣的一種機器。讓我們先對照理解一下:
1. OpenGL可以記錄自己的狀態(比如:當前所使用的顏色、是否開啟了混合功能,等等,這些都是要記錄的)
2. OpenGL可以接收輸入(當我們呼叫OpenGL函式的時候,實際上可以看成OpenGL在接收我們的輸入),根據輸入的內容和自己的狀態,修改自己的狀態,並且可以得到輸出(比如我們呼叫glColor3f,則OpenGL接收到這個輸入後會修改自己的“當前顏色”這個狀態;我們呼叫glRectf,則OpenGL會輸出一個矩形)
3. OpenGL可以進入停止狀態,不再接收輸入。這個可能在我們的程式中表現得不太明顯,不過在程式退出前,OpenGL總會先停止工作的。

還是沒理解?呵呵,看來這真不是個好的開始呀,難得等了這麼久,好不容易教程有更新了,怎麼如此的難懂啊??沒關係,實在沒理解,咱就不理解它了。接著往下看。
為什麼我要提到“狀態機”這個枯燥的、晦澀的概念呢?其實它可以幫助我們理解一些東西。
比如我在前面的教程裡面,經常說:
可以使用glColor*函式來選擇一種顏色,以後繪製的所有物體都是這種顏色,除非再次使用glColor*函式重新設定。
可以使用glTexCoord*函式來設定一個紋理座標,以後繪製的所有物體都是採用這種紋理座標,除非再次使用glTexCoord*函式重新設定。
可以使用glBlendFunc函式來指定混合功能的源因子和目標因子,以後繪製的所有物體都是採用這個源因子和目標因子,除非再次使用glBlendFunc函式重新指定。
可以使用glLight*函式來指定光源的位置、顏色,以後繪製的所有物體都是採用這個光源的位置、顏色,除非再次使用glBlendFunc函式重新指定。
……
呵呵,很繁,是吧?“狀態機”可以簡化這個描述。
OpenGL是一個狀態機,它保持自身的狀態,除非使用者輸入一條命令讓它改變狀態。

顏色、紋理座標、源因子和目標因子、光源的各種引數,等等,這些都是狀態,所以這一句話就包含了上面敘述的所有內容。
此外,“是否啟用了光照”、“是否啟用了紋理”、“是否啟用了混合”、“是否啟用了深度測試”等等,這些也都是狀態,也符合上面的描述:OpenGL會保持狀態,除非我們呼叫OpenGL函式來改變它。
取得OpenGL的當前狀態
OpenGL儲存了自己的狀態,我們可以通過一些函式來取得這些狀態。
首先來說一些啟用/禁用的狀態。
我們通過glEnable來啟用狀態,通過glDisable來禁用它們。例如:
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
可以用glIsEnabled函式來檢測這些狀態是否被開啟。例如:
glIsEnabled(GL_DEPTH_TEST);
glIsEnabled(GL_BLEND);
glIsEnabled(GL_CULL_FACE);
glIsEnabled(GL_LIGHTING);
glIsEnabled(GL_TEXTURE_2D);
如果狀態是開啟的,則glIsEnabled函式返回GL_TRUE(這是一個不為零的常量,一般被定義為1);否則返回GL_FALSE(這是一個常量,其值為零)
我們可以在程式裡面寫:
if
( glIsEnabled(GL_BLEND) ) {
     // 當前開啟了混合功能
} else {
     // 當前沒有開啟混合功能
}

再看其它型別的狀態。
比如當前顏色,其值是四個浮點數,當前設定的直線寬度,其值是一個浮點數,當前的視口(Viewport,參見第五課),其值是四個整數。
為了取得整數型別、浮點數型別的狀態,OpenGL提供了glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev這四個函式。呼叫函式時,指定需要得到的狀態的名稱,以及需要將狀態值存放到的位置(一個指標),則這四個函式可以把狀態值存放到指標所值位置。例如:
// 取得當前的直線寬度
GLfloat lw;
glGetFloatv(GL_LINE_WIDTH, &lw);
// 取得當前的顏色
GLfloat cc[4];
glGetFloatv(GL_CURRENT_COLOR, cc);
// 取得當前的視口
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);

說明:
1. 注意元素的個數。比如GL_LINE_WIDTH狀態只有一個值,而GL_CURRENT_COLOR有四個值。應該小心的定義變數或者陣列,避免下標越界。
2. 使用四個不同的函式,同一種狀態也可以返回為不同型別的值。比如要得到當前的顏色,一般可以返回GLfloat型別或者GLdouble型別。程式碼如下:
GLfloat cc[4];
GLdouble dcc[4];
glGetFloatv(GL_CURRENT_COLOR, cc);
glGetDoublev(GL_CURRENT_COLOR, dcc);

glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev這四個函式可以得到OpenGL中多數的狀態,但是還有一些狀態不便用這四個函式來取得。比如光源的狀態,因為可能有多個光源,所以不可能使用類似glGetFloatv(GL_LIGHT_POSITION, pos);這樣的方法來得到光源位置。為了解決這個問題,OpenGL專門提供了glGetLight*系列函式,來取得光源的狀態。
類似的,還有glGetMaterial*, glGetTexParameter*等,每個函式都有自己的適用範圍。
設定OpenGL狀態
呵呵,讀者可能會有疑問。既然有getXXX這樣的函式來取得OpenGL的狀態,那麼為什麼沒有setXXX這樣的函式來設定OpenGL狀態呢?
答案很簡單,因為OpenGL已經提供了大量的函式來設定狀態了:glColor*, glMaterial*, glEnable, glDisable, 等等,大多數OpenGL函式都是用來設定OpenGL狀態的,因此不需要再設計一個setXXX函式來設定OpenGL狀態。
從“狀態機”的角度來看。狀態機根據輸入來修改自己的狀態,而不是由外界直接修改自己的狀態。所以不設定setXXX這樣的函式,也是很合理的。

OpenGL工作流程
教程都放到第十三課了,但是我一直沒有對“工作流程”這種東西做過說明。OpenGL是按照什麼樣的流程來進行工作的呢?下面的圖片可以簡要的說明一下:
http://blog.programfan.com/upfile/200805/20080505132559.gif
宣告:該圖片來自www.opengl.org,該圖片是《OpenGL程式設計指南》一書的附圖,由於該書的舊版(第一版,1994年)已經流傳於網路,我希望沒有觸及到版權問題。
因為圖片中的文字是英語,這裡還翻譯一下。說明文字也夾雜在翻譯之中了。
1. Vertex data: 頂點資料。比如我們指定的顏色、紋理座標、法線向量、頂點座標等,都屬於頂點資料。
2. Pixel data: 畫素資料。我們在繪製畫素、指定紋理時都會用到畫素資料。
3. Display list: 顯示列表。可以把呼叫的OpenGL函式儲存起來。(參見第八課)
4. Evaluators: 求值器。這個我們在前面的課程中沒有提到,以後估計也不太會提到。利用求值器可以指定貝賽爾曲線或者貝賽爾曲面,但是實際上還是可以理解為指定頂點、指定紋理座標、指定法線向量等。
5. Per-vertex operations and primitive assembly: 單一的頂點操作以及圖元裝配。首先對單一的頂點進行操作,比如變換(參見第五課)。然後把頂點裝配為圖元(圖元就是OpenGL所能繪製的最簡單的圖形,比如點、線段、三角形、四邊形、多邊形等,參見第二課)
6. Pixel operations: 畫素操作。例如把記憶體中的畫素資料格式轉化為圖形硬體所支援的資料格式。對於紋理,可以替換其中的一部分畫素,這也屬於畫素操作。
7. Rasterization: 光柵化。頂點資料和畫素資料在這裡交匯(可以想像成:頂點和紋理,一起組合成了具有紋理的三角形),形成完整的、可以顯示的一整塊(可能是點、線段、三角形、四邊形,或者其它不規則圖形),裡面包含若干個畫素。這一整塊被稱為fragment(片段)。
8. Per-fragment operations: 片段操作。包括各種片段測試(參見第十二課)。
9. Framebuffer: 幀緩衝。這是一塊儲存空間,顯示裝置從這裡讀取資料,然後顯示到螢幕。
10. Texture assembly: 紋理裝配,這裡我也沒怎麼弄清楚:(,大概是說紋理的操作和畫素操作是相關的吧。
說明:圖片中實線表示正常的處理流程,虛線表示資料可以反方向讀取,比如可以用glReadPixels從幀緩衝中讀取畫素資料(實際上是從幀緩衝讀取資料,經過畫素操作,把顯示裝置中的畫素資料格式轉化為記憶體中的畫素資料格式,最終成為記憶體中的畫素資料)。
小結

本課是枯燥的理論知識。
OpenGL是一個狀態機,它維持自己的狀態,並根據使用者呼叫的函式來改變自己的狀態。根據狀態的不同,呼叫同樣的函式也可能產生不同的效果。
可以通過一些函式來獲取OpenGL當前的狀態。常用的函式有:glIsEnabled, glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev。
OpenGL的工作流程,輸入畫素資料和頂點資料,兩種資料分別操作後,通過光柵化,得到片段,再經過片段處理,最後繪製到幀緩衝區。繪製的結果也可以逆方向傳送,最終轉化為畫素資料。