【OpenGL ES】着色语言GLSL
OpenGL ES 3.0頂點(diǎn)著色器和片段著色器的第一行總是聲明著色器版本(如#version 300 es),通知著色器編譯器預(yù)期在著色器中出現(xiàn)的語法和結(jié)構(gòu),檢查著色器語法,默認(rèn)為OpenGL ES著色語言的1.00版本,用于OpenGL ES 2.0,對(duì)于OpenGL ES 3.0,版本號(hào)為3.00,增加了許多新功能,包括非方矩陣、全整數(shù)支持、插值限定符、統(tǒng)一變量塊、布局限定符、新的內(nèi)建函數(shù)、全循環(huán)、全分支支持以及無限的著色器指令長度等。下面介紹OpenGL ES著色語言3.00版本的用法。
1、數(shù)據(jù)類型
著色語言中的變量必須以某個(gè)數(shù)據(jù)類型作為聲明(如mat4 mvp;),這些數(shù)據(jù)類型如下所示。
標(biāo)量:float、int、uint、bool
浮點(diǎn)向量:float、vec2、vect3、vec4
整數(shù)向量:int、ivec2、ivec3、ivec4
無符號(hào)整數(shù)向量:uint、uvec2、uvec3、uvec4
布爾向量:bool、bvec2、bvect3、bvec4
浮點(diǎn)矩陣:mat2(mat2x2)、mat2x3、mat2x4、mat3x2、mat3(mat3x3)、mat3x4、mat4x2、mat4x3、mat4(ma4x4)
2、變量及變量類型
著色語言中的變量類型有著嚴(yán)格的要求,不允許像C/C++那樣進(jìn)行隱式類型轉(zhuǎn)換,也就是說不同類型的變量之間不能進(jìn)行簡單的賦值和運(yùn)算,必須使用對(duì)應(yīng)類型的構(gòu)造函數(shù)(類型名)進(jìn)行顯式類型轉(zhuǎn)換,但用法比較靈活,變量的值可以在變量聲明時(shí)初始化或者在后面賦值,下面舉幾個(gè)簡單的例子。
float myFloat = 1.0; float myFloat2 = 1; // error bool myBool = true; int myInt = 0; int myInt2 = 0.0; // error myFloat = float(myBool); // bool > float myFoat = float(myInt); // int > float myBool = bool(myInt); // int > bool vec4 myVec4 = vec4(1.0); // myVec4 = {1.0, 1.0, 1.0, 1.0}; vec3 myVec3 = vec3(1.0, 0.0, 0.5); // myVec3 = {1.0, 0.0, 0.5}; vec3 temp = vec3(myVec3); // temp = myVec3 vec2 myVec2 = vec2(myVec3); // myVec2 = {myVec3.x, myVec3.y}; myVec4 = vec4(myVec2, temp); // myVec4 = {myVec2.x, myVec2.y, temp.x, temp.y}; mat4 myMat4 = mat4(1.0); // 4x4單位矩陣 mat3 myMat3 = mat3(1.0, 0.0, 0.0, // 列優(yōu)先,第一列0.0, 1.0, 0.0, // 第二列0.0, 0.0, 1.0); // 第三列 const float zero = 0.0; const float pi = 3.14159; const vec4 read = vec4(1.0, 0.0, 0.0, 1.0); const mat4 identity = mat4(1.0); float floatArray[4] = float[4](1.0, 1.0, 1.0, 1.0); int intArray[4] = int[](1, 1, 1, 1); vec2 vec2Array[2] = vec2[2](vec2(1.0), vec2(1.0)); struct fogStruct { vec4 color; float begin; float end; } fogVar; fogVar = fogStruct(vec4(1.0, 0.0, 0.0, 1.0), // color0.5, // begin2.0); // end vec4 color = fogVar.color; float begin = fogVar.begin; float end = fogVar.end;常量(只讀變量)在聲明時(shí)使用const限定符并初始化。數(shù)組在聲明時(shí)指定數(shù)組大小,使用數(shù)組構(gòu)造函數(shù)(類型名加一對(duì)方括號(hào))進(jìn)行初始化,數(shù)組構(gòu)造函數(shù)可以不指定大小,但數(shù)組的元素個(gè)數(shù)需保持一致。結(jié)構(gòu)struct是一種自定義類型,用法同C語言一樣,不同的是使用結(jié)構(gòu)構(gòu)造函數(shù)(結(jié)構(gòu)類型名)進(jìn)行初始化。向量的各個(gè)分量有兩種方法,一種是使用數(shù)組下標(biāo)[index],index表示向量分量的索引,另一種是使用運(yùn)算符.,后面跟著向量分量的名字,名字有三種表示形式,坐標(biāo)形式的{x, y, z, w},顏色形式的{r, g, b, a},紋理形式的{s, t, p, q},三種形式的各個(gè)分量依次與向量的各個(gè)分量依次對(duì)應(yīng),如x、r、s對(duì)應(yīng)于向量的第一個(gè)分量,w、a、q對(duì)應(yīng)于向量的第四個(gè)分量,而且這三種形式不能混用,下面舉幾個(gè)簡單的例子。
vec3 myVec3 = vec3(0.0, 1.0, 2.0); // myVec3 = {0.0, 1.0, 2.0}; vec3 temp; temp = myVec3.xyz; // temp = {0.0, 1.0, 2.0}; temp = myVec3.xxx; // temp = {0.0, 0.0, 0.0}; temp = myVec3.zyx; // temp = {2.0, 1.0, 0.0};3、運(yùn)算符
* 乘 / 除 % 取模 + 加 - 減 ++ 遞增 -- 遞減 = 賦值 += -= *= /= 算術(shù)賦值 == != < > <= >= 比較運(yùn)算符 && 邏輯與 ^^ 邏輯異或 || 邏輯或 << >> 移位 & ^ | 按位與、異或、或 ?: 選擇 , 序列著色語言支持以上運(yùn)算符,對(duì)于二元運(yùn)算符*、/、+、-,變量的類型必須是浮點(diǎn)或者整數(shù),但*可以在浮點(diǎn)、向量和矩陣之間進(jìn)行運(yùn)算,除了==和!=之外,比較運(yùn)算符<、<=、>、>=只能用于標(biāo)量值,要比較向量,可以使用內(nèi)建函數(shù),逐個(gè)分量進(jìn)行比較,下面舉幾個(gè)簡單的例子。
float myFloat; vec4 myVec4; mat4 myMat4; myVec4 = myVec4 * myFloat; myVec4 = myVec4 * myVec4; myMat4 = myMat4 * myFloat; myMat4 = myMat4 * myVec4; myMat4 = myMat4 * myMat4;4、函數(shù)
著色語言提供了許多內(nèi)建函數(shù),如dot計(jì)算兩個(gè)向量的點(diǎn)積,pow計(jì)算標(biāo)量的冪次等,可以處理通常在著色器中進(jìn)行的各種計(jì)算任務(wù),當(dāng)然也可以像C語言一樣自定義函數(shù),最明顯的不同之處在于函數(shù)參數(shù)的傳遞方法,有三個(gè)參數(shù)限定符in、inout、out,其中默認(rèn)限定符in表示參數(shù)按值傳遞,不可修改,inout表示參數(shù)按引用傳遞,可以修改,out表示參數(shù)不傳入,但可以修改,下面是一個(gè)自定以函數(shù)的聲明。
vec4 myFunc(inout float myFloat, // inoutout vec4 myVec4, // outmat4 myMat4); // default in函數(shù)用法有一個(gè)限制,不能遞歸,這一限制的原因是,某些實(shí)現(xiàn)通過把函數(shù)代碼真正地內(nèi)嵌到為GPU生成的最終程序來實(shí)施函數(shù)調(diào)用,著色語言有意地構(gòu)造為允許這種內(nèi)嵌式實(shí)現(xiàn),以支持沒有堆棧的GPU。
5、控制流和預(yù)處理器
著色語言支持if-else、while、do-while,條件語句中測試表達(dá)式求出的必須是一個(gè)布爾值。在大部分GPU架構(gòu)中,頂點(diǎn)或者片段并行批量執(zhí)行,GPU通常要求一個(gè)批次中的所有頂點(diǎn)或者片段計(jì)算控制流語句中的所有分支或者循環(huán)迭代,如果批次中的頂點(diǎn)或者片段執(zhí)行不同的路徑,則批次中的其它頂點(diǎn)、片段通常都必須也執(zhí)行該路徑,批次的大小特定于GPU,往往需要進(jìn)行剖析,以確定在特定架構(gòu)中使用控制流的性能意義,但是,經(jīng)驗(yàn)法則是,應(yīng)該嘗試限制跨頂點(diǎn)、片段的擴(kuò)散性控制流和循環(huán)迭代的使用。下面是一個(gè)if-else控制流語句。
if (color.a < 0.25) { color *= color.a; } else { color = vec4(0.0); }著色語言支持如下預(yù)處理器指令:
#define #undefine #ifdef #ifndef #if #elif #else #endif #error #pragma #extension預(yù)處理器指令類似于C語言,但也有不同之處。定義宏時(shí)不能帶有參數(shù),if、elif和else指令可以使用defind測試來查看宏是否已經(jīng)定義,著色語言內(nèi)置宏包括__LINE__(著色器中的行號(hào))、__FILE__(OpenGL ES 3.0中為0)、__VERSION__(著色語言版本,如300)、GL_ES(1)。error指令將會(huì)導(dǎo)致在著色器編譯時(shí)出現(xiàn)編譯錯(cuò)誤,并在信息日志中放入對(duì)應(yīng)的消息。pragma指令用于為編譯器指定特定于實(shí)現(xiàn)的指令。extension用于啟用和設(shè)置擴(kuò)展的行為,格式為#extension extension_name : behavior,extension_name為一個(gè)擴(kuò)展名或者是影響全部擴(kuò)展的默認(rèn)關(guān)鍵字all,behavior可以是關(guān)鍵字enable、disable、require、warn。
6、uniform
uniform是著色語言中的變量類型限定符,存儲(chǔ)應(yīng)用程序通過OpenGL ES API傳入著色器的只讀值,統(tǒng)一變量在全局作用域中聲明,其命名空間在頂點(diǎn)著色器和片段著色器中都是共享的,也就是說,如果頂點(diǎn)和片段著色器一起鏈接到一個(gè)程序?qū)ο?#xff0c;它們就會(huì)共享同一組統(tǒng)一變量,因此,如果在頂點(diǎn)著色器和片段著色器中都聲明一個(gè)統(tǒng)一變量,那么兩個(gè)聲明必須匹配,應(yīng)用程序通過API加載統(tǒng)一變量時(shí),它的值在頂點(diǎn)和片段著色器中都可用。統(tǒng)一變量通常保存在硬件中,這個(gè)區(qū)域通常被稱作常量存儲(chǔ),不同于編譯時(shí)已知道值的常數(shù)變量,而是硬件中為存儲(chǔ)常量值而分配的特殊空間,因?yàn)槌A看鎯?chǔ)的大小一般是固定的,所以程序中可以使用的統(tǒng)一變量數(shù)量受到限制,這種限制可以通過讀取內(nèi)建變量gl_MaxVertexUniformVectors和gl_MaxFragmentUniformVectors的值來確定,或者用glGetIntegerv查詢GL_MAX_VERTEX_UNIFORM_VECTORS或GL_MAX_FRAGMENT_UNIFORM_VECTORS。OpenGL ES 3.0實(shí)現(xiàn)必須提供至少256個(gè)頂點(diǎn)統(tǒng)一變量和224個(gè)片段統(tǒng)一變量。下面舉幾個(gè)簡單的例子。
uniform mat4 viewProjMatrix; uniform mat4 viewMatrix; uniform vec3 lightPosition;uniform還有一個(gè)更高級(jí)的概念,統(tǒng)一變量緩沖區(qū)對(duì)象,比單獨(dú)的統(tǒng)一變量更高效,在著色語言中對(duì)應(yīng)于統(tǒng)一變量塊,下面是一個(gè)統(tǒng)一變量塊,塊名稱TransformBlock供應(yīng)用程序使用,如作為glGetUnformBlockIndex的參數(shù),塊中的變量在著色器中都可以直接訪問,就像常規(guī)形式聲明的變量一樣。
uniform TransformBlock { mat4 matViewProj; mat3 matNormal; mat3 matTexGen; };7、布局
先看下面的例子:
layout(location = 0) in vec4 a_position; layout(shared, column_major) uniform; // default if not specified layout(packed, row_major) uniform; layout(std140) uniform TransformBlock { mat4 matViewProj; layout(row_major) mat3 matNormal; mat3 matTexGen; };布局layout是可選的限定符,指定屬性變量或統(tǒng)一變量(塊)在內(nèi)存中的布局方式,有多種設(shè)置方式,其中l(wèi)ocation用于指定頂點(diǎn)輸入和片段輸出的索引;shared表示多個(gè)著色器或者多個(gè)程序中統(tǒng)一變量塊的內(nèi)存布局相同,不同定義中的row_major、column_major值必須相等,這個(gè)為默認(rèn)選項(xiàng);packed表示編譯器可以優(yōu)化統(tǒng)一變量塊的內(nèi)存布局,必須查詢偏移位置,而且統(tǒng)一變量塊無法在頂點(diǎn)、片段著色器或者程序間共享;std140表示OpenGL ES 3.0規(guī)范的標(biāo)準(zhǔn)統(tǒng)一變量塊布局;column_major表示矩陣在內(nèi)存中以列優(yōu)先順序布局,這是個(gè)默認(rèn)選項(xiàng);row_major表示矩陣在內(nèi)存中以行優(yōu)先順序布局。
8、著色器的輸入輸出
著色器的輸入、輸出變量分別使用in、out關(guān)鍵字進(jìn)行修飾,頂點(diǎn)輸入變量用于指定頂點(diǎn)著色器中每個(gè)頂點(diǎn)的輸入,通常存儲(chǔ)位置、法線、紋理坐標(biāo)和顏色這樣的數(shù)據(jù),和統(tǒng)一變量一樣,底層硬件通常在可輸入頂點(diǎn)著色器的屬性變量數(shù)目上有限制,由內(nèi)建常量gl_MaxVertexAttribs給出,也可以使用glGetIntegerv查詢GL_MAX_VERTEX_ATTRIBS得到,OpenGL ES 3.0實(shí)現(xiàn)可支持的最小屬性為16個(gè)。每個(gè)頂點(diǎn)著色器將在一個(gè)或多個(gè)輸出變量中輸出需要傳遞給片段著色器的數(shù)據(jù),然后,這些變量也會(huì)在片段著色器中聲明為類型相符的輸入變量,在光柵化階段中對(duì)圖元進(jìn)行線性插值,同樣,底層硬件通常限制頂點(diǎn)著色器輸出、片段著色器輸入(硬件上稱作插值器)的數(shù)量,由內(nèi)建變量gl_MaxVertexOutputVectors、glMaxFragmentInputVectors給出,或者使用glGetIntegerv查詢GL_MAX_VERTEX_OUTPUT_COMPONENTS、GL_MAX_FRAGMENT_INPUT_COMPONENTS得到總分量值數(shù)量而非向量數(shù)量,OpenGL ES 3.0實(shí)現(xiàn)可以支持的最小頂點(diǎn)輸出向量數(shù)為16,最小片段輸入向量數(shù)為15。片段著色器輸出一個(gè)或多個(gè)顏色,一般只渲染到一個(gè)顏色緩沖區(qū),layout是可選的,但是,當(dāng)渲染到多個(gè)渲染目標(biāo)(MRT)時(shí),可以使用layout指定每個(gè)輸出前往的渲染目標(biāo),這時(shí)在片段著色器中會(huì)有一個(gè)輸出變量,該值將是傳遞給管線逐片段操作部分的輸顏色。注意,與頂點(diǎn)著色器輸入不同,頂點(diǎn)著色器輸出和片段著色器輸入變量不能有布局限定符layout,OpenGL ES實(shí)現(xiàn)自動(dòng)選擇位置。下面是頂點(diǎn)、片段著色器的例子。
// vertex shader #version 300 es uniform mat4 u_matViewProjection; layout(location = 0) in vec4 a_position; layout(location = 1) in vec3 a_color; out vec3 v_color; void main() {gl_Position = u_matViewProjection * a_position;v_color = a_color; } // fragment shader #version 300 es precision mediump float; in vec3 v_color; layout(location = 0) out vec4 o_fragColor; void main() {o_fragColor = vec4(v_color, 1.0); }9、精度
精度限定符用以指定著色器中任何基于浮點(diǎn)數(shù)或者整數(shù)的變量的計(jì)算精度,包括低、中、高三種精度,關(guān)鍵字分別為lowp、mediump、highp,還有一個(gè)關(guān)鍵字precision在著色器的開頭用以指定默認(rèn)精度,精度較低時(shí)運(yùn)行著色器時(shí)可能更快,或者電源效率更高,在沒有正確使用精度限定符時(shí)可能造成偽像。在頂點(diǎn)著色器中,如果沒有指定默認(rèn)精度,則int和float的默認(rèn)精度都為highp,片段著色器的規(guī)則于此不同,浮點(diǎn)值沒有默認(rèn)的精度值,必須進(jìn)行適當(dāng)?shù)穆暶鳌P枰⒁獾氖?#xff0c;精度限定符指定的精度與OpenGL ES特定實(shí)現(xiàn)的精度和范圍有關(guān)。下面是精度限定符的例子。
precision highp float; precision mediump int; highp vec4 position; varying lowp vec4 color; mediump float specularExp;10、不變性
著色器需要編譯,而編譯器可能進(jìn)行導(dǎo)致指令重新排序的優(yōu)化,這種指令重排意味著兩個(gè)著色器之間的等價(jià)計(jì)算不能保證產(chǎn)生完全相同的結(jié)果,這種不一致性在多遍著色器特效時(shí)尤其可能成為問題,在這種情況下,相同的對(duì)象用alpha混合繪制在自身上方,如果用于計(jì)算輸出位置的數(shù)值的精度不完全一樣,精度差異就會(huì)導(dǎo)致偽像,這個(gè)問題突出表現(xiàn)為深度沖突,每個(gè)像素的深度Z精度差異導(dǎo)致不同遍著色相互之間有微小的偏移。為此,引入了invariant不變性關(guān)鍵字,可以用于任何可變的頂點(diǎn)著色器輸出,提供了一種途徑來規(guī)定用于計(jì)算輸出的相同計(jì)算的值必須相同,雖然不變性表示在指定GPU上的計(jì)算結(jié)果會(huì)得到相同的結(jié)果,但是并不意味著計(jì)算在任何OpenGL ES實(shí)現(xiàn)之間保持不變。invariant可以用于變量聲明,或者用于已經(jīng)聲明的變量,例子如下。
#version 300 es uniform mat4 u_viewProjMatrix; layout(location = 0) in vec4 a_vertex; invariant gl_Position; void main() { // will be the same value in all shaders with the same u_viewProjMatrix and a_vertex gl_Position = u_viewProjMatrix * a_vertex; }另外,也可以用如下pragma指令讓所有變量全部不變,因?yàn)榫幾g器需要保證不變性,所以可能限制它所做的優(yōu)化,導(dǎo)致性能下降,需要謹(jǐn)慎使用。
#pragma STDGL invariant(all)11、插值
插值限定符用于頂點(diǎn)著色器的輸出和片段著色器的輸入,默認(rèn)為smooth,即平滑著色,來自頂點(diǎn)著色器的輸出變量在圖元中線性插值,片段著色器接收線性插值之后的數(shù)值作為輸入,另一種插值方式為平面著色,圖元中的值沒有進(jìn)行插值,而是將其中一個(gè)頂點(diǎn)視為驅(qū)動(dòng)頂點(diǎn),該頂點(diǎn)的值被用于圖元中的所有片段。插值限定符還有一個(gè)關(guān)鍵字centroid,表示質(zhì)心采樣,使用多重采樣渲染時(shí),centroid可用于強(qiáng)制插值發(fā)生在被渲染圖元內(nèi)部,否則在圖元的邊緣可能出現(xiàn)偽像。下面是質(zhì)心采樣、平滑著色的著色器輸入輸出變量。
smooth centroid out vec3 v_color; // vertex shader output smooth centroid in vec3 v_color; // fragment shader input from vertex shader12、統(tǒng)一變量和插值器打包
前面提到了統(tǒng)一變量和頂點(diǎn)著色器輸出變量與片段著色器輸入變量在底層硬件中可用于每個(gè)變量存儲(chǔ)的資源是固定的,著色器可能聲明各種類型的統(tǒng)一變量和著色器輸入輸出變量,包括標(biāo)量、向量和矩陣,那么,這些變量如何映射到硬件上的可用物理空間呢?在OpenGL ES 3.0中,這個(gè)問題通過打包規(guī)則處理,該規(guī)則定義統(tǒng)一變量和插值器映射到物理存儲(chǔ)空間的方式,打包規(guī)則(layout(packed))基于物理存儲(chǔ)空間被組織為一個(gè)每個(gè)存儲(chǔ)位置四列和一行的網(wǎng)格的概念,打包規(guī)則尋求打包變量,使生成代碼的復(fù)雜度保持不變,也就是說,打包規(guī)則不進(jìn)行重排序操作,因?yàn)橹嘏判虿僮餍枰幾g器生成合并未打包數(shù)據(jù)的額外指令,而是試圖在不對(duì)運(yùn)行時(shí)性能產(chǎn)生負(fù)面影響的情況下,優(yōu)化物理地址空間的使用。需要注意的是,打包影響統(tǒng)一變量和頂點(diǎn)著色器輸出與片段著色器輸入的計(jì)數(shù)方式,編寫保證能夠在所有OpenGL ES 3.0實(shí)現(xiàn)上運(yùn)行的著色器,就不應(yīng)該使用打包之后超過最小運(yùn)行存儲(chǔ)大小的統(tǒng)一變量和插值器。例如下面的幾個(gè)統(tǒng)一變量,如果完全不進(jìn)行打包,m占3行,f占6行,v占1行,共需要10行才能存儲(chǔ)這些變量,許多常量存儲(chǔ)空間將被浪費(fèi),如果使用打包規(guī)則,只需使用6個(gè)物理常量位置,數(shù)組f的元素會(huì)跨越行的邊界,原因是GPU通常會(huì)按照向量位置索引對(duì)常量存儲(chǔ)進(jìn)行索引,打包必須使數(shù)組跨越行邊界,這樣索引才能起作用。
uniform mat3 m; uniform float f[6]; uniform vec3 v;非打包——
打包——
總結(jié)
以上是生活随笔為你收集整理的【OpenGL ES】着色语言GLSL的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硬盘都有哪些型号和规格大小
- 下一篇: Python 实现一个自动下载小说的简易