每個OpenGL ES 3.0程序要求一個頂點着色器和一個片段着色器去渲染一個圖形。着色器概念是API 的中心,本篇將介紹着色器語言部分包含下面幾項
1、變量和變量類型
2、矢量和矩陣創建及選擇
3、常量
4、結構和陣列
5、運算符、流控制和函數
6、屬性、只讀變量和變量
7、預處理和指令
8、只讀變量和變量壓縮
9、精度控制和不變性
一、變量和變量類型
計算機圖形學中,轉換有兩種基本的數據類型:矢量和矩陣。下圖是OpenGL ES 着色器編程語言數據類型
變量可以在聲明時初始化,或以后初始化,初始化是通過構造函數進行,也可做類型轉換。
float myFloat = 1.0; float myFloat2 = 1; // ERROR: invalid type conversion bool myBool = true; int myInt = 0; int myInt2 = 0.0; // ERROR: invalid type conversion myFloat = float(myBool); // Convert from bool -> float myFloat = float(myInt); // Convert from int -> float myBool = bool(myInt); // Convert from 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}
矩陣轉換
mat3 myMat3 = mat3(1.0, 0.0, 0.0, // First column 0.0, 1.0, 0.0, // Second column 0.0, 1.0, 1.0); // Third column
二、矢量和矩陣元素
矩陣元素能夠通過兩種方式獲取,使用“.”操作符或者數組下標。依據被給的元素的組成,每個被給的矩陣都能使用{x, y, z, w}, {r, g, b, a},或{s, t, r, 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}
矩陣也可以使用[]操作符,在這種下標模式[0]代表x, [1]代表y。矩陣被認為是多個矢量組成的,例如mat2 被考慮是兩個vec2s,mat3 是3 個vec3s。對矩陣,單獨的列被使用列下標[]選中。下面是例子:
mat4 myMat4 = mat4(1.0); // Initialize diagonal to 1.0 (identity) vec4 colO = myMat4[0]; // Get colO vector out of the matrix float ml_l = myMat4[1][1]; // Get element at [1][1] in matrix float m2_2 = myMat4[2].z; // Get element at [2][2] in matrix
三、常量
常量是在着色器中不可改變的數據類型。使用const修飾,必須在聲明時初始化。
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);
四、結構和數組
結構
像C 語言一樣,可以集合幾種變量成為結構。在OpenGL ES 結構如下:
struct fogStruct { vec4 color; float start; float end; } fogVar;
這將產生新的變量類型fogStruct,和新的變量名fogVar。使用能夠初始化構造函數變量。定義一個結構類型,定義一個結構,一個同名的構造函數也被定義,必須是一對一的。上面的結構能夠被使用下面的語法初始化:
fogVar = fogStruct(vec4(0.0, 1.0, 0.0, 0.0), // color 0.5, // start 2.0); // end
結構的構造基於結構的類型,它把每個成員做輸入參數。訪問結構中的元素和C 語言相同。
vec4 color = fogVar.color; float start = fogVar.start; float end = fogVar.end;
數組
OpenGL ES 數組語法和C 語言非常類似,索引以0 開始。下面是創建數組的例子:
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));
五、操作符
這些運算符使用和C 語言非常類似。但OpenGL ES 語法有嚴格的語法限制,執行運算符的變量必須有相同的類型,二進制運算符(*, /, +, -)必須是浮點變量或者是整型變量。乘運算符能夠在浮點、矢量、矩陣的組合中運行。例如:
float myFloat; vec4 myVec4; mat4 myMat4; myVec4 = myVec4 * myFloat; // Multiplies each component of myVec4 // by a scalar myFloat myVec4 = myVec4 * myVec4; // Multiplies each component of myVec4 // together (e.g., myVec4 ^ 2 ) myVec4 = myMat4 * myVec4; // Does a matrix * vector multiply of // myMat4 * myVec4 myMat4 = myMat4 * myMat4; // Does a matrix * matrix multiply of // myMat4 * myMat4 myMat4 = myMat4 * myFloat; // Multiplies each matrix component by // the scalar myFloat
比較運算符(==, !=, <, etc.)僅能夠執行標量,矢量有專門的比較函數
六、函數
函數聲明和C 語言一樣,函數使用前,必須定義,它的原型必須給出。使用時非常類似C 語言。不同是參數使用上,提供特殊的變量限定詞,指示變量是否能夠被函數修改。那些限定詞如下表:
使用如下:
vec4 myFunc(inout float myFloat, // inout parameter out vec4 myVec4, // out parameter mat4 myMat4); // in parameter (default)
散射光計算函數。
vec4 diffuse(vec3 normal, vec3 light, vec4 baseColor) { return baseColor * dot(normal, light); }
OpenGL ES 函數不能遞歸,原因是一些編譯工具執行這個函數時,這會讓這個函數在線執行,最后使GPU 產生問題。
着色器語言也提供了內置函數。下面的例子是,在片段着色器中計算基本反射光的着色器代碼。
float nDotL = dot(normal , light); float rDotV = dot(viewDir, (2.0 * normal) * nDotL C light); float specular = specularColor * pow(rDotV, specularPower);
七、流控制聲明
控制語句語法和C 類似,if-then-else 邏輯也被使用,例如:
if(color.a < 0.25) { color *= color.a; } else { color = vec4(0.0); }
條件表達式的結果必須是布爾值。或者說表達式基於布爾值計算,或者計算結果是布爾值(例如比較運算)。
八、Uniforms
Uniform
是變量類型的一種修飾符。。uniform 是OpenGL ES 中被輸入着色器的只讀值。。uniform被使用存儲各種着色器需要的數據,例如:轉換矩陣、光照參數或者顏色。基本上各種輸入着色器的常量參數像頂點和片段(但在編譯時並不知道)應該是uniform。uniform 應該使用修飾詞被聲明為全局變量,如下:
uniform mat4 viewProjMatrix;
uniform mat4 viewMatrix;
uniform vec3 lightPosition;
uniform 的空間被頂點着色器和片段着色器分享。也就是說頂點着色器和片段着色器被鏈接到一起進入項目,它們分享同樣的uniform。因此一個在頂點着色器中聲明的uniform,相當於在片段着色器中也聲明過了。當應用程序裝載uniform 時,它的值在頂點着色器和片段着色器都可用。
另一個需要注意的是,uniform 被存儲在硬件被稱為常量存儲,這是一種分配在硬件上的存儲常量值的空間。因為這種存儲需要的空間是固定的,在程序中這種uniform 的數量是受限的。這個限制能通過讀gl_MaxVertexUniformVectors 和gl_MaxFragmentUniformVectors編譯變量得出。( 或者用GL_MAX_VERTEX_UNIFORM_VECTORS 或GL_MAX_FRAGMENT_UNIFORM_ VECTORS 為參數調用glGetIntegerv)OpenGL ES 3.0必須至少提供256 個頂點着色器uniform 和224個片段着色器uniform。但可以更多,
Uniform Blocks
uniform TransformBlock { mat4 matViewProj; mat3 matNormal; mat3 matTexGen; }; layout(location = 0) in vec4 a_position; void main() { gl_Position = matViewProj * a_position; }
九、屬性
OpenGL ES 着色器語言的另一個變量是屬性。屬性變量僅僅在頂點着色器中被使用,逐頂點的指定頂點着色器的輸入。典型的被用來儲存位置、法線、貼圖坐標和顏色數據。關鍵是懂得屬性是每個頂點被繪制的詳細數據。它實際上是着色器的使用者決定什么數據是屬性。
uniform mat4 u_matViewProjection; attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; void main(void) { gl_Position = u_matViewProjection * a_position; v_texCoord = a_texCoord0; }
像uniform 一樣,硬件對頂點着色器的屬性變量數量有限制。最大的屬性數通過工具編譯支持gl_MaxVertexAttribs 確定。(或者使用GL_MAX_VERTEX_ATTRIBS 為參數調用glGetIntegerv 查詢)最小數是8。如果為確保你的程序能在任何OpenGL ES 2.0 工具上編譯,確保你的屬性不超過8。
變量被用來存儲頂點着色器的輸出和片段着色器的輸入。基本上每個頂點着色器把輸出數據轉變成一個或更多片段着色器的輸入。那些變量也被片段着色器聲明(類型需匹配),並且在光柵化階段被線性插補變成圖元。
varying vec2 texCoord;
varying vec4 color;
變量也有數量的限制(硬件上它們被用來做插補)。工具支持的最大數目是gl_MaxVaryingVectors ( 使用GL_MAX_VARYING_VECTORS 為參數調用glGetIntegerv 查詢)。最大數目是8.
下面是頂點着色器和片段着色器的變量如何匹配的聲明
// Vertex shader uniform mat4 u_matViewProjection; attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; // Varying in vertex shader void main(void) { gl_Position = u_matViewProjection * a_position; v_texCoord = a_texCoord0; }
// Fragment shader precision mediump float; varying vec2 v_texCoord; // Varying in fragment shader uniform sampler2D s_baseMap; uniform sampler2D s_lightMap; void main() { vec4 baseColor; vec4 lightColor; baseColor = texture2D(s_baseMap, v_texCoord); lightColor = texture2D(s_lightMap, v_texCoord); gl_FragColor = baseColor * (lightColor + 0.25); }
十、預處理指令
#define #undef #if #ifdef #ifndef #else #elif #endif
__LINE__ // 被着色器當前的行數取代
__FILE__ // 在 OpenGL ES 中總是0
__VERSION__ // OpenGL ES 着色器語言版本 (e.g., 100)
GL_ES // 被 ES 着色器定義為值1
其他
精度控制符,lowp,highp,mediump.對所有基於浮點的變量默認的精度是浮點值,基於整型的變量默認的精度是整型。在着色器中, 如果沒有定義指定精度,默認對int 和float 的精度都是高。換句話說在頂點着色器中沒有指定進度控制的變量將是高質量。而片段着色器規則卻不同。對浮點值沒有默認的精度控制。每個着色器必須聲明默認的着色器浮點精度或指定每個浮點變量的精度。即OpenGL ES 2.0 不要求片段着色器支持高精度。決定是否高精度被支持是片段着色器是否定義了GL_FRAGMENT_PRECISION_HIGH 宏(和工具輸出OES_fragment_precision_high 擴展字符串)。
#ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif
最后要討論的題目是不變性。在OpenGL ES 着色器編程語言里,invariant 是被用於任何頂點着色器的變量輸出的關鍵字。它意味着什么和為什么需要他呢。着色器被編譯時可能進行優化,一些指令被重新整理。指令重新整理意味着兩個着色器之間平等的計算不保證產生相同的結果。這是個問題在多通道着色器特殊情況下,依稀物體使用透明混合來繪制時。如果精度被使用來計算輸出位置是不准確一樣,精度的不同將導致artifacts。這在Z fighting情況下經常發生。每個像素很小的Z 精度不同引起了不同的反光。下面的例子顯示了進行多通道渲染時invariance 對得到正確的結果是非常重要的。下面的圓環被繪制用兩種方式。片段着色器先計算鏡面反射光,再計算環境光和散射光。頂點着色器不使用invariance,因此小的精度不同引起了Z 光在圖
invariant gl_Position;
invariant varying texCoord;
一旦輸出被宣布為invariance,同樣的計算輸入相同,編譯器保證輸出結果相同。例如你有兩個頂點着色器使用多通道圖像投射矩陣依據輸入計算輸出。你能保證那些位置是invariance。
uniform mat4 u_viewProjMatrix; attribute vec4 a_vertex; invariant gl_Position; void main { // gl_Position = u_viewProjMatrix * a_vertex; // Will be the same // value in all // shaders with the // same viewProjMatrix // and vertex }