###寫在前面
[上一篇筆記](/articles/紋理對映(貼圖)-webgl 筆記 8/) 後,我們就結束了 WebGL 最最基礎的部分,本篇筆記全面介紹 GLSL ES 語法,相對獨立,然後就是矩陣矩陣矩陣(變換、視角、光照、控制複雜模型等等,全都是在搞矩陣)
##一。概述
GLSL ES 是在 GLSL(OpenGL 著色器語言)的基礎上,刪除和簡化了一部分功能後形成的,目標平臺是消費電子產品和嵌入式裝置,比如智慧手機、遊戲主機等等,ES 版本主要降低了硬體功耗,減少了性能開銷
P.S. 實際上 WebGL 並不支援 GLSL ES 的所有特性,支援的是 GLSL ES 1.00 版本的一個子集
##二。基本語法規則
-
大小寫敏感
-
語句末尾必須要有分號
-
從 main 函式開始執行
-
函式宣告中不能省略返回值型別(無返回值就是 void,C 語言可以省略,但這裡不行)
-
註釋語法和 C 語言一致(單行//,多行/**/)
##三。變數和基本資料型別
###1. 基本資料型別
只支援 2 種基本資料型別:
-
數值型別:整數,浮點數
-
布林型別:true 和 false2 個布林常量
注意:不支援字串
###2. 變數
- 變數宣告
和 C 語言一樣,型別 + 變數名,變數命名規則也一樣,基本型別只有 int、float 和 bool
- 型別轉換
沒有隱式型別轉換,也不支援3f 這樣的型別後綴,但提供了型別轉換函式:int()、float() 和 bool(),都只接受其餘 2 種基本資料型別
- 運算子
不支援位運算,其它和 C 語言一致,也支援 3 目選擇,邏輯與(&&)和邏輯或(||)也有短路特性
- 作用域
與 C 語言一致,函式內部宣告的是區域性變數,外面宣告的就是全域性變數
##四。複雜資料型別
###1. 矢量(vec)
支援 2、3、4 維矢量,按分量資料型別分為 3 類:
-
vec2、vec3、vec4:分量是浮點數
-
ivec2、ivec3、ivec4:分量是整數
-
bvec2、bvec3、bvec4:分量是布林值
建構函式名和型別名一致,比如 vec4(1.0) 返回 4 維向量 [1.0, 1.0, 1.0, 1.0],如果像這樣只傳入一個引數,會把所有分量都賦值為該值,如果傳入的引數不止一個但比需要的引數數目少,就會報錯。例如,vec4(1.0) 和 vec4(1.0, 1.0, 1.0, 1.0) 都沒問題,而 vec4(1.0, 1.0) 和 vec4(1.0, 1.0, 1.0) 就會報錯
此外,還可以傳入矢量來構造新矢量,或者用現有矢量組合出新矢量,例如:
vec3 v3 = vec3(0.0, 0.5, 1.0); // [0.0, 0.5, 1.0]
vec2 v2 = vec2(v3); // [0.0, 0.5],截取 v3 的前兩個分量
vec4 v4 = vec4(v2, vec4(1.5)); // [0.0, 0.5, 1.5, 1.5],組合 v2 和新矢量 [1.5, 1.5, 1.5, 1.5]
總之,引數可以來自基本值也可以來自其它矢量,但如果引數數量不夠且數量不為 1 就報錯
訪問矢量的分量有 2 種方式,如下:
v4 = vec4(1, 2, 3, 4);
// .分量名
v4.x, v4.y, v4.z, v4.w // 齊次座標
v4.r, v4.g, v4.b, v4.a // 色值
v4.s, v4.t, v4.p, v4.q // 紋理座標
// [] 運算子
v4[0], v4[1], v4[2], v4[3]
點號分量名方式只是為了添上語義,等價於方括號運算子,可以理解為別名,例如 v4[0] 的別名是 v4.x、v4.r 以及 v4.s。更有趣的是,還可以組合使用,比如 v4.xz 返回的 2 維向量,但此時分量名不能混用(v4.sz 是不對的)
注意:方括號中的值必須是常量索引值,要麼是整數字面量,要麼是 const 修飾的變數、迴圈索引(流程控制部分再解釋),或者這 3 者組成的表示式
###2. 矩陣(mat)
只支援 2、3、4 維方陣,只支援浮點數型別分量:mat2、mat3、mat4
特別注意:矩陣元素是列主序的,例如:
mat4 m4 = mat4(
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
);
// 生成的矩陣是:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
可能與想象的大不一樣,但確實是這樣,同樣地,矩陣的建構函式也可以接受其它矢量或者矩陣,無論引數來自哪裡,最終這組數都將按列主序來構造矩陣,例如:
vec2 v2_1 = vec2(1, 2);
vec2 v2_2 = vec2(3, 4);
mat2 m2 = mat2(v2_1, v2_2);
// 生成的矩陣是:
1 3
2 4
同樣,如果引數不夠且引數數量不止 1 個,就會報錯
訪問矩陣元素一般使用方括號運算子,例如:
m4[0] // 第 1 列元素,4 維向量
m4[0][1] // 第 1 列第 2 行的元素,基本值
m4[0].y // 同上
注意:同樣,方括號中的值也必須是常量索引值
###3. 結構體(struct)
類似於 C 語言的結構體,如下:
// 宣告自定義結構體型別
struct light {
vec4 color;
vec3 pos;
};
// 宣告結構體型別變數
light l1, l2; // 等價於 C 語言的 struct light l1, l2;
// 也可以像 C 語言那樣在宣告結構體的同時宣告結構體型別的變數
struct light {
vec4 color;
vec3 pos;
} l3;
宣告結構體後會自動生成同名建構函式,引數順序必須與結構體中的成員順序一致,例如:
light l4 = light(vec4(1.0), vec3(0.0));
用點運算子可以直接訪問結構體變數的成員,結構體本身只支援賦值(=)和 2 個比較運算子(==、!=),如果 2 個結構體成員及順序都一樣,則相等
###4. 陣列(xArray)
只支援1 維陣列,而且不支援 pop、push 等操作,陣列宣告方式和 C 語言一致,例如:
float a[10];
vec4 arr[3];
同樣,方括號中的值只能是常量索引值,而且陣列不能在宣告的同時初始化,必須顯示地對每個元素進行賦值
###5. 取樣器(sampler)
可以通過取樣器變數訪問紋理,取樣器變數只有 2 種:sampler2D 和 samplerCube,而且取樣器變數只能是 uniform 變數,例如:
uniform sampler2D u_Sampler;
唯一能給取樣器變數賦的值是紋理單元編號,比如 gl.uniformi(u_Sampler, 0) 把紋理單元編號 0 傳遞給著色器,所以取樣器變數數量有限,片元著色器中最多 8 個,頂點著色器中沒有取樣器變數
此外,除了 =、== 和 != 之外,取樣器變數不可以作為運算元參與運算
##五。矢量運算和矩陣運算
矢量和矩陣只支援比較運算子中的 == 和 !=,運算賦值(+=, -=, *=, /=)操作作用在矢量和矩陣上實際效果是對每一個分量進行運算賦值
###1. 矢量和浮點數運算
v2 + f; // v2[0] + f
// v2[1] + f
###2. 矢量運算
v2_1 + v2_2; // v2_1[0] + v2_2[0]
// v2_1[1] + v2_2[1]
###3. 矩陣和浮點數運算
m2 + f; // m2[0] + f
// m2[1] + f
// m2[2] + f
// m2[3] + f
###4. 矩陣右乘矢量
m3 * v3; // m3[0][0] * v3[0] + m3[1][0] * v3[1] + m3[2][0] * v3[2]
// m3[0][1] * v3[0] + m3[1][1] * v3[1] + m3[2][1] * v3[2]
// m3[0][2] * v3[0] + m3[1][2] * v3[1] + m3[2][2] * v3[2]
###5. 矩陣左乘矢量
v3 * m3; // v3[0] * m3[0][0] + v3[1] * m3[0][1] + v3[2] * m3[0][2]
// v3[0] * m3[1][0] + v3[1] * m3[1][1] + v3[2] * m3[1][2]
// v3[0] * m3[2][0] + v3[1] * m3[2][1] + v3[2] * m3[2][2]
###6. 矩陣乘矩陣
m3a * m3b; // m3a[0][0] * m3b[0][0] + m3a[1][0] * m3b[0][1] + m3a[2][0] * m3b[0][2]
// m3a[0][0] * m3b[1][0] + m3a[1][0] * m3b[1][1] + m3a[2][0] * m3b[1][2]
// m3a[0][0] * m3b[2][0] + m3a[1][0] * m3b[2][1] + m3a[2][0] * m3b[2][2]
// m3a[0][1] * m3b[0][0] + m3a[1][1] * m3b[0][1] + m3a[2][1] * m3b[0][2]
// m3a[0][1] * m3b[1][0] + m3a[1][1] * m3b[1][1] + m3a[2][1] * m3b[1][2]
// m3a[0][1] * m3b[2][0] + m3a[1][1] * m3b[2][1] + m3a[2][1] * m3b[2][2]
// m3a[0][2] * m3b[0][0] + m3a[1][2] * m3b[0][1] + m3a[2][2] * m3b[0][2]
// m3a[0][2] * m3b[1][0] + m3a[1][2] * m3b[1][1] + m3a[2][2] * m3b[1][2]
// m3a[0][2] * m3b[2][0] + m3a[1][2] * m3b[2][1] + m3a[2][2] * m3b[2][2]
##六。流程控制
###1. 分支
if-else 結構用法與 C 語言和 js 一致,但沒有 switch 語句
###2. 迴圈
只支援for 迴圈,而且只能在初始化表示式(for(;;) 中第一個分號前面的位置)中定義迴圈變數,例如:
for (int i = 0; i < 10; i++) {
//...
}
只允許有一個迴圈變數,而且迴圈變數只能是 int 或者 float,而且條件表示式(for(;;) 中 2 個分號之間的位置)必須是迴圈變數與整形常量的比較,而且在迴圈體內部,迴圈變數不能被賦值
限制比較多,是為了讓編譯器能夠對 for 迴圈進行內聯展開
continue、break 用法與 js 一致,此外,還有一個 discard,只能在片元著色器中使用,表示放棄當前片元,直接處理下一個片元
##七。函式
與 C 語言基本一致,但無法返回陣列,如果返回自定義結構體,結構體成員中也不能有陣列,例如:
float luma(vec4 color) {
return 0.2126 * color.r + 0.7162 * color.g + 0.0722 * color.b;
}
同樣,需要先宣告,後呼叫,否則就要在呼叫之前宣告函式簽名,例如:
float luma(vec4); // 宣告函式簽名
void main() {
luma(color);
}
float luma...
此外,不允許遞迴,這個限制也是為了編譯器能夠對函式進行內聯展開
在 GLSL ES 中有幾個引數限定字,如下:
in 值傳遞,可以省略,預設就是值傳遞
const in 值傳遞,在函式內部無法修改引數
out 地址傳遞
inout 地址傳遞,傳入的引數必須已經初始化過了
##八。儲存限定字
attribute、uniform、varying,區別如下:
- attribute
只能出現在頂點著色器中,只能是全域性變數,型別只能是 float 或者分量是 float 的矢量和矩陣。WebGL 環境至少支援 8 個 attribute 變數,用來表示逐頂點的資訊
- uniform
只能是全域性變數,可以用於頂點著色器和片元著色器,可以是除陣列和結構體外的任意型別。WebGL 環境至少支援片元著色器中出現 16 個 uniform 變數,頂點著色器中是 128 個,用來表示各頂點、各片元共用的資料
特殊的:如果頂點著色器和片元著色器中宣告了同名的 uniform 變數,那麼會被 2 個著色器共享
- varying
只能是全域性變數,型別只能是 float 或者分量是 float 的矢量和矩陣。WebGL 環境至少支援 8 個 varying 變數,用來從頂點著色器向片元著色器傳遞資料,但不是直接傳遞,有內插的過程
此外,還有 const 限定字,但不常用
##九。精度限定字
引入精度限定字是為了幫助著色器程式提高執行效率,減少記憶體開支。一般採用中精度:
#ifdef GL_ES
precision mediump float; // 還可以是 highp 和 lowp
#endif
該語句表示後面遇到的所有沒有宣告精度的浮點數都用中精度,只有 float 型別沒有設定預設精度,所以在著色器源程式中必須先設定 float 的預設精度再使用 float 型別
片元著色器是否支援高精度需要裝置支援,可以通過檢查宏來檢測:GL_FRAGMENT_PRECISION_HIGH
##十。預處理指令
類似於 C 語言,常用的 3 種預處理指令如下:
// 1
#if 條件表示式
如果條件表示式為真,執行這部分
#endif
// 2
#ifdef 宏
如果宏存在,執行這部分
#endif
// 3
#ifndef 宏
如果宏不存在,執行這部分
#endif
// 定義宏
#define 宏名 宏內容
// 解除宏定義
#undef 宏名
比較有用的是:#version 101 可以指定使用 GLSL ES1.01 版本,該指令必須在著色器頂部,之前只能是空白或者註釋
###參考資料
- 《WebGL 程式設計指南》
暫無評論,快來發表你的看法吧