著色器(Shader)是運(yùn)行在GPU上的小程序粪摘。這些小程序?yàn)閳D形渲染管線的某個(gè)特定部分而運(yùn)行响驴。從基本意義上來(lái)說(shuō)擅笔,著色器只是一種把輸入轉(zhuǎn)化為輸出的程序舆乔。著色器也是一種非常獨(dú)立的程序磨德,因?yàn)樗鼈冎g不能相互通信缘回;它們之間唯一的溝通只有通過(guò)輸入和輸出。
前面的教程里我們簡(jiǎn)要地觸及了一點(diǎn)著色器的皮毛典挑,并了解了如何恰當(dāng)?shù)厥褂盟鼈儭酥宴,F(xiàn)在我們會(huì)用一種更加廣泛的形式詳細(xì)解釋著色器,特別是OpenGL著色器語(yǔ)言(GLSL)您觉。
GLSL
著色器是使用一種叫GLSL的類C語(yǔ)言寫(xiě)成的拙寡。GLSL是為圖形計(jì)算量身定制的,它包含一些針對(duì)向量和矩陣操作的有用特性琳水。
著色器的開(kāi)頭總是要聲明版本肆糕,接著是輸入和輸出變量、uniform和main函數(shù)在孝。每個(gè)著色器的入口點(diǎn)都是main函數(shù)诚啃,在這個(gè)函數(shù)中我們處理所有的輸入變量,并將結(jié)果輸出到輸出變量中私沮。如果你不知道什么是uniform也不用擔(dān)心始赎,我們后面會(huì)進(jìn)行講解。
一個(gè)典型的著色器有下面的結(jié)構(gòu):
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 處理輸入并進(jìn)行一些圖形操作
...
// 輸出處理過(guò)的結(jié)果到輸出變量
out_variable_name = weird_stuff_we_processed;
}
當(dāng)我們特別談?wù)摰巾旤c(diǎn)著色器的時(shí)候仔燕,每個(gè)輸入變量也叫頂點(diǎn)屬性(Vertex Attribute)造垛。我們能聲明的頂點(diǎn)屬性是有上限的,它一般由硬件來(lái)決定晰搀。OpenGL確保至少有16個(gè)包含4分量的頂點(diǎn)屬性可用五辽,但是有些硬件或許允許更多的頂點(diǎn)屬性,你可以查詢GL_MAX_VERTEX_ATTRIBS
來(lái)獲取具體的上限:
GLint nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
通常情況下它至少會(huì)返回16個(gè)厕隧,大部分情況下是夠用了奔脐。
數(shù)據(jù)類型
和其他編程語(yǔ)言一樣,GLSL有數(shù)據(jù)類型可以來(lái)指定變量的種類吁讨。GLSL中包含C等其它語(yǔ)言大部分的默認(rèn)基礎(chǔ)數(shù)據(jù)類型:int、float峦朗、double建丧、uint和bool。GLSL也有兩種容器類型波势,它們會(huì)在這個(gè)教程中使用很多翎朱,分別是向量(Vector)和矩陣(Matrix)橄维,其中矩陣我們會(huì)在之后的教程里再討論。
向量
GLSL中的向量是一個(gè)可以包含有1拴曲、2争舞、3或者4個(gè)分量的容器,分量的類型可以是前面默認(rèn)基礎(chǔ)類型的任意一個(gè)澈灼。它們可以是下面的形式(n代表分量的數(shù)量):
類型 含義
vecn 包含n個(gè)float分量的默認(rèn)向量
bvecn 包含n個(gè)bool分量的向量
ivecn 包含n個(gè)int分量的向量
uvecn 包含n個(gè)unsigned int分量的向量
dvecn 包含n個(gè)double分量的向量
大多數(shù)時(shí)候我們使用vecn竞川,因?yàn)閒loat足夠滿足大多數(shù)要求了。
一個(gè)向量的分量可以通過(guò)vec.x這種方式獲取叁熔,這里x是指這個(gè)向量的第一個(gè)分量委乌。你可以分別使用.x、.y荣回、.z和.w來(lái)獲取它們的第1遭贸、2、3心软、4個(gè)分量壕吹。GLSL也允許你對(duì)顏色使用rgba,或是對(duì)紋理坐標(biāo)使用stpq訪問(wèn)相同的分量删铃。
向量這一數(shù)據(jù)類型也允許一些有趣而靈活的分量選擇方式算利,叫做重組(Swizzling)。重組允許這樣的語(yǔ)法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
你可以使用上面4個(gè)字母任意組合來(lái)創(chuàng)建一個(gè)和原來(lái)向量一樣長(zhǎng)的(同類型)新向量泳姐,只要原來(lái)向量有那些分量即可效拭;然而,你不允許在一個(gè)vec2向量中去獲取.z元素胖秒。我們也可以把一個(gè)向量作為一個(gè)參數(shù)傳給不同的向量構(gòu)造函數(shù)缎患,以減少需求參數(shù)的數(shù)量:
vec2 vect = vec2(0.5f, 0.7f);
vec4 result = vec4(vect, 0.0f, 0.0f);
vec4 otherResult = vec4(result.xyz, 1.0f);
關(guān)于OpenGLES渲染管線,請(qǐng)參考博客 [OpenGL ES 02]OpenGL ES渲染管線與著色器阎肝。著色器是可編程管線中的術(shù)語(yǔ)挤渔,其語(yǔ)法類似C語(yǔ)言,分為頂點(diǎn)著色器(Vertex Shader)和片元著色器(Fragment Shader)
頂點(diǎn)著色器
Vertex shader – 在你的場(chǎng)景中风题,每個(gè)頂點(diǎn)都需要調(diào)用的程序判导,稱為“頂點(diǎn)著色器”。假如你在渲染一個(gè)簡(jiǎn)單的場(chǎng)景:一個(gè)長(zhǎng)方形沛硅,每個(gè)角只有一個(gè)頂點(diǎn)眼刃。于是vertex shader 會(huì)被調(diào)用四次。它負(fù)責(zé)執(zhí)行:諸如燈光摇肌、幾何變換等等的計(jì)算擂红。得出最終的頂點(diǎn)位置后,為下面的片段著色器提供必須的數(shù)據(jù)围小。
vertex shader可通過(guò)可編程的方式實(shí)現(xiàn)對(duì)頂點(diǎn)的操作昵骤,如坐標(biāo)空間轉(zhuǎn)換树碱,顏色及紋理坐標(biāo)。最簡(jiǎn)單的Vertex shader如下
attribute vec4 Position;
void main(Void) {
gl_Position = Position; // must set gl_Position for vertex shader
}
1变秦、attribute聲明vertex shader接收的變量成榜,針對(duì)每一個(gè)頂點(diǎn)的數(shù)據(jù)。屬性可理解為針對(duì)每一個(gè)頂點(diǎn)的輸入數(shù)據(jù)蹦玫,只有在vertex shader中才有赎婚,在fragment shader中沒(méi)有。vec4表示由4部分組成的矢量钳垮。這里的Position用來(lái)傳入頂點(diǎn)vertex的位置數(shù)據(jù)惑淳。
2、main是shader腳本的入口饺窿。
3歧焦、gl_Position是vertex shader內(nèi)建的輸出變量,傳遞給fragment shader肚医,必須設(shè)置绢馍。這里將Position直接傳遞給fragment shader。
片元著色器
Fragment shader – 在你的場(chǎng)景中肠套,大概每個(gè)像素都會(huì)調(diào)用的程序舰涌,稱為“片段著色器”。在一個(gè)簡(jiǎn)單的場(chǎng)景你稚,也是剛剛說(shuō)到的長(zhǎng)方形瓷耙。這個(gè)長(zhǎng)方形所覆蓋到的每一個(gè)像素,都會(huì)調(diào)用一次fragment shader刁赖。片段著色器的責(zé)任是計(jì)算燈光搁痛,以及更重要的是計(jì)算出每個(gè)像素的最終顏色
Fragment是可以被渲染到屏幕上的像素點(diǎn),fragment shader即用于計(jì)算每個(gè)像素的顏色等屬性宇弛。最簡(jiǎn)單的Fragment shader如下
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // must set gl_FragColor for fragment shader
}
1鸡典、precision mediump float設(shè)置float的精度為mediump,還可設(shè)置為lowp和highp枪芒,主要是出于性能考慮彻况。
2、gl_FragColor是fragment shader唯一的內(nèi)建輸出變量舅踪,設(shè)置像素的顏色纽甘。這里設(shè)置所有像素均為紅色。
Vertex shader接收多個(gè)參數(shù)
上邊的vertex shader僅接收頂點(diǎn)的位置信息硫朦,因此像素顏色都是在fragment shader中寫(xiě)固定的(紅色)贷腕。而在下邊的vertex shader中,通過(guò)SourceColor傳遞像素顏色咬展。
attribute vec4 Position; // position of vertex
attribute vec4 SourceColor; // color of vertex
varying vec4 DestinationColor; // will pass out to fragment shader
void main(void) {
DestinationColor = SourceColor;
gl_Position = Position;
}
1泽裳、未聲明為attribute的變量即為輸出變量(如DestinationColor),將傳遞給fragment shader破婆。
2涮总、varying表示依據(jù)兩個(gè)頂點(diǎn)的顏色,平滑地計(jì)算出頂點(diǎn)之間每個(gè)像素的顏色祷舀。
對(duì)應(yīng)的fragment shader為:
varying lowp vec4 DestinationColor;
void main(void) {
gl_FragColor = DestinationColor;
}
這里瀑梗,fragment shader接收來(lái)自vertex shader的變量DestinationColor,賦值給gl_FragColor裳扯,再輸出至OpenGLES抛丽。即每個(gè)像素的顏色由DestinationColor決定,這樣可在代碼中精確控制每個(gè)像素的顏色饰豺。
Vertex shader與Fragment shader的差異
1亿鲜、shader腳本中有三種級(jí)別的精度:lowp,mediump冤吨,highp蒿柳。如precision highp float; 。在vertex shader中漩蟆,int和float都默認(rèn)為highp級(jí)別垒探。而fragment shader中沒(méi)有默認(rèn)精度,必須設(shè)置精度描述符怠李,一般設(shè)為mediump即可圾叼。
2、attribute只作用于vertex shader中捺癞,表示接收的變量夷蚊。在vertex shader中,若沒(méi)有attribute則為輸出變量(輸出至fragment shader)翘簇。
3撬码、vertex shader的默認(rèn)輸出變量至少應(yīng)該有g(shù)l_Position,另外有兩個(gè)可選的gl_FrontFacing和gl_PointSize版保。而fragment shader只有唯一的varying輸出變量gl_FragColor呜笑。
4、Uniform是全局變量彻犁,可用于vertex shader和fragment shader叫胁。在vertex shader中通常是變換矩陣、光照參數(shù)汞幢、顏色等驼鹅,。在fragment shader中通常是霧化參數(shù)、紋理參數(shù)等输钩。OpenGLES 2.0規(guī)定所有實(shí)現(xiàn)應(yīng)該支持的最大vertex shader的uniform變量個(gè)數(shù)不能少于128個(gè)豺型,而最大fragment shader的uniform變量個(gè)數(shù)不能少于16個(gè)。
5买乃、simpler是一種特殊的uniform姻氨,用于呈現(xiàn)紋理,可用于vertex shader和fragment shader.
本文主要參考:http://learnopengl-cn.readthedocs.io/zh/latest/
http://blog.csdn.net/icetime17/article/details/50436927