[OpenGL]Shader語言GLSL語言基礎-變量

OpenGL ES 的渲染管線包含有一個可編程的頂點階段和一個可編程的片段階段。其餘的階段則有固定的功能,應用程序對其行爲的控制非常有限。每個可編程階段中編譯單元的集合組成了一個着色器。在OpenGL ES 2.0 中,每個着色器只支持一個編譯單元。着色程序則是一整套編譯好並鏈接在一起的着色器的集合。着色器 shader 的編寫需要使用着色語言 GL Shader Language(GLSL),GLSL 的語法與 C 語言很類似。

// 頂點着色器 .vsh
attribute vec4 position;
attribute vec4 color;

varying vec4 colorVarying;

void main(void) {
    colorVarying = color;
    gl_Position = position;
}

// 片段着色器 .fsh
varying lowp vec4 colorVarying;

void main(void) {
    gl_FragColor = colorVarying;
}

習慣上,我們一般把頂點着色器命名爲 xx.vsh,片段着色器命名爲 xx.fsh。當然,你喜歡怎麼樣就怎麼樣~

和 C 語言程序對應,用 GLSL 寫出的着色器,它同樣包括:

  • 變量 position
  • 變量類型 vec4
  • 限定符 attribute
  • main 函數
  • 基本賦值語句 colorVarying = color
  • 內置變量 gl_Position

這一切,都是那麼像…所以,在掌握 C 語言的基礎上,GLSL 的學習成本是很低的。

變量

變量及變量類型

變量類別 變量類型 描述
void 用於無返回值的函數或空的參數列表
標量 float, int, bool 浮點型,整型,布爾型的標量數據類型
浮點型向量 float, vec2, vec3, vec4 包含1,2,3,4個元素的浮點型向量
整數型向量 int, ivec2, ivec3, ivec4 包含1,2,3,4個元素的整型向量
布爾型向量 bool, bvec2, bvec3, bvec4 包含1,2,3,4個元素的布爾型向量
矩陣matrix mat2, mat3, mat4 尺寸爲2x2,3x3,4x4的浮點型矩陣
紋理句柄 sampler2D, samplerCube 表示2D,立方體紋理的句柄

除上述之外,着色器中還可以將它們構成數組或結構體,以實現更復雜的數據類型。
GLSL中沒有指針類型。

變量構造器和類型轉換

對於變量運算,GLSL 中有非常嚴格的規則,即只有類型一致時,變量才能完成賦值或其它對應的操作。可以通過對應的構造器來實現類型轉換。

標量

標量對應 C 語言的基礎數據類型,它的構造和 C 語言一致,如下:

float myFloat = 1.0;
bool myBool = true;

myFloat = float(myBool); 	// bool -> float
myBool = bool(myFloat);     // float -> 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, 0.0);   // myVec4 = {myVec2.x, myVec2.y , temp, 0.0 }

矩陣

矩陣的構造方法則更加靈活,有以下規則:

  • 如果對矩陣構造器只提供了一個標量參數,該值會作爲矩陣的對角線上的值。例如 mat4(1.0) 可以構造一個 4 × 4 的單位矩陣
  • 矩陣可以通過多個向量作爲參數來構造,例如一個 mat2 可以通過兩個 vec2 來構造
  • 矩陣可以通過多個標量作爲參數來構造,矩陣中每個值對應一個標量,按照從左到右的順序

除此之外,矩陣的構造方法還可以更靈活,只要有足夠的組件來初始化矩陣,其構造器參數可以是標量和向量的組合。在 OpenGL ES 中,矩陣的值會以列的順序來存儲。在構造矩陣時,構造器參數會按照列的順序來填充矩陣,如下:

mat3 myMat3 = mat3(1.0, 0.0, 0.0,  // 第一列
                   0.0, 1.0, 0.0,  // 第二列
                   0.0, 1.0, 1.0); // 第三列

向量和矩陣的分量

單獨獲得向量中的組件有兩種方法:即使用 “.” 符號或使用數組下標方法。依據構成向量的組件個數,向量的組件可以通過 {x, y, z, w} , {r, g, b, a} 或 {s, t, r, q} 等 swizzle 操作來獲取。之所以採用這三種不同的命名方法,是因爲向量常常會用來表示數學向量、顏色、紋理座標等。其中的x、r、s 組件總是表示向量中的第一個元素,如下表:

分量訪問符 符號描述
(x,y,z,w) 與位置相關的分量
(r,g,b,a) 與顏色相關的分量
(s,t,p,q) 與紋理座標相關的分量

不同的命名約定是爲了方便使用,所以哪怕是描述位置的向量,也是可以通過 {r, g, b, a} 來獲取。但是在使用向量時不能混用不同的命名約定,即不能使用 .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}

除了使用 “.” 操作符之外,還可以使用數組下標操作。在使用數組下標操作時,元素 [0] 對應的是 x,元素 [1] 對應 y,以此類推。值得注意的是,在 OpenGL ES 2.0 中的某些情況下,數組下標不支持使用非常數的整型表達式(如使用整型變量索引),這是因爲對於向量的動態索引操作,某些硬件設備處理起來很困難。在 OpenGL ES 2.0 中僅對 uniform 類型的變量支持這種動態索引。

矩陣可以認爲是向量的組合。例如一個 mat2 可以認爲是兩個 vec2,一個 mat3 可以認爲是三個 vec3 等等。對於矩陣來說,可以通過數組下標 “[]” 來獲取某一列的值,然後獲取到的向量又可以繼續使用向量的操作方法,如下:

mat4 myMat4 = mat4(1.0); 	// Initialize diagonal to 1.0 (identity)
vec4 col0 = myMat4[0];	    // Get col0 vector out of the matrix 
float m1_1 = myMat4[1][1];  // Get element at [1][1] in matrix 
float m2_2 = myMat4[2].z;   // Get element at [2][2] in matrix

向量和矩陣的操作

絕大多數情況下,向量和矩陣的計算是逐分量進行的(component-wise)。當運算符作用於向量或矩陣時,該運算獨立地作用於向量或矩陣的每個分量。
以下是一些示例:

vec3 v, u;
float f;
v = u + f;

等價於:

v.x = u.x + f;
v.y = u.y + f;
v.z = u.z + f;

再如:

vec3 v, u, w;
w = v + u;

等價於:

w.x = v.x + u.x;
w.y = v.y + u.y;
w.z = v.z + u.z;

對於整型和浮點型的向量和矩陣,絕大多數的計算都同上,但是對於向量乘以矩陣、矩陣乘以向量、矩陣乘以矩陣則是不同的計算規則。這三種計算使用線性代數的乘法規則,並且要求參與計算的運算數值有相匹配的尺寸或階數。
例如:

vec3 v, u;
mat3 m;

向量乘以矩陣
u = v * m;

等價於:

u.x = dot(v, m[0]); // m[0] is the left column of m
u.y = dot(v, m[1]); // dot(a,b) is the inner (dot) product of a and b
u.z = dot(v, m[2]);

再如:

矩陣乘以向量
u = m * v;

等價於:

u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z;
u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z;
u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z;

再如:

矩陣乘以矩陣
mat m, n, r;
r = m * n;

等價於:

r[0].x = m[0].x * n[0].x + m[1].x * n[0].y + m[2].x * n[0].z;
r[1].x = m[0].x * n[1].x + m[1].x * n[1].y + m[2].x * n[1].z;
r[2].x = m[0].x * n[2].x + m[1].x * n[2].y + m[2].x * n[2].z;
r[0].y = m[0].y * n[0].x + m[1].y * n[0].y + m[2].y * n[0].z;
r[1].y = m[0].y * n[1].x + m[1].y * n[1].y + m[2].y * n[1].z;
r[2].y = m[0].y * n[2].x + m[1].y * n[2].y + m[2].y * n[2].z;
r[0].z = m[0].z * n[0].x + m[1].z * n[0].y + m[2].z * n[0].z;
r[1].z = m[0].z * n[1].x + m[1].z * n[1].y + m[2].z * n[1].z;
r[2].z = m[0].z * n[2].x + m[1].z * n[2].y + m[2].z * n[2].z;

對於2階和4階的向量或矩陣也是相似的規則。

結構體

與 C 語言相似,除了基本的數據類型之外,還可以將多個變量聚合到一個結構體中

struct customStruct
{
	vec4 color;
	vec2 position;
} customVertex;

首先,定義會產生一個新的類型叫做 customStruct ,及一個名爲 customVertex 的變量。結構體可以用構造器來初始化,在定義了新的結構體之後,還會定義一個與結構體類型名稱相同的構造器。構造器與結構體中的數據類型必須一一對應,如下:

customVertex = customStruct(vec4(0.0, 1.0, 0.0, 0.0), // color
							vec2(0.5, 0.5)); 		  // position

結構體的構造器是基於類型的名稱,以參數的形式來賦值。獲取結構體內元素的方法和C語言中一致:

vec4 color = customVertex.color;
vec4 position = customVertex.position;

數組

除了結構體外,GLSL 中還支持數組。 語法與 C 語言相似,創建數組的方式如下代碼所示:

float floatArray[4];
vec4 vecArray[2];

與C語言不同,在GLSL中,關於數組有兩點需要注意:

  • 除了uniform 變量之外,數組的索引只允許使用常數整型表達式。
  • 在GLSL 中不能在創建的同時給數組初始化,即數組中的元素需要在定義數組之後逐個初始化,且數組不能使用 const 限定符。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章