本文主要解決一個問題:
如何渲染一個三角形?
本章中,會有大量的新名詞和解釋,大量的函數(shù)出現(xiàn),建議找個安靜的地方慢慢啃這塊骨頭。
首先,先從直覺上來想想要渲染一個三角形我們需要做些什么?大概需要這三個步驟:
- 1、定義三個頂點。
- 2、將三個頂點的邊兩兩相連。
- 3、將內(nèi)部的區(qū)域涂成一種或幾種顏色。
我們就從這幾個方面來畫出我們的三角形。
一、頂點
在OpenGL中,所有的頂點都是三維空間內(nèi)的頂點,不過這不是問題,我們可以把深度定義為0來保證其在一個平面上。于是,我們定義三個點:(-0.5, 0.5, 0.0),(0.5, -0.5, 0.0),(0.0, 0.5, 0.0),由于在OpenGL中所有的頂點都會被規(guī)范化(將頂點的位置變換到-1到1之間),我們就直接定義規(guī)范化后的點,方便處理。我們的代碼就像這個樣子(編程是一種手寫編碼的過程,不是Ctrl+C、Ctrl+V的過程,手寫一遍代碼還是非常必要的):
我們有了頂點數(shù)組,如何讓OpenGL知道呢?這里有個新東西,叫頂點緩存對象(Vertex Buffer Object,簡稱VBO),表示存儲在GPU顯存中的大量頂點數(shù)據(jù)。我們可以通過這個對象,一次性向GPU發(fā)送大量的數(shù)據(jù),而不是一次次地從CPU中發(fā)送數(shù)據(jù)到GPU,這是個很慢的過程。
1、使用VBO
唯一ID:OpenGL中有太多的東西,我們需要一個唯一的ID,就像身份證一樣來標識出哪個是哪個。這個ID不能我們自己來定,只能告訴OpenGL說,我需要一個唯一ID,你給我一個吧。然后,OpenGL就會給你一個沒用過的唯一ID,這個過程是由glGenBuffers來實現(xiàn)的。
指明緩存對象類型:OpenGL中有很多緩存對象,雖然它給了我們一個ID,但不知道這個ID是用來表示什么緩存對象的。我們要明確告訴它,這是一個頂點緩存對象。綁定的作用,是將原來有的東西替換成我們新的東西,這樣,接下來對GL_ARRAY_BUFFER的操作都是對我們新東西的操作了。
將頂點數(shù)據(jù)傳到VBO:
- 參數(shù)1:我們的頂點數(shù)據(jù)需要拷貝到的地方。(之前我們綁定的VBO)
- 參數(shù)2:數(shù)組的大小
- 參數(shù)3:數(shù)組的地址
- 參數(shù)4:指定顯卡要采用什么方式來管理我們的數(shù)據(jù),GL_STATIC_DRAW表示這些數(shù)據(jù)不會經(jīng)常改變。
2、頂點著色器
從OpenGL 3.3開始,OpenGL的渲染方式就從固定功能管線轉(zhuǎn)向了可編程功能管線。而可編程的意思,就是可以通過許許多多的著色器來實現(xiàn)多種多樣的效果,這些效果要比固定的效果好的多。頂點著色器就是這些許許多多的著色器中的一種。
先放出一段代碼(GLSL語言編寫,語法與C極為類似):
- 第一行:指明了使用OpenGL的版本以及運行模式(版本號3.3,核心模式)
- 第二行:指明了需要從上一個步驟中獲取一個vec3類型的位置數(shù)據(jù),數(shù)據(jù)位置在輸入數(shù)據(jù)的0偏移位置(類似于輸入了一塊數(shù)據(jù),我們要的數(shù)據(jù)在頭部)
- 第七行(main函數(shù)內(nèi)部):將頂點的位置直接賦值成輸入的位置,gl_Position是一個內(nèi)置的變量,用來表示頂點位置的。
2.1編譯著色器
代碼自然是要編譯鏈接之后才能執(zhí)行的,這里我們先說編譯,鏈接的部分等講完片元著色器再一起說,先不用糾結(jié)。
首先,創(chuàng)建一個著色器對象,返回值是這個對象的唯一ID
然后,我們將源碼附加到著色器對象上并且編譯這個著色器對象
3、片元著色器
同樣,先放出代碼
類似的,我們輸出一個顏色供片元使用,這個顏色是橙色。一個片元,包含了OpenGL用來渲染一個像素的所有信息。
3.1編譯著色器
我們同樣需要創(chuàng)建一個著色器對象,然后將源碼附加到著色器對象上,然后編譯。代碼如下:
4、著色器程序?qū)ο?/h4>
著色器程序?qū)ο笫亲罱K將所有著色器連接起來的對象。把所有的著色器對象和這個著色器程序?qū)ο筮B接起來之后,我們就能使用這個著色器程序?qū)ο罅恕?br>
將著色器都連接到程序?qū)ο笊蠒r,OpenGL會將上一個階段的輸出連接到下一個階段的輸入上。
我們需要三步來使用著色器程序?qū)ο?,現(xiàn)在已經(jīng)很熟悉了。創(chuàng)建、附加、鏈接。
5、使用著色器程序?qū)ο?/h4>
代碼很簡單,只有一行
6、清理著色器
將已經(jīng)附加過的著色器都清除掉,我們現(xiàn)在不需要它們了(過河拆橋???)。
7、指明頂點屬性
頂點著色器給了我們極大的便利性,我們可以輸入想要輸入的任何屬性格式。因此,我們也就要告訴OpenGL我們頂點的格式是什么樣子的。我們定義的頂點格式如下:
可以看到一些重要信息,我們的起始頂點的偏移為0,每個位置分量占用4字節(jié)的空間,一個頂點占用12字節(jié)空間。
于是,我們調(diào)用glVertexAttribPointer函數(shù)來指定頂點格式。
- 參數(shù)1:指明我們想要配置的頂點屬性。類似編號的東西,之前我們設(shè)置了location = 0,就是我們在這里用到的0.
- 參數(shù)2:頂點屬性的大小。我們用到的頂點是一個vec3的結(jié)果,所以大小為3.
- 參數(shù)3:數(shù)據(jù)的類型。我們使用的是float類型
- 參數(shù)4:指明數(shù)據(jù)是否要被規(guī)范化。這里我們設(shè)置成FALSE,不用規(guī)范化,因為我們已經(jīng)規(guī)范化好了。
- 參數(shù)5:表示屬性的跨度。正如之前我們分析的,我們的跨度是12,就是3倍的float類型。
- 參數(shù)6:指明了數(shù)據(jù)的起始偏移量。這里轉(zhuǎn)成了一個void*指針類型比較奇怪,我們以后再聊。
我們獲取的頂點屬性是由VBO決定的,而glVertexAttribPointer操作的是當前綁定到GL_ARRAY_BUFFER上的VBO,所以,我們當前操作的就是我們之前生成并綁定的那個VBO。
glEnableVertexAttribArray(0)是用來讓頂點屬性生效的,參數(shù)0就是我們之前配置的那個頂點屬性的位置。
二、繪制三角形
OpenGL中并沒有將我們上面設(shè)想的兩步單獨弄出來,只需要指明要畫的是三角形,而且是實體三角形,這兩步就能自動完成了。
所以,當我們做完上面那么多步驟之后,我們只需要調(diào)用一行代碼就可以了。
到這里,似乎我們要做的事情都已經(jīng)做完,只要編譯運行代碼就能看到我們想要的三角形了。然而,事實并不像我們想象的那樣。運行的代碼還是一片青灰色,根本看不到三角形的影子。加了一個VAO進去之后,就能顯示出三角形來了??纯唇坛蹋瓉磉@個VAO已經(jīng)是OpenGL渲染管線中不可缺少的一部分了。所以,我們要鄭重其事地來了解一下VAO。
三、VAO
全稱是頂點數(shù)組對象(Vertex Array Object),作用是來保存對頂點屬性的調(diào)用。這樣,當我們需要這些頂點屬性的時候,只需要簡單地綁定VAO,不需要再設(shè)置一遍頂點屬性就可以進行繪制了。(是不是覺得沒有必要調(diào)用,我不想保存,只想顯示。所以筆者才嘗試不用VAO看看能不能顯示出三角形,結(jié)果是,沒戲,只能乖乖的用它。)
VAO會保存兩種東西:
- 其一、對glEnableVertexAttribArray或者是DisableVertexAttribArray的調(diào)用。
- 其二、使用glVertexAttribPointer設(shè)置的頂點屬性以及與頂點屬性相關(guān)連的VBO。
生成VAO并綁定的代碼如下:
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
將這段代碼添加到生成VBO的代碼之后就可以編譯運行了。
嗯,不出所料,我們的三角形顯示出來了。
最后,源碼可以參考這里,強烈建議自己手輸一遍代碼,不自己寫一遍還算是程序員么?
總結(jié)
這是一張頂點處理的流程圖,我們所做的工作就是處理其中的一些階段。今天我們處理的是頂點著色和片元著色,之后在學光照和紋理的時候我們依舊是處理這些著色,可見這兩個階段是多么重要。
參考資料:
www.learningopengl.com(推薦學習)