GLSL
前一篇主要是學(xué)習(xí)OpenGL的基本使用以及完成我們的Hello Triangle項目葬荷。在里面我們配置了頂點和片段著色器供置,對于Hello Triangle項目來說這兩個著色器完全夠用督暂。但如果需要處理更為復(fù)雜的繪制壁晒,顯然這兩個基本著色器是遠(yuǎn)遠(yuǎn)不行的赁温,所以這篇文章主要更深入著色器程序挑势,對著色器程序進(jìn)行更完善的配置价淌。
前面我們已經(jīng)說過在Xcode中如何編寫著色器源碼了申眼,在新建空文件的時候我們保存的后綴為.glsl。沒錯蝉衣,著色器是使用一種叫GLSL的類C語言完成的括尸。GLSL是為圖形計算量身定制的,它包含一些針對向量和矩陣操作的有用特性病毡。這是前面使用到的頂點著色器源碼
attribute vec4 Position;
void main(void) {
gl_Position = Position;
}
第一行定義輸入變量也叫頂點屬性濒翻,這是一個4分量的向量Position。其實我們能聲明的頂點屬性是有上限的,它一般由硬件來決定有送。OpenGL確保至少有16個包含4分量的頂點屬性可用淌喻。可以使用glGetIntegerv來輸出上限數(shù):
int numAttributs;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &numAttributs);
NSLog(@"----numAttributs:%d",numAttributs);
這里輸出的是----numAttributs:16雀摘,表明能聲明的頂點屬性上限為16個裸删,大部分情況下是夠用了。
數(shù)據(jù)類型
和其他編程語言一樣阵赠,GLSL有數(shù)據(jù)類型可以來指定變量的種類涯塔,GLSL中包含C等其它語言大部分的默認(rèn)基礎(chǔ)數(shù)據(jù)類型:Int、float豌注、double伤塌、uint和bool,GLSL也有兩種容器類型轧铁,分別是向量(Vector)和矩陣(Matrix)每聪。
向量(Vector)
GLSL中的向量是一個可以包含有1、2齿风、3或者4個分量的容器药薯,分量的類型可以是前面默認(rèn)基礎(chǔ)類型的任意一個。它們可以是下面的形式(n代表分量的數(shù)量):
vecn | 包含n個float分量的默認(rèn)向量 |
---|---|
bvecn | 包含n個bool分量的默認(rèn)向量 |
ivecn | 包含n個int分量的默認(rèn)向量 |
uvecn | 包含n個unsigned int分量的默認(rèn)向量 |
dvecn | 包含n個double分量的默認(rèn)向量 |
大多數(shù)時候我們使用vecn救斑,因為float足夠滿足大多數(shù)要求了童本。
一個向量的分量可以通過vec.x這種方式獲取,這里x是指這個向量的第一個分量脸候。你可以分別使用.x穷娱、.y、.z和.w來獲取它們的第1运沦、2泵额、3、4個分量携添。GLSL也允許你對顏色使用rgba嫁盲,或是對紋理坐標(biāo)使用stpq訪問相同的分量。 向量這一數(shù)據(jù)類型也允許一些有趣而靈活的分量選擇方式烈掠,叫做重組:
vec1 pos1;
vec2 pos2 = pos1.xx // 可以使用pos1的x分量重組一個vec2的pos2
vec4 pos3 = pos2.xyxy // 使用pos2的x和y分量重組一個4分量的pos3
你可以使用上面4個字母任意組合來創(chuàng)建一個和原來向量一樣長的(同類型)新向量羞秤,只要原來向量有那些分量即可;然而左敌,你不允許在一個vec2向量中去獲取.z元素瘾蛋,例如上面的pos2里面的沒有z元素的。
除了上面的重組矫限,我們還可以這樣操作:
vec2 pos4 = vec2(0.5瘦黑,0.5);
vec3 pos5 = vec3(pos4京革,1.0);
vec4 pos6 = vec4(pos5.xyz幸斥,1.0)匹摇;
//向量是一種靈活的數(shù)據(jù)類型,我們可以把用在各種輸入和輸出上甲葬。
Uniform
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
在前面的程序中廊勃,我們是在片段著色器中定義的最終輸出顏色為vec4(1.0, 0.0, 0.0, 1.0)。這里就有個問題经窖,如果我們想從應(yīng)用程序中直接給片段著色器設(shè)置顏色該怎么做坡垫。
OpenGL顯然早已解決了這個問題。在OpenGL中存在一種從CPU中的應(yīng)用向GPU中的著色器發(fā)送數(shù)據(jù)的方式画侣,但凡定義在著色器程序中的屬性前面加上Uniform修飾冰悠,這個屬性就是一個全局常量,可以存儲著色器所需要的各種數(shù)據(jù)配乱。另外uniform 的空間被頂點著色器和片段著色器分享溉卓。也就是說頂點著色器和片段著色器被鏈接到一起進(jìn)入項目,它們分享同樣的uniform搬泥。因此一個在頂點著色器中聲明的uniform桑寨,相當(dāng)于在片段著色器中也聲明過了。當(dāng)應(yīng)用程序裝載uniform 時忿檩,它的值在頂點著色器和片段著色器都可用尉尾。在鏈接階段,鏈接器將分配常量在項目里的實際地址燥透,那個地址是被應(yīng)用程序使用和加載的標(biāo)識沙咏。
下面我們在片段著色器中聲明一個uniform屬性,并在著色器主函數(shù)中設(shè)置最終輸出顏色為定義好的uniform:
precision mediump float;
uniform vec4 color;
void main(void) {
gl_FragColor = color;
}
//現(xiàn)在這個uniform現(xiàn)在還是空的班套;我們還沒有給它添加任何數(shù)據(jù)肢藐,這里需要注意如果你聲明了一個uniform卻在GLSL代碼中沒用過,
編譯器會靜默移除這個變量孽尽,導(dǎo)致最后編譯出的版本中并不會包含它窖壕,這可能導(dǎo)致幾個非常麻煩的錯誤
現(xiàn)在我們需要在應(yīng)用中找到這個uniform所在的位置然后對其進(jìn)行更新:
// 獲取uniform的位置值忧勿,如果glGetUniformLocation返回-1就代表沒有找到這個位置值杉女。
int vertexColorLoc = glGetUniformLocation(programHandle, "color");
glUseProgram(programHandle);
// 然后我們可以通過glUniform4f函數(shù)設(shè)置uniform值。
glUniform4f(vertexColorLoc, 0.5f, 1.0f, 0.5f, 1.0f);
這里有一點需要注意鸳吸,查詢uniform地址不要求之前使用過著色器程序熏挎,但是更新一個uniform之前你必須先使用程序(調(diào)用glUseProgram),因為它是在當(dāng)前激活的著色器程序中設(shè)置uniform的晌砾,所以在設(shè)置uniform之前我們還需要調(diào)用glUseProgram函數(shù)激活著色器程序
這樣運行的話我們就可以看到顏色不一樣的圖形了坎拐。
多個屬性
在前面的教程中,我們了解了如何填充VBO、配置頂點屬性指針以及如何把它們都儲存到一個VAO里哼勇。這次都伪,我們同樣打算把顏色數(shù)據(jù)加進(jìn)頂點數(shù)據(jù)中。我們將把顏色數(shù)據(jù)添加為3個float值至vertices數(shù)組积担。我們將把三角形的三個角分別指定為紅色陨晶、綠色和藍(lán)色:
const GLfloat vertices[] = {
//位置 // 顏色
0.5f, -0.5f, 0.0f, 1.0f,0.0f,0.0f // 右上角
-0.5f, -0.5f, 0.0f, 0.0f,1.0f,0.0f // 右下角
0.0f, 0.5f, 0.0f , 0.0f,0.0f,1.0f // 左下角
};
定義好頂點數(shù)組,我們還需要修改一下頂點著色器源碼帝璧,由于新增了顏色值先誉,所以在頂點著色器中我們需要定義一個輸入屬性來接受頂點數(shù)組中傳進(jìn)來的顏色數(shù)據(jù)。并且還需要定義一個輸出屬性的烁,用來傳遞顏色值給片段著色器褐耳。所以現(xiàn)在的頂點著色器源碼如下:
attribute vec4 Position;
attribute vec4 color;
varying vec4 aColor; // 向片段著色器輸出一個顏色
void main(void) {
gl_Position = Position;
aColor = color; // aColor設(shè)置為我們從頂點數(shù)據(jù)那里得到的輸入顏色
}
再看下片段著色器:
precision mediump float;
varying lowp vec4 aColor; // 接受頂點著色器輸出的顏色
void main(void) {
gl_FragColor = aColor; // 設(shè)置最終顏色為接受的顏色
}
配置完頂點著色器和片段著色器,接下來需要重新設(shè)置頂點數(shù)據(jù)格式了渴庆,因為我們添加了另一個頂點屬性铃芦,并且更新了VBO的內(nèi)存,更新后的VBO內(nèi)存中的數(shù)據(jù)現(xiàn)在看起來像這樣:相比較只有頂點坐標(biāo)屬性的時候把曼,VBO內(nèi)存中的每個頂點數(shù)據(jù)中新增了顏色值杨帽,知道了現(xiàn)在的數(shù)據(jù)布局,我們就可以使用glVertexAttribPointer函數(shù)更新頂點格式:
// 位置屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), 0);
glEnableVertexAttribArray(0);
// 顏色屬性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
這里需要注意的是第四個參數(shù):由于我們現(xiàn)在有了兩個頂點屬性嗤军,所以不得不重新計算連續(xù)頂點屬性之間的偏移量注盈,為獲得數(shù)據(jù)隊列中下一個屬性值(比如位置向量的下個x分量)我們必須向右移動6個float,其中3個是位置值叙赚,另外3個是顏色值老客。這使我們的步長值為6乘以float的字節(jié)數(shù)(=24字節(jié))。
這個圖片可能不是你所期望的那種击罪,因為我們只提供了3個顏色哲嘲,而不是我們現(xiàn)在看到的大調(diào)色板。這是在片段著色器中進(jìn)行的所謂片段插值(Fragment Interpolation)的結(jié)果媳禁。當(dāng)渲染一個三角形時眠副,光柵化(Rasterization)階段通常會造成比原指定頂點更多的片段。光柵會根據(jù)每個片段在三角形形狀上所處相對位置決定這些片段的位置竣稽。
基于這些位置囱怕,它會插值(Interpolate)所有片段著色器的輸入變量霍弹。比如說,我們有一個線段娃弓,上面的端點是綠色的典格,下面的端點是藍(lán)色的。如果一個片段著色器在線段的70%的位置運行台丛,它的顏色輸入屬性就會是一個綠色和藍(lán)色的線性結(jié)合钝计;更精確地說就是30%藍(lán) + 70%綠。
這正是在這個三角形中發(fā)生了什么齐佳。我們有3個頂點私恬,和相應(yīng)的3個顏色,從這個三角形的像素來看它可能包含50000左右的片段炼吴,片段著色器為這些像素進(jìn)行插值顏色本鸣。如果你仔細(xì)看這些顏色就應(yīng)該能明白了:紅首先變成到紫再變?yōu)樗{(lán)色。片段插值會被應(yīng)用到片段著色器的所有輸入屬性上硅蹦。
參考《OpenGL ES 3.0編程指南》