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是按照什麼樣的流程來進行工作的呢?下面的圖片可以簡要的說明一下:

聲明:該圖片來自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的工作流程,輸入像素數據和頂點數據,兩種數據分別操作後,通過光柵化,得到片段,再經過片段處理,最後繪製到幀緩衝區。繪製的結果也可以逆方向傳送,最終轉化爲像素數據。



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

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