OpenGL ES 着色器語言:OpenGL ES Shading Language,下面簡寫爲 ES SL 或 SL。
着色器版本指定
#version 300 es
如果沒有指定,則默認爲 1.00,這是 OpenGL ES 2.0 使用的版本,在 OpenGL ES 3.0 中,制定規範的作者決定匹配 OpenGL ES 和 ES SL 的版本,因此直接從 1.0 跳到了 3.0。
變量類型
標量 | float, int, uint, bool | 基於標量的數據類型 |
---|---|---|
浮點向量 | float, vec2, vec3, vec4 | 分別表示有 1、2、3、4 個成員的浮點向量 |
整型向量 | int, ivec2, ivec3, ivec4 | 分別表示有 1、2、3、4 個成員的整型向量 |
無符號整型向量 | uint, uvec2, uvec3, uvec4 | 分別表示有 1、2、3、4 個成員的無符號整型向量 |
布爾向量 | bool, bvec2, bvec3, bvec4 | 分別表示有 1、2、3、4 個成員的布爾向量 |
矩陣 | mat2 (or mat2x2), mat2x3, mat2x4, mat3x2, mat3 (or mat3x3), mat3x4, mat4x2, mat4x3, mat4 (or mat4x4) | 分別表示 2x2、2x3、…、4x4 矩陣 |
構造函數
在 OpenGL ES SL 中,對於類型轉換有很嚴格的限制,即變量只允許使用相同類型的其它變量進行賦值或其它操作。但 SL 中提供了一些構造函數可以用於類型轉換:
float myFloat = 1.0;
float myFloat2 = 1; // 錯誤,不正確的類型轉換
bool myBool = true;
int myInt = 0;
int myInt2 = 0.0; // 錯誤,不正切的類型轉換
myFloat = float(myBool); // 正確,bool -> float
myFloat = 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}
矩陣類型的構造函數則更加靈活,規則如下:
1) 如果只提供了一個標量(基本數據類型),則會用這個值填充矩陣的對角線,比如 mat4(1.0) 將會構造一個 4x4 的單位矩陣
2) 可以使用多個向量構造,比如 mat2 可以使用兩個 vec2 構造
3) 可以使用多個標量構造
需要注意的是,和平時的寫法不同,OpenGL 中的矩陣以列優先的形式排序:
mat3 myMat3 = mat3(1.0, 0.0, 0.0, // 第一列
0.0, 1.0, 0.0, // 第二列
0.0, 1.0, 1.0); // 第三列
向量和矩陣的成員
根據組成 vector 的成員數量,可以通過 {x, y, z, w}, {r, g, b, a}, 或 {s, t, p, q} 來訪問它的成員,x、r、s 永遠指向第一個成員,使用不同的名字僅僅只是爲了方便,但不能混合使用,即不能用 “.xgr” 這樣的形式。使用示例:
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}
也可以使用 “[]” 操作符,記住,矩陣是以列排序的:
mat4 myMat4 = mat4(1.0); // 初始化對角線的值爲 1.0 (單位矩陣)
vec4 col0 = myMat4[0]; // 從矩陣中獲取第 0 列向量
float m1_1 = myMat4[1][1]; // 從矩陣中獲取元素 [1][1]
float m2_2 = myMat4[2].z; // 從矩陣中獲取元素 [2][2]
常量
常量必須在聲明的時候初始化:
const float zero = 0.0;
const float pi = 3.14159;
const vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
const mat4 identity = mat4(1.0);
Structures
結構體定義:
struct fogStruct
{
vec4 color;
float start;
float end;
} fogVar;
結構體被定義的同時,它的構造函數也被定義:
fogVar = fogStruct(vec4(0.0, 1.0, 0.0, 0.0), // color
0.5, // start
2.0); // end
注意形參和實參必須一一對應。
訪問結構體成員:
vec4 color = fogVar.color;
float start = fogVar.start;
float end = fogVar.end;
數組
數組聲明及初始化:
float floatArray[4];
vec4 vecArray[2];
float a[4] = float[](1.0, 2.0, 3.0, 4.0);
float b[4] = float[4](1.0, 2.0, 3.0, 4.0);
vec2 c[2] = vec2[2](vec2(1.0), vec2(1.0));
函數
OpenGL ES SL 中定義函數的方法和 C 差不多,最重要的一點區別是,OpenGL ES SL 中提供了幾個特殊的標識符,表明某一個參數是否會在函數內部被更改:
Qualifier | Description |
---|---|
in | 如果沒有指定,則默認爲 in 表明變量以傳值的形式進入到函數中,不會被更改 |
inout | 表明變量以引用的形式傳遞到函數中,可能會被更改 |
out | 表明變量的值不會被傳遞到函數中,但會在函數返回值被更改 |
例如:
vec4 myFunc(inout float myFloat, // inout 參數
out vec4 myVec4, // out 參數
mat4 myMat4); // in 參數(默認)
值得注意的是,在 OpenGL ES SL 中,函數不能遞歸,這是因爲有一些 GPU 調用函數的方式爲 inline。
內置函數
OpenGL ES 中定義了許多內置函數可供開發者直接使用,比如下面這個計算漫射反光的函數:
vec4 diffuse(vec3 normal, vec3 light, vec4 baseColor) {
return baseColor * dot(normal, light);
}
這個函數調用了一個內置函數 dot,dot 用於計算點積,即矩陣各個對應元素相乘,要求兩個矩陣具有相同的大小。
下面是鏡面反射(specular lighting )的計算過程:
float nDotL = dot(normal, light);
float rDotV = dot(viewDir, (2.0 * normal) * nDotL - light);
float specular = specularColor * pow(rDotV, specularPower);
流程控制語句
使用方法和 C 一樣,但在 OpenGL ES 2.0 中,對循環的使用有着非常嚴格的規則控制,只支持編譯器可以展開的循環;而在 OpenGL 3.0 中,這些限制不再存在。但循環的使用依然會對性能有一些影響,因此,應該儘量限制流程控制語句的使用。
Uniforms
和 attribute(2.0) 或 in(3.0) 數據一樣,Uniforms 也是由外部程序輸入到着色器代碼中的,所不同的是,它被同一 program 中的頂點、片段着色器共享,且着色器不能更改。Uniform 變量存儲在硬件上的“常量存儲區”中,因爲這個區域的尺寸是固定的,因此同一個 program 中的 uniform 數量是有限制的。可以使用 gl_MaxVertexUniformVectors 和 gl_MaxFragmentUniformVectors 確認可使用的最大 uniform 個數,或使用函數 glGetintegerv(參數爲 GL_MAX_VERTEX_UNIFORM_VECTORS 或 GL_MAX_FRAGMENT_UNIFORM_VECTORS) 獲取。
OpenGL ES 3.0 保證最少有 256 個 vertex uniform vector,224 個 fragment uniform vector 可用。
Uniform Blocks
使用 uniform buffer object 通常有幾個優點:
1) uniform 數據可以在多個 program 之間共享(注意僅能設置一次)
2) 允許存儲數量更多的 uniform
3) 在 uniform buffer object 之間切換可能比一次單獨地加載一個 uniform 效率更高
uniform buffer object 通過 uniform block 的形式使用:
#version 300 es
uniform TransformBlock
{
mat4 matViewProj;
mat3 matNormal;
mat3 matTexGen;
};
layout(location = 0) in vec4 a_position;
void main()
{
gl_Position = matViewProj * a_position;
}
in、out
OpenGL ES SL 中另一個特殊類型是 vertex input (or attribute) 變量, 用 in 修飾,用於指定頂點着色器中每一個頂點的輸入,通常用於存儲位置、紋理座標、顏色等信息。代碼示例:
#version 300 es
layout(location=0) in vec4 aColor;
layout(location=1) in vec4 aPosition;
out vec4 vColor;
void main() {
vColor = aColor;
gl_Position = aPosition;
}
layout 限定符相當於 index,用於分辨頂點屬性,如果沒有指定,鏈接器會自動爲其賦值。
和 uniform 變量一樣,硬件對 attribute 變量的數量有限制,可以通過 gl_MaxVertexAttribs 或 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) 查詢,OpenGL ES 3.0 保證最少能支持 16 個,因此如果希望編寫在任何 OpenGL ES 3.0 的實現上都能運行的着色器,應該限制不超過 16 個 attribute。
頂點着色器的輸出變量用 out 修飾,對應於片元着色器中使用 in 修飾對應的變量。這些數據會被輸出到片元着色器中,並且在光柵化過程中,會對圖元進行線性插值。注意不能使用 layout 限定符修飾,程序實現會自動選擇它們的 location 值。同樣的,頂點着色器的輸出變量在硬件中也有數量限制,可以通過 gl_MaxVertexOutputVectors 或 glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS,返回總的成員數量,而不是 vector 的數量) 查詢,OpenGL ES 3.0 最少能支持 16 個。而片元着色器 in 變量的數量限制可以通過 gl_MaxFragmentInputVectors 或 glGetIntegerv (GL_MAX_FRAGMENT_INPUT_COMPONENTS,返回總的成員數量,而不是 vector 的數量) 獲取,OpenGL ES 3.0 最少能支持 15 個。
通常,片元着色器僅需要渲染到一個顏色緩衝區中,但如果存在多個渲染目標,則需要使用 layout 限定符指定,比如:
#version 300 es
precision mediump float;
layout(location = 0) out vec4 fragData0;
layout(location = 1) out vec4 fragData1;
layout(location = 2) out vec4 fragData2;
layout(location = 3) out vec4 fragData3;
void main()
{
fragData0 = vec4 ( 1, 0, 0, 1 );
fragData1 = vec4 ( 0, 1, 0, 1 );
fragData2 = vec4 ( 0, 0, 1, 1 );
fragData3 = vec4 ( 1, 1, 0, 1 );
}
插值限定符
頂點着色器、片元着色器的輸入變量可以指定插值限定符,如果沒有指定,則默認使用 smooth 差值方法渲染,也就是說,圖元會在頂點着色器的輸出變量的值的基礎上進行線性插值,片元着色器接收到的是經過線性插值後的結果。可以在着色器代碼中顯式地指定插值方式:
// 頂點着色器
smooth out vec3 v_color;
// 片段着色器
smooth in vec3 v_color;
另一個插值方式爲 flat shading,這種方式不會在圖元上插值,而是使用一個激發頂點(provoking vertex),該頂點的值會被應用於圖元中的所有片段:
// 頂點着色器
flat out vec3 v_color;
// 片段着色器
flat in vec3 v_color;
最後一個可以加入到插值器中的是 centroid 關鍵字,可以強制插值作用於圖元的內部,否則,在圖元邊緣可能會出現僞像。使用:
// 頂點着色器
smooth centroid out vec3 v_color;
// 片段着色器
smooth centroid in vec3 v_color;
預處理器
和 C 類似,有:
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
以下是一些內置的宏:
__LINE__ // 替換爲着色中的當前行號
__VERSION__ // OpenGL ES 着色器語言版本 (例如 300)
另外有:
#error // 將在着色器編譯期間導致編譯錯誤,並在信息日誌中放置相應的消息
#pragma // 用於改爲編譯器指定 implementation-specificc 指令
還有一個重要的指令是 #extension,用於啓用和設置擴展的行爲:
// 設置某個擴展的行爲
#extension extension_name : behavior
// 設置所有擴展的行爲
#extension all : behavior
behavior 的可選值有:require、enable、warn、disable。
精度限定符
精度限定符可以指定着色器變量進行計算時的精度,可選值有:lowp、mediump、highp。低精度在一些 OpenGL ES 的實現中可能會帶有速度及效率的提升,但精度的降低可能造成僞像(artifacts)現象。但 OpenGL ES 規範中沒有規定必須支持多個精度,因此所有計算以最高精度執行並忽略精度限定符是有效的。
精度限定符可用於修飾 flocating-point 和 integer-based 變量,示例:
highp vec4 position;
varying lowp vec4 color;
mediump float specularExp;
可以在着色器頂部指定默認精度:
precision highp float;
precision mediump int;
如果沒有指定,則 int 和 flocat 都使用 highp 作爲默認精度。但在片元着色器中,必須指定一個默認的 flocat 精度。