1. 程式人生 > >著色語言 Shading Language(一)

著色語言 Shading Language(一)

由於Android平臺下的可程式設計圖形硬體支援是 OpenGL ES 2.0標準,因此本教程向巴友們介紹 OpenGL ES著色語言。 OpenGL ES 著色語言是一種高階的圖形程式語言。其源自於應用廣泛的C語言,同時具有RendeMan以及其他著色語言的一些優良特性,易於被開發人員掌握。 OpenGL ES 的著色語言主要包括以下特性:OpenGL ES 2.0著色語言是一種高階的過程語言(注意,不是面向物件的)。

對頂點著色器、片元著色器使用的是同樣的語言。

基於C/C++的語法及流程控制。

完美支援向量與矩陣的各種操作。

通過型別限定符來管理輸入與輸出。

擁有大量的內建函式來提供豐富的功能。

一、著色語言基礎

資料型別概述

1. 標量

標量也被稱為“無向量”其值只有大小,並不具有方向。標量之間的運算遵循簡單的代數法則,如質量、密度、體積、時間以及溫度等都屬於標量。OpenGL ES著色語言支援的標量型別有布林型(bool)、整形(int)和浮點型(float)

2. 向量

OpenGL ES著色語言中,向量可以看做是用同樣型別的標量組成,其基本型別也分為bool、int和float三種。每個向量可以由2個、3個、4個相同的標量組成,具體情況如下:

向量型別

說明

向量型別

說明

vec2

包含了2個浮點數的向量

ivec4

包含了4個整數的向量

vec3

包含了3個浮點數的向量

bvec2

包含了2個布林數的向量

vec4

包含了4個浮點數的向量

bvec3

包含了3個布林數的向量

ivec2

包含了2個整數的向量

bvec4

包含了4個布林數的向量

ivec3

包含了3個整數的向量

向量在著色器程式碼的開發中有著十分重要的作用,可以很方面的儲存以及儲存顏色、位置、紋理座標等不僅包含一個組成部分的量。開發中,有時可能需奧單獨訪問向量中的某個分量,基本的語法為“<向量名>.<分量名>”,根據目的的不同,主要有以下幾種用法:

將一個向量看做顏色時,可以使用r,g,b,a四個分量名,分別代表紅、綠、藍、透明度4個色彩通道。具體用法如下:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //給向量aColor的紅色通道賦值
  2. aColor.r = 0.6;  

將一個向量看做位置時,可以使用x,y,z,w等4個分量名,分別代表X軸,Y軸,Z軸和向量的模四個分量,具體用法和顏色類似。

將一個向量看做紋理座標時,可以使用s,t,p,q四個分量名,期分別代表紋理座標的不同分量,具體用法同顏色。(對紋理座標中的s,t等分量博友可能不是很明白,不用擔心,在後面介紹紋理貼圖的教程會進行詳細的介紹)

訪問向量中的各個不同的分量不但可以採用“.”加上不同的分量名,還可以將向量看做一個數組,用下標來進行訪問,具體用法如下:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //給向量aColor的紅色通道賦值
  2. aColor[0] = 0.6;  

3. 矩陣

有一些基礎的開發人員都知道,3D場景中的移位、旋轉、縮放等變換都是由矩陣的運算來實現的。因此3D場景的開發中會非常多的使用矩陣,矩陣按尺寸分為2x2矩陣、3x3矩陣、4x4矩陣,具體情況如下表所示:

矩陣型別

說明

mat2

2x2浮點數矩陣

mat3

3x3浮點數矩陣

mat4

4x4浮點數矩陣

對於矩陣的訪問,可以講矩陣作為列向量的陣列來訪問。如matrix為一個mat4,可以使用matrix[2]取到該矩陣的第三列,其為一個vec4;也可以使用matix[2][2]取得第三列向量的第3個分量。

4. 取樣器

取樣器是著色語言中不同於c語言的一種特殊的基本資料型別,其專門用來進行紋理取樣的相關操作。一般情況下,一個取樣器變數代表一幅或一套紋理貼圖,其具體情況如下:

取樣器

說明

sampler2D

用於訪問二維紋理

smapler3D

用於訪問三維紋理

samplerCube

用於訪問立方貼圖紋理

需要注意的是,與前面介紹的幾種變數不同,取樣器變數不能再著色器中初始化。一般情況下采樣器變數都用uniform限定符來修飾,從宿主語言(如Java)接受傳遞進著色器的值。

5. 結構體

OpenGL ES著色語言還提供了類似C語言中的使用者自定義結構體,同樣也是使用struct關鍵字進行宣告。其基本用法如下:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. struct info{  
  2. vec3 color;  
  3. vec3 position;  
  4. vec2 textureCoor;  
  5. }  

6. 陣列

宣告陣列的方式主要有兩種,

在宣告陣列的同時,指定陣列的大小:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. vec3 position[20];  

在宣告陣列時,也可以不指定陣列的大小,但是必須符合下列兩種情況之一。

引用陣列之前,要再次使用第一種宣告方式來生命該陣列:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //聲明瞭一個大小不定的vec3陣列
  2. vec3 position[];  
  3. //再次宣告該陣列,並且指定大小。
  4. vec3 position[5];  

程式碼中訪問陣列的下標都是編譯時常量,這時編譯器會自動建立適當大小的陣列,使得陣列尺寸足夠儲存編譯器看到的最大索引值對應的元素。

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //聲明瞭一個大小不定的vec3陣列
  2. vec3 position[];  
  3. //position需要一個大小為4的陣列
  4. position[3] = vec3(3.0);  
  5. //position需要一個大小為21的陣列
  6. position[20] = vec3(6.0);  

7. 空型別使用void表示,僅用來宣告不返回任何值得函式。例如在頂點著色器以及片元著色器中必須存在的main函式就是一個返回值為空的函式,程式碼如下:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. void main() {  
  2. }  

資料型別的基本使用

1. 宣告、作用域及初始化

變數的宣告以及作用域與Java/C++語法類似,可以再任何需要的位置宣告變數,同時期作用域也同樣分為區域性變數和全域性變數:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //聲明瞭全域性變數a和b
  2. int a,b;  
  3. //聲明瞭全域性變數aPosition並賦值
  4. vec3 aPosition = vec3(1.0, 2.2, 3.3);  
  5. void myFunction() {  
  6.     //聲明瞭區域性變數c並賦值
  7.     int c = 14;  
  8.     //給全域性變數a賦值
  9.     a = 4;  
  10.     //給全域性變數b賦值
  11.     b = a * c;  
  12. }  

向量的初始化還有一些很靈活的技巧,博友們體會一下下面的程式碼:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //宣告浮點變數a並賦值
  2. float a = 12.3;  
  3. //宣告浮點變數b並賦值
  4. float b = 11.4;  
  5. //宣告2維向量va並賦值
  6. vec2 va = vec2(2.3, 2.5);  
  7. //宣告2維向量vb並賦值
  8. vec2 vb = vec2(a, b);  
  9. //宣告3維向量vc並賦值
  10. vec3 vc = vec3(vb, 12.5);  
  11. //宣告4維向量vd並賦值
  12. vec4 vd = vec4(va, vb);  
  13. //宣告4維向量ve並賦值, 相當於vec4(0.2 , 0.2 , 0.2, 0.2);
  14. vec4 ve = vec4(0.2);  

2. 運算子

與大多數程式語言類似,常見的運算子都可以在該語言中使用。下面按照優先順序順序列出了OpenGL ES著色語言中可以使用的運算子:

運算子

說明

運算子

說明

[]

用於索引

.

成員選擇與混合

++ --

自加1與自減1字尾

++ --

自加1與自減1字首

- !

一元非與邏輯非

* /

乘法與除法

+ -

加法與減法

< > <= >= 

關係運算符

== !=

等於和不等於

&&

邏輯與

^^

邏輯異或

||

邏輯或

?:

選擇

= += -= *= /=

賦值運算子

3. 限定符

與其他的程式語言一樣,著色器中對變數也有很多可選的限定符,主要如下:

限定符

說明

attribute

一般用於每個頂點都各不相同的量,如頂點位置、顏色等。

uniform

一般用於對同一組頂點組成的單個3D物體中所有頂點都相同的量,如當前光源的位置。

varying

用於從頂點著色器傳遞到片元著色器的量

const

用於宣告常量

attribute限定符

顧名思義為屬性限定符,其修飾的變數用來接收渲染管線傳遞進頂點著色器的當前待處理頂點的各種屬性值。這些屬性值每個頂點各自擁有獨立的副本,用於描述頂點的各項特徵,如頂點座標、法向量、顏色、紋理座標等。

用attribute限定符修士的變數其值是由宿主程式批量出入渲染管線的,管線進行基本處理後再傳遞給頂點著色器。資料中有多少個頂點,管線就呼叫多少次頂點著色器,每次講一個頂點的各種屬性資料傳遞給頂點著色器中對應atribute變數。因此,頂點著色器每次執行將完成對一個頂點各項屬性資料的處理。

從上面的介紹中可以看出,atribute限定符只能用於頂點著色器中,不能再片元著色器中使用,且attribute限定符只能用來修飾浮點數標量、浮點向量以及矩陣變數,不能用來修飾其他型別的變數。下面的程式碼片段給出了在頂點著色器中正確使用attribute限定符的情況:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //頂點位置
  2. attribute vec3 aPosition;  
  3. //頂點法向量
  4. attribute vec3 aNormal;  

前面已經提到,對於用attribute限定符修飾的變數的值是由宿主程式批量傳入渲染管線的,相關程式碼如下:

[程式碼]java程式碼:

[js] view plain copy print?
  1. // 宣告頂點位置屬性引用
  2. int maPositionHandle;  
  3. // 獲取頂點位置屬性引用的值,
  4. // mProgram為著色器程式ID,
  5. // aPosition為著色器中對應屬性的變數名稱。
  6. maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");  
  7. // 將頂點位置資料傳入渲染管線
  8. // maPositionHandle:頂點位置屬性引用
  9. // 3:每頂點一組的資料個數(這裡是X,Y,Z座標,因此為3)
  10. // GLES20.GL_FLOAT:資料型別
  11. // false:是否格式化
  12. // 3 * 4:每組資料的尺寸,這個魅族3個浮點數值(X,Y,Z座標),每個浮點數4個位元組
  13. // mVertexBuffer:存放了資料的緩衝
  14. GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);  

具體程式碼可以參考第一個教程中的Triangle類。

uniform限定符

uniform為一致變數限定符,一致變數指的是對於同一組頂點組成的單個3D物體中所有頂點都相同的量。Uniform變數可以用在頂點著色器或片元著色器中,其支援用來修飾所有的基本資料型別。與屬性限定符類似,一致變數的值也是從宿主程式傳入的。

下面的程式碼片給出了在頂點或片元著色器中正確使用uniform限定符的情況:

[程式碼]xml程式碼:

[js] view plain copy print?
  1. //總變換矩陣
  2. uniform mat4 uMVPMatrix;  
  3. //變換矩陣
  4. uniform mat4 uMMatrix;  
  5. //光源位置
  6. uniform vec3 uLightLocation;  
  7. //攝像機位置
  8. uniform vec3 uCamera;