天天看點

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

現在我們來深入探讨一下之前的第一個程式。

1.5.1 進入main()函數

為了了解示例程式從一開始是如何運作的,首先了解一下main()函數當中都發生了什麼。前面的6行使用GLFW設定和打開了一個渲染用的視窗。這方面的詳細介紹可以參見附錄A,這裡隻介紹每一行的執行結果。

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

第一個函數glfwtInit()負責初始化GLFW庫。它會處理向程式輸入的指令行參數,并且移除其中與控制GLFW如何操作相關的部分(例如設定視窗的大小)。glfwtInit()必須是應用程式調用的第一個GLFW函數,它會負責設定其他GLFW例程所必需的資料結構。

glfwCreateWindow()設定了程式所使用的視窗類型以及期望的視窗尺寸。如果我們不想在這裡設定一個固定值的話,也可以先查詢顯示裝置的尺寸,然後根據計算機的螢幕大小動态設定視窗的大小。

glfwCreateWindow()還建立了一個與視窗關聯的OpenGL裝置環境。在使用環境之前,我們必須設定它為目前環境。在一個程式中,我們可以設定多個裝置環境以及多個視窗,而使用者指令隻會傳遞到目前裝置環境中。

繼續讨論這個例子,接下來會調用gl3wInit()函數,它屬于我們用到的另一個輔助庫GL3W。GL3W可以簡化擷取函數位址的過程,并且包含了可以跨平台使用的其他一些OpenGL程式設計方法。如果沒有GL3W,我們可能還需要執行相當多的工作才能夠運作程式。

到這裡,我們已經完成了使用OpenGL之前的全部設定工作。在馬上要介紹的init()例程中,我們将初始化OpenGL相關的所有資料,以便完成之後的渲染工作。

main()函數中調用的最後一個指令是一個無限執行的循環,它會負責一直處理視窗和作業系統的使用者輸入等操作。在循環中我們會判斷是否需要關閉視窗(通過調用glfwWindowShouldClose()),重繪它的内容,并且展現給最終使用者(通過調用glfwSwapBuffers()),然後檢查作業系統傳回的任何資訊(通過調用glfwPollEvents())。

如果我們認為需要關閉視窗,應用程式需要退出的話,會調用glfwDestroyWindow()來清理視窗,然後調用glfwTerminate()關閉GLFW庫。

1.5.2 OpenGL的初始化過程

下面将要讨論例1.1中的init()函數。首先再次列出與之相關的代碼。

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析
《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

初始化頂點數組對象

在init()中使用了不少函數和資料。在函數的起始部分,我們調用glCreateVertexArrays()配置設定了頂點數組對象(vertex-array object)。OpenGL會是以配置設定一部分頂點數組對象的名稱供我們使用,在這裡共有NumVAOs個對象,即這個全局變量所指代的數值。glCreateVertexArrays()的第二個參數傳回的是對象名的數組,也就是這裡的VAOs。

我們對glCreateVertexArrays()函數的完整解釋如下:

傳回n個未使用的對象名到數組arrays中,用作頂點數組對象。傳回的名字可以用來配置設定更多的緩存對象,并且它們已經使用未初始化的頂點數組集合的預設狀态進行了數值的初始化。如果n是負數,産生GL_INVALID_VALUE錯誤。

我們會發現很多OpenGL指令都是glCreate*的形式,它們負責配置設定不同類型的OpenGL對象的名稱。這裡的名稱類似C語言中的一個指針變量,我們可以配置設定記憶體對象并且用名稱引用它。當我們得到對象之後,可以将它綁定(bind)到OpenGL環境以便使用。在這個例子中,我們通過glBindVertexArray()函數建立并且綁定了一個頂點數組對象。

glBindVertexArray()完成了兩項工作。如果輸入的變量array非0,并且是glCreate-VertexArrays()所傳回的,那麼會激活這個頂點數組對象,并且直接影響對象中所儲存的頂點數組狀态。如果輸入的變量array為0,那麼OpenGL将不再使用之前綁定的頂點數組。

如果array不是glCreateVertexArrays()所傳回的數值,或者它已經被glDelete-VertexArrays()函數釋放了,那麼這裡将産生一個GL_INVALID_OPERATION錯誤。

這個例子中,在生成一個頂點數組對象之後,就會使用glBindVertexArray()将它綁定起來。在OpenGL中這樣的對象綁定操作非常常見,但是我們可能無法立即了解它做了什麼。當我們綁定對象時(例如,用指定的對象名作為參數調用glBind()),OpenGL内部會将它作為目前對象,即所有後繼的操作都會作用于這個被綁定的對象,例如,這裡的頂點數組對象的狀态就會被後面執行的代碼所改變。在第一次調用glCreate()函數之後,新建立的對象都會初始化為其預設狀态,而我們通常需要一些額外的初始化工作來確定這個對象可用。

綁定對象的過程有點類似設定鐵路的道岔開關。一旦設定了開關,從這條線路通過的所有列車都會駛向對應的軌道。如果我們将開關設定到另一個狀态,那麼所有之後經過的列車都會駛向另一條軌道。OpenGL的對象也是如此。總體上來說,在兩種情況下我們需要綁定一個對象:建立對象并初始化它所對應的資料時;以及每次我們準備使用這個對象,而它并不是目前綁定的對象時。我們會在display()例程中看到後一種情況,即在程式運作過程中第二次調用glBindVertexArray()函數。

由于示例程式需要盡量短小,是以我們不打算做任何多餘的操作。舉例來說,在較大的程式裡當我們完成對頂點數組對象的操作之後,是可以調用glDeleteVertexArrays()将它釋放的。

删除n個在arrays中定義的頂點數組對象,這樣所有的名稱可以再次用作頂點數組。如果綁定的頂點數組已經被删除,那麼目前綁定的頂點數組對象被重設為0(類似執行了glBindBuffer()函數,并且輸入參數為0),并且不再存在一個目前對象。在arrays當中未使用的名稱都會被釋放,但是目前頂點數組的狀态不會發生任何變化。

最後,為了確定程式的完整性,我們可以調用glIsVertexArray()檢查某個名稱是否已經被保留為一個頂點數組對象了。

如果array是一個已經用glCreateVertexArrays()建立且沒有被删除的頂點數組對象的名稱,那麼傳回GL_TRUE。如果array為0或者不是任何頂點數組對象的名稱,那麼傳回GL_FALSE。

對于OpenGL中其他類型的對象,我們都可以看到類似的名為glDelete和glIs的例程。

配置設定緩存對象

頂點數組對象負責儲存一系列頂點的資料。這些資料儲存到緩存對象當中,并且由目前綁定的頂點數組對象管理。我們隻有一種頂點數組對象類型,但是卻有很多種類型的對象,并且其中一部分對象并不負責處理頂點資料。正如前文中所提到的,緩存對象就是OpenGL服務端配置設定和管理的一塊記憶體區域,并且幾乎所有傳入OpenGL的資料都是存儲在緩存對象當中的。

緩存對象的初始化過程與頂點數組對象的建立過程類似,不過需要有向緩存中添加資料的一個過程。

首先,我們需要建立頂點緩存對象的名稱。我們調用的還是glCreate*形式的函數,即glCreateBuffers()。在這個例子中,我們配置設定NumVBOs個對象(VBO即Vertex Buffer Object,用來辨別存儲頂點資料的緩存對象)到數組buffers當中。以下是glCreateBuffers()的詳細介紹。

傳回n個目前未使用的緩存對象名稱,并儲存到buffers數組中。傳回到buffers中的名稱不一定是連續的整型資料。如果n是負數,那麼産生GL_INVALID_VALUE錯誤。

這裡傳回的名稱表示新建立的緩存對象,帶有預設可用狀态。

0是一個保留的緩存對象名稱,glCreateBuffers()永遠都不會傳回這個值的緩存對象。

當配置設定緩存之後,就可以調用glBindBuffer()來綁定它們到OpenGL環境了。由于OpenGL中有很多種不同類型的緩存對象,是以綁定一個緩存時,需要指定它所對應的類型。在這個例子中,由于是将頂點資料儲存到緩存當中,是以使用GL_ARRAY_BUFFER類型。而綁定緩存的類型也稱作綁定目标(binding target)。緩存對象的類型現在有很多種,它們用于不同的OpenGL功能實作。本書後面的章節會分别讨論各種類型的對應操作。

glBindBuffer()函數的詳細介紹如下。

指定目前激活的緩存對象。target必須設定為以下類型中的一個:GL_ARRAY_BUFFER、GL_ATOMIC_COUNTER_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_SHADER_STORAGE_BUFFER、GL_QUERY_RESULT_BUFFER、GL_DRAW_INDIRECT_BUFFER、GL_TRANSFORM_FEEDBACK_BUFFER和GL_UNIFORM_BUFFER。buffer設定的是要綁定的緩存對象名稱。

glBindBuffer()完成了兩項工作:1) 如果綁定到一個已經建立的緩存對象,那麼它将成為目前target中被激活的緩存對象。2)如果綁定的buffer值為0,那麼OpenGL将不再對目前target使用任何緩存對象。

所有的緩存對象都可以使用glDeleteBuffers()直接釋放。

删除n個儲存在buffers數組中的緩存對象。被釋放的緩存對象可以重用(例如,使用glCreateBuffers())。

如果删除的緩存對象已經被綁定,那麼該對象的所有綁定将會重置為預設的緩存對象,即相當于用0作為參數執行glBindBuffer()的結果。如果試圖删除不存在的緩存對象,或者緩存對象為0,那麼将忽略該操作(不會産生錯誤)。

我們也可以用glIsBuffer()來判斷一個整數值是否是一個緩存對象的名稱。

如果buffer是一個已經配置設定并且沒有釋放的緩存對象的名稱,則傳回GL_TRUE。如果buffer為0或者不是緩存對象的名稱,則傳回GL_FALSE。

将資料載入緩存對象

初始化頂點緩存對象之後,我們需要讓OpenGL配置設定緩存對象的空間并把頂點資料從對象傳輸到緩存對象當中。這一步是通過glNamedBufferStorage()例程完成的,它主要有兩個任務:配置設定頂點資料所需的存儲空間,然後将資料從應用程式的數組中拷貝到OpenGL服務端的記憶體中。glNamedBufferStorage()為一處緩存配置設定空間,并進行命名(緩存不需要被綁定)。

有可能在很多不同的場景中多次應用glNamedBufferStorage(),是以我們有必要在這裡深入了解它的過程,盡管我們在這本書中還會多次遇到這個函數。首先,glNamedBufferStorage()的詳細定義介紹如下。

在OpenGL服務端記憶體中配置設定size個存儲單元(通常為byte),用于存儲資料或者索引。glNamedBufferStorage()作用于名為buffer的緩存區域。它不需要設定target參數。

size表示存儲資料的總數量。這個數值等于data中存儲的元素的總數乘以機關元素存儲空間的結果。

data要麼是一個用戶端記憶體的指針,以便初始化緩存對象,要麼是NULL。如果傳入的指針合法,那麼将會有size大小的資料從用戶端拷貝到服務端。如果傳入NULL,那麼将保留size大小的未初始化的資料,以備後用。

f?lags提供了緩存中存儲的資料相關的用途資訊。它是下面一系列辨別量經過邏輯“與”運算的總和:

GL_DYNAMIC_STORAGE_BIT、GL_MAP_READ_BIT、GL_MAP_WRITE_BIT、GL_MAP_PERSISTENT_BIT、GL_MAP_COHERENT_BIT和GL_CLIENT_STORAGE_BIT。我們會在本書的後面部分依次予以介紹。

如果所需的size大小超過了服務端能夠配置設定的額度,那麼glNamedBufferData()将産生一個GL_OUT_OF_MEMORY錯誤。如果f?lags包含的不是可用的模式值,那麼将産生GL_INVALID_VALUE錯誤。

一下子了解這麼多的内容可能有點困難,但是這些函數在後面的學習中會多次出現,是以有必要在本書的開始部分就詳細地對它們做出講解。

在上面的例子中,直接調用了glNamedBufferData()。因為頂點資料就儲存在一個vertices數組當中。如果需要靜态地從程式中加載頂點資料,那麼我們可能需要從模型檔案中讀取這些數值,或者通過某些算法來生成。由于我們的資料是頂點屬性資料,是以設定這個緩存的目标為GL_ARRAY_BUFFER,即它的第一個參數。我們還需要指定記憶體配置設定的大小(機關為byte),是以直接使用sizeof(vertices)來完成計算。最後,我們需要指定資料在OpenGL中使用的方式。我們可以簡單地設定f?lags為0。至于f?lags中可以使用的其他辨別量,我們會在本書後面的部分進行介紹。

如果我們仔細觀察vertices數組中的數值,就會發現它們在x和y方向都被限定在[–1, 1]的範圍内。實際上,OpenGL隻能夠繪制坐标空間内的幾何體圖元。而具有該範圍限制的坐标系統也稱為規格化裝置坐标系統(Normalized Device Coordinate,NDC)。這聽起來好像是一個巨大的限制,但實際上并不是問題。第5章會介紹将三維空間中的複雜物體映射到規格化裝置坐标系中的數學方法。在這個例子中直接使用NDC坐标,不過實際上我們通常會使用一些更為複雜的坐标空間。

現在,我們已經成功地建立了一個頂點數組對象,并且将它傳遞到緩存對象中。下一步,我們要設定程式中用到的着色器了。

初始化頂點與片元着色器

每一個OpenGL程式進行繪制的時候,都需要指定至少兩個着色器:頂點着色器和片元着色器。在這個例子中,我們通過一個輔助函數LoadShaders()來實作這個要求,它需要輸入一個ShaderInfo結構體數組(這個結構體的實作過程可以參見示例源代碼的頭檔案LoadShaders.h)。

對于OpenGL程式員而言,着色器就是使用OpenGL着色語言(OpenGL Shading Language,GLSL)編寫的一個小型程式。GLSL是構成所有OpenGL着色器的語言,它與C++語言非常類似,盡管GLSL中的所有特性并不能用于OpenGL的每個着色階段。我們可以以字元串的形式傳輸GLSL着色器到OpenGL。不過為了簡化這個例子,并且讓讀者更容易地使用着色器去進行開發,我們選擇将着色器字元串的内容儲存到檔案中,并且使用LoadShaders()讀取檔案和建立OpenGL着色器程式。使用OpenGL着色器進行程式設計的具體過程可以參見第2章的内容。

為了幫助讀者盡快開始了解着色器的内容,我們并沒有将所有相關的細節内容都立即呈現出來。事實上,本書後面的内容都會與GLSL的具體實作相關,而現在,我們隻需要在例1.2中對頂點着色器的代碼做一個深入了解。

例1.2 triangles.cpp對應的頂點着色器:triangles.vert

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析
《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

沒錯,它的内容隻有這麼多。事實上這就是我們之前所說的傳遞着色器(pass-through shader)的例子。它隻負責将輸入資料拷貝到輸出資料中。不過即便如此,我們也還是要展開深入讨論。

第一行“#version 450 core”指定了我們所用的OpenGL着色語言的版本。這裡的“450”表示我們準備使用OpenGL 4.5對應的GLSL語言。這裡的命名規範是基于OpenGL 3.3版本的。在那之前的OpenGL版本中,版本号所用的數字是完全不一樣的(詳細介紹參見第2章)。這裡的“core”表示我們将使用OpenGL核心模式(core prof?ile),它也是新的應用程式應當采用的模式。每個着色器的第一行都應該設定“#version”,否則系統會假設使用“110”版本,但是這與OpenGL核心模式并不相容。我們在本書中隻針對330版本及以上的着色器以及它的特性進行講解;如果這個版本号不是最新的版本,那麼程式的可移植性應該會更好,但是你将無法使用最新的系統特性。

下一步,我們配置設定了一個着色器變量。着色器變量是着色器與外部世界的聯系所在。換句話說,着色器并不知道自己的資料從哪裡來,它隻是在每次運作時直接擷取資料對應的輸入變量。而我們必須自己完成着色管線的裝配(在後面内容中你将了解它所表示的意思),然後才可以将應用程式中的資料與不同的OpenGL着色階段互相關聯。

在這個簡單的例子中,隻有一個名為vPosition的輸入變量,它被聲明為“in”。事實上,就算是這一行也包含了很多的内容。

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

我們最好從右往左來解讀這一行的資訊。

顯而易見vPosition就是變量的名稱。我們使用一個字元“v”作為這個頂點屬性名稱的字首。這個變量所儲存的是頂點的位置資訊。

下一個字段是vec4,也就是vPosition類型。在這裡它是一個GLSL的四維浮點數向量。GLSL中有非常多的資料類型,這會在第2章裡詳細介紹。

你也許已經注意到,我們在例1.1的程式中對每個頂點隻設定了兩個坐标值,但是在頂點着色器中卻使用vec4來表達它。那麼另外兩個坐标值來自哪裡?事實上OpenGL會用預設數值自動填充這些缺失的坐标值。而vec4的預設值為(0.0, 0.0, 0.0, 1.0),是以當僅指定了x和y坐标的時候,其他兩個坐标值(z和w)将被自動指定為0和1。

在類型之前就是我們剛才提到的in字段,它指定了資料進入着色器的流向。正如你所見,這裡還可以聲明變量為out。不過我們在這裡暫時還不會用到它。

最後的字段是layout(location = 0),它也叫做布局限定符(layout qualif?ier),目的是為變量提供中繼資料(meta data)。我們可以使用布局限定符來設定很多不同的屬性,其中有些是與不同的着色階段相關的。

在這裡,設定vPosition的位置屬性location為0。這個設定與init()函數的最後兩行會共同起作用。

最後,在着色器的main()函數中實作它的主體部分。OpenGL的所有着色器,無論是處于哪個着色階段,都會有一個main()函數。對于這個着色器而言,它所實作的就是将輸入的頂點位置複制到頂點着色器的指定輸出位置gl_Position中。後文中我們将會了解到OpenGL所提供的一些着色器變量,它們全部都是以gl_作為字首的。

與之類似,我們也需要一個片元着色器來配合頂點着色器的工作。例1.3所示就是片元着色器的内容。

例1.3 triangles.cpp對應的片元着色器:triangles.frag

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

令人高興的是,這裡大部分的代碼看起來很類似,雖然它們分别屬于兩個完全不同的着色器類型。我們還是需要聲明版本号、變量以及main()函數。這裡存在着一些差異,但是你依然可以看出,幾乎所有着色器的基本結構都是這樣的。

片元着色器的重點内容如下:

聲明的變量名為fColor。沒錯,它使用了out限定符!在這裡,着色器将會把fColor對應的數值輸出,而這也就是片元所對應的顔色值(是以這裡用到了字首字元“f”)。

與我們在頂點着色器中的輸入類似,在輸出變量fColor的聲明之前也需要加上限定符layout (location = 0)。片元着色器可以設定多個輸出值,而某個變量所對應的輸出結果就是通過location來設定的。雖然在這個着色器中我們隻用到了一個輸出值,但是我們還是有必要養成一個好習慣,給所有的輸入和輸出變量設定location。

設定片元的顔色。在這裡,每個片元都會設定一個四維的向量。OpenGL中的顔色是通過RGB顔色空間來表示的,其中每個顔色分量(R表示紅色,G表示綠色,B表示藍色)的範圍都是[0, 1]。留心的讀者在這裡可能會問,“但是這是一個四維的向量”。沒錯,OpenGL實際上使用了RGBA顔色空間,其中第四個值并不是顔色值。它叫做alpha值,專用于度量透明度。第4章将深入讨論這個話題,但是在現在,我們将它直接設定為1.0,這表示片元的顔色是完全不透明的。

片元着色器具有非常強大的功能,我們可以用它來實作非常多的算法和技巧。

我們已經基本完成了初始化的過程。init()中最後的兩個函數指定了頂點着色器的變量與我們存儲在緩存對象中資料的關系。這也就是我們所說的着色管線裝配的過程,即将應用程式與着色器之間,以及不同着色階段之間的資料通道連接配接起來。

為了輸入頂點着色器的資料,也就是OpenGL将要處理的所有頂點資料,需要在着色器中聲明一個in變量,然後使用glVertexAttribPointer()将它關聯到一個頂點屬性數組。

設定index(着色器中的屬性位置)位置對應的資料值。pointer表示緩存對象中,從起始位置開始計算的數組資料的偏移值(假設起始位址為0),使用基本的系統機關(byte)。size表示每個頂點需要更新的分量數目,可以是1、2、3、4或者GL_BGRA。type指定了數組中每個元素的資料類型(GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FIXED、GL_HALF_FLOAT、GL_FLOAT或GL_DOUBLE)。normalized設定頂點資料在存儲前是否需要進行歸一化(或者使用glVertexAttribFourN*()函數)。stride是數組中每兩個元素之間的大小偏移值(byte)。如果stride為0,那麼資料應該緊密地封裝在一起。

看起來我們有一大堆事情需要考慮,因為glVertexAttribPointer()其實是一個非常靈活的指令。隻要在記憶體中資料是規範組織的(儲存在一個連續的數組中,不使用其他基于節點的容器,比如連結清單),我們就可以使用glVertexAttribPointer()告訴OpenGL直接從記憶體中擷取資料。在例子中,vertices裡已經包含了我們所需的全部資訊。表1-2所示為在這個例子裡glVertexAttribPointer()中各個參數的設定及其意義。

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

希望上面的參數解釋能夠幫助你判斷自己的資料結構所對應的數值。在後文中我們還會多次用到glVertexAttribPointer()來實作示例程式。

這裡我們還用到了一個技巧,就是用glVertexAttribPointer()中的BUFFER_OFFSET宏來指定偏移量。這個宏的定義沒有什麼特别的,如下所示:

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

在以往版本的OpenGL當中并不需要用到這個宏,不過現在我們希望使用它來設定資料在緩存對象中的偏移量,而不是像glVertexAttribPointer()的原型那樣直接設定一個指向記憶體塊的指針。

在init()中,我們還有一項任務沒有完成,那就是啟用頂點屬性數組。我們通過調用glEnableVertexAttribArray()來完成這項工作,同時将glVertexAttribPointer()初始化的屬性數組指針索引傳入這個函數。有關glEnableVertexAttribArray()的詳細解釋如下所示。

設定是否啟用與index索引相關聯的頂點數組。index必須是一個介于0到GL_MAX_VERTEX_ATTRIBS-1之間的值。

需要注意的是,我們剛剛使用glVertexAttribPointer()和glEnableVertexAttribArray()設定的狀态是儲存到在函數伊始就綁定好的頂點數組對象中的。而狀态的改變是在綁定對象時私下完成的。如果希望設定一個頂點數組對象,但是不要把它綁定到裝置環境中,那麼可以調用glEnableVertexArrayAttrib()、glVertexArrayAttribFormat()和glVertexArrayVertexBuffers(),也就是通過直接狀态通路(direct state access)的模式來完成相同的操作。

現在,我們隻需要完成繪制的工作即可。

1.5.3 第一次使用OpenGL進行渲染

在設定和初始化所有資料之後,渲染的工作(在這個例子中)就非常簡單了。display()函數隻有4行代碼,不過它所包含的内容在所有OpenGL程式中都會用到。下面我們先閱讀其中的代碼。

《OpenGL程式設計指南(原書第9版)》——1.5 第一個程式:深入分析

首先,我們要清除幀緩存的資料再進行渲染。清除的工作由glClearBufferfv()完成。

清除目前繪制幀緩存中的指定緩存類型,清除結果為value。參數buffer設定了要清除的緩存類型,它可以是GL_COLOR、GL_DEPTH,或者GL_STENCIL。參數drawbuffer設定了要清除的緩存索引。如果目前綁定的是預設幀緩存,或者buffer設定為GL_DEPTH或GL_STENCIL,那麼drawbuffer必須是0。否則它表示需要被清除的顔色緩存的索引。

參數value是一個數組的指針,其中包含了一個或者四個浮點數,用來設定清除緩存之後的顔色。如果buffer設定為GL_COLOR,那麼value必須是一個最少四個數值的數組,以表示顔色值。如果buffer是GL_DEPTH或者GL_STENCIL,那麼value可以是一個單獨的浮點數,分别用來設定深度緩存或者模闆緩存清除後的結果。

我們會在第4章中學習深度緩存(depth buffer)與模闆緩存(stencil buffer)的内容,當然還有對顔色緩存(color buffer)的深入探讨。

在這個例子中,我們将顔色緩存清除為黑色。如果你想把視口中的畫面清除為白色,可以調用glClearBufferfv()并設定value為一個數組的指針,且這個數組的四個浮點數都是1.0。

試一試 在triangles.cpp中修改black變量中的數值,觀察顔色清除後的不同效果。

使用OpenGL進行繪制

例子中後面兩行的工作是選擇我們準備繪制的頂點資料,然後請求進行繪制。首先調用glBindVertexArray()來選擇作為頂點資料使用的頂點數組。正如前文中提到的,我們可以用這個函數來切換程式中儲存的多個頂點資料對象集合。

其次調用glDrawArrays()來實作頂點資料向OpenGL管線的傳輸。

使用目前綁定的頂點數組元素來建立一系列的幾何圖元,起始位置為f?irst,而結束位置為f?irst + count-1。mode設定了建構圖元的類型,它可以是GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN和GL_PATCHES中的任意一種。

glDrawArrays()函數可以被認為是更複雜的glDrawArraysInstancedBaseInstance()函數的一個簡化版本,後者包含了更多的參數。我們會在3.4.2節予以介紹。

在這個例子中,我們使用glVertexAttribPointer()設定渲染模式為GL_TRIANGLES,起始位置位于緩存的0偏移位置,共渲染NumVertices個元素(這個例子中為6個),這樣就可以渲染出獨立的三角形圖元了。我們會在第3章詳細介紹所有的圖元形狀。

試一試 修改triangles.cpp讓它渲染一個不同類型的幾何圖元,例如GL_POINTS或者GL_LINES。你可以使用上文中列出的任何一種圖元,但是有些的結果可能會比較奇怪,此外GL_PATCHES類型是不會輸出任何結果的,因為它是用于細分着色器的,參見第9章的内容。

就是這樣!現在我們已經繪制了一些内容。而這些架構性質的代碼已經可以很好地維護顯示的結果了。

啟用和禁用OpenGL的操作

在第一個例子當中有一個重要的特性并沒有用到,但是在後文中我們會反複用到它,那就是對于OpenGL操作模式的啟用和禁用。絕大多數的操作模式都可以通過glEnable()和glDisable()指令開啟或者關閉。

glEnable()會開啟一個模式,glDisable()會關閉它。有很多枚舉量可以作為模式參數傳入glEnable()和glDisable()。例如GL_DEPTH_TEST可以用來開啟或者關閉深度測試;GL_BLEND可以用來控制融合的操作,而GL_RASTERIZER_DISCARD用于transform feedback過程中的進階渲染控制。

很多時候,尤其是我們用OpenGL編寫的庫需要提供給其他程式員使用的時候,可以根據自己的需要來判斷是否開啟某個特性,這時候可以使用glIsEnabled()來傳回是否啟用指定模式的資訊。

根據是否啟用目前指定的模式,傳回GL_TRUE或者GL_FALSE。

繼續閱讀