OpenGL ES 3.0(二)着色器語言

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 精度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章