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