opengl著色器shader介紹
1. Shader
Shader其實就是一段執行在GPU上的程式,此程式使用OpenGL ES SL語言來編寫。它是一個描述頂點或畫素特性的簡單程式。在opengles中常用的shader有兩種:vertex shader和fragment shader。Geometry Shader(幾何著色器)是繼Vertex Shader和Fragment Shader之後,由Shader Model 4引入的新的著色器。還有一個compute Shader由Shader Model 5引入的提供通用計算能力的著色器。
1.1 Vertex Shader
對於傳送給GPU的每一個Vertex(頂點),都要執行一次Vertex Shader。其功能是把每個頂點在虛擬空間中的三維座標變換為可以在螢幕上顯示的二維座標,並帶有用於z-buffer的深度資訊。Vertex Shader可以操作的屬性有:位置、顏色、紋理座標,但是不能建立新的頂點。
vertex shader主要完成以下工作:1).基於點操作的矩陣乘法位置變換;2).根據光照公式計算每點的color值;3).生成或者轉換紋理座標。
Vertex Shader輸入資料如下:
1).Attributes:由 vertext array 提供的頂點資料,如空間位置,法向量,紋理座標以及頂點顏色,它是針對每一個頂點的資料。屬性只在頂點著色器中才有,片元著色器中沒有屬性。屬性可以理解為針對每一個頂點的輸入資料。OpenGL ES 2.0 規定了所有實現應該支援的最大屬性個數不能少於 8 個。
注:Vertex Attributes 是每點的屬性資料。與一個index序號繫結。外部程式可通過 glBindAttribLocation將一個attribute 名與一個index繫結起來。當然,OPENGL ES 內部會自動繫結所有attributes.外部程式只需通過 glGetAttribLocation獲取指定attribute名的index。 給Attribute傳值可以通過 glVertexAttribPointer函式或者glVertexAttrib4fv
2).Uniforms:uniforms儲存由應用程式傳遞給著色器的只讀常量資料。在頂點著色器中,這些資料通常是變換矩陣,光照引數,顏色等。由 uniform 修飾符修飾的變數屬於全域性變數,該全域性性對頂點著色器與片元著色器均可見,也就是說,這兩個著色器如果被連線到同一 個program Object,則它們共享同一份 uniform 全域性變數集。因此如果在這兩個著色器中都聲明瞭同名的 uniform 變數,要保證這對同名變數完全相同:同名+同類型,因為它們實際是同一個變數。此外,uniform 變數儲存在常量儲存區,因此限制了 uniform 變數的個數,OpenGL ES 2.0 也規定了所有實現應該支援的最大頂點著色器 uniform 變數個數不能少於 128 個,最大的片元著色器 uniform 變數個數不能少於 16 個。
3).Samplers:一種特殊的 uniform,在vertex shader中是可選的,用於呈現紋理。sampler 可用於頂點著色器和片元著色器。
4).Shader program:由 main 宣告的一段程式原始碼,描述在頂點上執行的操作:如座標變換,計算光照公式來產生 per-vertex 顏色或計算紋理座標。
Vertex Shader輸出為:
1).Varying:varying 變數用於儲存頂點著色器的輸出資料,當然也儲存片元著色器的輸入資料,varying 變數最終會在光柵化處理階段被線性插值。頂點著色器如果聲明瞭 varying 變數,它必須被傳遞到片元著色器中才能進一步傳遞到下一階段,因此頂點著色器中宣告的 varying 變數都應在片元著色器中重新宣告同名同類型的 varying 變數。OpenGL ES 2.0 也規定了所有實現應該支援的最大 varying 變數個數不能少於 8 個。
2).在頂點著色器階段至少應輸出位置資訊-即內建變數:gl_Position,是每個點固有的Varying,表示點的空間位置。其它兩個可選的變數為:gl_FrontFacing 和 gl_PointSize。
1.2 Fragment Shader
Pixel Shader(畫素著色器)就是眾所周知的Fragment Shader(片元著色器),它計算每個畫素的顏色和其它屬性。它通過應用光照值、凹凸貼圖,陰影,鏡面高光,半透明等處理來計算畫素的顏色並輸出。它也可改變畫素的深度(z-buffering)或在多個渲染目標被啟用的狀態下輸出多種顏色。一個Pixel Shader不能產生複雜的效果,因為它只在一個畫素上進行操作,而不知道場景的幾何形狀。
Fragment Shader輸入資料如下:
1).Varyings:這個在前面已經講過了,頂點著色器階段輸出的 varying 變數在光柵化階段被線性插值計算之後輸出到片元著色器中作為它的輸入,即上圖中的 gl_FragCoord,gl_FrontFacing 和 gl_PointCoord。OpenGL ES 2.0 也規定了所有實現應該支援的最大 varying 變數個數不能少於 8 個。
2).Uniforms:前面也已經講過,這裡是用於片元著色器的常量,如霧化引數,紋理引數等;OpenGL ES 2.0 也規定了所有實現應該支援的最大的片元著色器 uniform 變數個數不能少於 16 個。
3).Samples:一種特殊的 uniform,用於呈現紋理。
4).Shader program:由 main 申明的一段程式原始碼,描述在片元上執行的操作。
FragmentShader輸出為:
在頂點著色器階段只有唯一的 varying 輸出變數-即內建變數:gl_FragColor
1.3 Geometry Shader
Geometry Shader(幾何著色器)是繼Vertex Shader和Fragment Shader之後,由Shader Model 4(第四代顯示卡著色架構)正式引入的第三個著色器。它在Driect3D 10和OpenGL 3.2中開始引入,在OpengGL 2.0+中作為擴充套件使用,在OpenGL3.x中也成為核心。它的輸入為:點、線和三角形;其輸出為點、線帶和三角形帶。
在Geometry Shader裡,我們處理的單元是Primative。雖然根本上都是頂點的處理,但進入vertex shader裡的是一次一個的頂點,而進入Geometry Shader的是一次一批的頂點,Geometry Shader掌握著這些頂點所組成的圖元的資訊。Geometry Shader的處理階段處於流水線的柵格化之前,也在視錐體裁剪和裁剪空間座標歸一化之前。雖說裁剪過程會剔除部分圖元也會分割某些圖元, 但就目前來說,不會有其他流水線的可程式設計階段會在Geometry Shader之後提供出影響圖元的性質(形式和數量)——這是Geometry Shader鑑於其位置的特殊性而擁有的一個重要特點。
Geometry Shader程式在Vertex Shader程式執行之後執行。
2.頂點著色與片元著色在程式設計上的差異
1).精度上的差異
著色語言定了三種級別的精度:lowp, mediump, highp。我們可以在 glsl 指令碼檔案的開頭定義預設的精度。如下程式碼定義在 float 型別預設使用 highp 級別的精度
precision highp float;
在頂點著色階段,如果沒有使用者自定義的預設精度,那麼 int 和 float 都預設為 highp 級別;而在片元著色階段,如果沒有使用者自定義的預設精度,那麼就真的沒有預設精度了,我們必須在每個變數前放置精度描述符。此外,OpenGL ES 2.0 標準也沒有強制要求所有實現在片元階段都支援 highp 精度的。我們可以通過檢視是否定義 GL_FRAGMENT_PRECISION_HIGH 來判斷具體實現是否在片元著色器階段支援 highp 精度,從而編寫出可移植的程式碼。當然,通常我們不需要在片元著色器階段使用 highp 級別的精度,推薦的做法是先使用 mediump 級別的精度,只有在效果不夠好的情況下再考慮 highp 精度。
2).attribute 修飾符只可用於頂點著色。這個前面已經說過了。
3).或由於精度的不同,或因為編譯優化的原因,在頂點著色和片元著色階段同樣的計算可能會得到不同的結果,這會導致一些問題(z-fighting)。因此 glsl 引入了 invariant 修飾符來修飾在兩個著色階段的同一變數,確保同樣的計算會得到相同的值。
OpenGL ES著色語言是兩種緊密關聯的語言。這些語言用來在OpenGL ES處理管線的可程式設計處理器建立著色器。 在本文件中,除非另外說明,一個語言功能適用於所有語言,並且通用用法將把他們當做一個語言來看待。特定語言將指出它們的目標處理器:頂點(vertext)或片元(fragment)。
任何被著色器使用的OpenGL ES狀態值都會自動地被跟蹤並且作用於著色器上。這個自動狀態跟蹤機制允許應用程式為狀態管理而使用OpenGL ES狀態命令,並且這些狀態值自動地應用在著色器上。
2.1頂點處理器
頂點處理器是一個處理輸入頂點和相關資料的可程式設計單元。OpenGL ES著色語言中執行這個處理器的編譯單元叫做頂點著色器(vertex shader)。
一個頂點著色器同時只能處理一個頂點。
2.2片元處理器
片元處理器是一個處理片元和相關資料的可程式設計單元。OpenGL ES著色語言中執行這個處理器的編譯單元叫做片元著色器(fragment shader)。
片元著色器不能更改片元的位置。訪問相鄰的片元也是不允許的。片元著色器計算的值最終用在更新幀緩衝區記憶體或紋理記憶體,這要取決於當前OpenGL ES狀態以及產生片元的命令。
3.1字符集
OpenGL ES著色語言的源字符集是ASCII編碼的一個子集,包含以下字元:
字元 a-z, A-Z, and下劃線 ( _ ).
數字 0-9.
點 (.), 加號(+), 分割線(-), 斜線(/), 星號(*), 百分號(%),大於小於號 (< and >), 方括號 ( [ and ] ),小括號 ( ( and ) ), 大括號( { and } ), 插入符號(^), 豎線 ( | ), and(&), 波浪號(~), 等於號(=), 歎號 (!), 冒號(:), 分號(;), 逗號(,), 問好 (?).
井號 (#)用作前處理器.
空格: 空格字元,水平製表符,垂直製表符,換頁符,回車符,換行符。
行連線符(\)不是語言的一部分。
總之,語言使用的這個字符集都是大小寫敏感的。
沒有字元和字串型別,因此字符集中不包含引號。
也沒有檔案結束符。編譯器通過字串長度判斷字串結束,而不是通過特定的字元判斷。
3.2源字串
一個著色器的源是一個由字符集中字元構成的字串的陣列。著色器通過這些字串連線而成。每個字串可以跨越多行。單行可以包含多個字串。
在這個版本的著色語言中,僅一個頂點著色器和一個片元著色器可以連結在一起。
3.3編譯的邏輯階段
編譯處理是基於標準C++的一個子集。頂點和片元處理器的編譯單元在編譯的最後階段——連結之前是分開處理的。編譯的邏輯階段如下:
1.源字串連結
2.源字串被轉換成預處理符號序列。這些符號包括預處理數字,標示符和操作符。註釋被替換成一個空格,而換行符保留。
3.前處理器執行。
4.GLSL ES語法解析轉換後的符號。
5.連結uniform, verying, fixed 功能變數。
3.4前處理器
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
#error
#pragma
#extension
#version
#line
defined
__LINE__ 行號
__FILE__ 檔名
__VERSION__ 版本號
GL_ES 值為1,表示使用的是ES OpenGL渲染語言
操作符的優先順序和結合性:
defined操作符的用法:
defined identifier
defined ( identifier )
#error // 將診斷資訊儲存到著色器物件的資訊日誌中
#pragma //允許實現從屬的編譯控制。#pragma後面的符號不是預處理巨集定義擴充套件的一部分。如果#pragma後面的符號無法識別,那麼將忽略這個#pragma。可
以使用的pragma如下:
#pragma STDGL //STDGL pragma是預留的pragma,用來將來的語言修訂。如果沒有這個巨集的實現將使用以其開頭的巨集定義
#pragma optimize(on)
#pragma optimize(off) //用來開啟和關閉開發和除錯著色器的優化操作,預設情況下,所有的著色器的優化操作都是開啟的,它只能在函式定義的外部使用。
#pragma debug(on)
#pragma debug(off) //編譯和註釋一個著色器,並輸出除錯資訊,因此可以用作一個偵錯程式。只能用在函式外部,預設是關閉的。
#version number //返回當前著色語言的版本號
預設情況下,著色語言編譯器必須釋出編譯時句法、文法和語義錯誤。任何擴充套件的行為必須被首先啟用。控制編譯器擴充套件行為的指令是#extension:
#extension extension_name : behavior
#extension all :: behavior
extension_name是擴充套件的名稱,在這個說明文件中並沒有錄入。符號all意味著行為作用於編譯器支援的所有擴充套件。behavior的值如下:require, enable, warn, disable 。
3.5註釋
/* 註釋一個模組 */
// 註釋一行
3.6符號
著色語言是一個符號序列,可以包含以下符號:
關鍵字, 標示符, 整型常量, 浮點型常量, 運算元
3.7關鍵字
以下是著色語言已經在使用的關鍵字:
attribute const uniform varying
break continue do for while
if else
in out inout
float int void bool true false
lowp mediump highp precision invariant
discard return
mat2 mat3 mat4
vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4
sampler2D samplerCube
struct
以下是保留關鍵字:
asm
class union enum typedef template this packed
goto switch default
inline noinline volatile public static extern external interface flat
long short double half fixed unsigned superp
input output
hvec2 hvec3 hvec4 dvec2 dvec3 dvec4 fvec2 fvec3 fvec4
sampler1D sampler3D
sampler1DShadow sampler2DShadow
sampler2DRect sampler3DRect sampler2DRectShadow
sizeof cast
namespace using
3.8識別符號
識別符號用作變數名, 函式名, 結構體名和域選擇器名(選擇向量和矩陣的元素,類似結構體域)。識別符號命名規則:
(1)不能以數字開頭, 只能以字母和下劃線開頭
(2)不能以“gl_”開頭,這是被OpenGL預留的
所有變數和函式在使用前必須宣告。變數和函式名是識別符號。
沒有預設型別,所有變數和函式宣告必須包含一個宣告型別以及可選的修飾符。變數在宣告的時候首先要標明型別,後邊可以跟多個變數,之間用逗號隔開。很多情況下,變數在宣告的時候可以使用等號“=”進行初始化。
使用者定義型別可以使用struct,在結構體中所有變數型別都必須是OpenGL ES著色器語言定義的關鍵字。OpenGL ES著色語言是型別安全的,因此不支援隱式型別轉換。
4.1 基本資料型別
4.1.1 void
函式沒有返回值必須宣告為void,沒有預設的函式返回值。關鍵字void不能用於其他宣告,除了空形參列表外。
4.1.2 Booleans
布林值,只有兩個取值true或false。
bool success, done = false;
4.1.3 Integers
整型主要作為程式設計的援助角色。在硬體級別上,真正地整數幫助有效的實現迴圈和陣列索引,紋理單元索引。然而,著色語言沒必要將整型數對映到硬體級別的整數。我們並不贊成底層硬體充分支援範圍廣泛的整數操作。OpenGL ES著色語言會把整數轉化成浮點數進行操作。整型數可以使用十進位制(非0開頭數字),八進位制(0開頭陣列)和十六進位制表示(0x開頭數字)。
int i, j = 42;
4.1.4 Floats
浮點型用於廣泛的標量計算。可以如下定義一個浮點數:
float a, b = 1.5;
4.1.5 Vectors
OpenGL ES著色語言包含像2-,3-, 4-浮點數、整數、booleans型向量的泛型表示法。浮點型向量可以儲存各種有用的圖形資料,如顏色,位置,紋理座標。
vec2 texCoord1, texCoord2;
vec3 position;
vec4 rgba;
ivec2 textureLookup;
bvec3 lessThan;
向量的初始化工作可以放在建構函式中完成。
4.1.6 Matrices
矩陣是另一個在計算機圖形中非常有用的資料型別,OpenGL ES著色語言支援2*2, 3*3, 4*4浮點數矩陣。
mat2 mat2D;
mat3 optMatrix;
mat4 view, projection;
矩陣的初始化工作可以放在建構函式中完成。
4.1.7 Sampler
取樣器型別(如sampler2D)實際上是紋理的不透明控制代碼。它們用在內建的紋理函式來指明要訪問哪一個紋理。它們只能被宣告為函式引數或uniforms。除了紋理查詢函式引數, 陣列索引, 結構體欄位選擇和圓括號外,取樣器不允許出現在表示式中。取樣器不能作為左值,也不能作為函式的out或inout引數。這些限制同樣適用於包含取樣器的的結構體。作為uniforms時,它們通過OpenGL ES API初始化。作為函式引數,僅能傳入匹配的取樣器型別。這樣可以在著色器執行之前進行著色器紋理訪問和OpenGL ES紋理狀態的一致性檢查。
4.1.8 Structures
通過結構體使用者可以建立自己的資料型別。
struct light{
float intensity;
vec3 position;
}lightVar;
結構體不支援內部匿名結構體物件,也不支援內部嵌入結構體,但可以宣告另一個結構體的變數。
4.1.9 Arrays
同種型別的變數可以放在一個數組中儲存和管理。陣列長度必須是大於0的常整型數。用負數或大於等於陣列程度的索引值索引陣列是不合法的。陣列作為函式形參必須同時指明陣列長度。僅支援一維陣列,基本資料型別和結構體型別都可以作為陣列元素。
float frequencies[3];
uniform vec4 lightPosition[4];
const int numLights = 2;
light lights[bumLights];
不能在著色器中宣告陣列的同時進行初始化。
4.2 Scopes
宣告的範圍決定了變數的可見性。GLSL ES使用了靜態巢狀範圍,允許在著色器內重定義一個變數。
4.2.1術語的定義
術語scope說明程式的一個特定的區域,在這個區域定義的變數時可見的。
4.2.2範圍型別
4.2.3 重宣告變數
在一個編譯單元,具有相同名字的變數不能重宣告在同一個範圍。可以在不同的範圍內宣告同名的變數,但沒有辦法訪問外層範圍的同名變數。
4.2.4共享全域性變數
共享全域性變數是指可以在多個編譯單元訪問的變數。在GLSL ES中僅uniform變數可以作為全域性共享變數。varying變數不能作為全域性共享變數因為在片元著色器能讀取它們之前必須通過光柵化傳遞。
共享全域性變數必須有相同的名字, 儲存和精度修飾符。它們必須有如下相同等價的規則:必須具有相同的精度,基本型別和尺寸。標量必須有一樣的型別名稱和型別定義,而且欄位名稱必須為相同型別。
4.3儲存修飾符
本地變數只能使用儲存修飾符const。
函式引數只能用const。函式返回值型別和結構體欄位不要使用const。
從一個執行時著色器到下一個執行時著色器之間進行資料型別通訊是不存在的。這阻止了同一個著色器在多個頂點和片元之間同時執行。
沒有儲存修飾符或僅僅使用const修飾符的全域性變數,可能在main()執行前進行初始化。Uniforms, attributes和varyings可能沒有初始化器。
4.3.1預設儲存修飾符
如果在全域性變數前沒有修飾符,那麼它們就與應用程式和其他處理器上的著色器沒有關聯。對於全域性或本地的無修飾符變數,宣告都會在其所屬的那個處理器上分配記憶體。這個變數將提供對分配的記憶體的讀寫訪問。
4.3.2常量修飾符
命名的編譯時常量可以用const宣告。任何使用const宣告的變數在其所屬的著色器中均是隻讀的。將變數宣告為常量可以減少使用硬連線的數字常數。const可以用來修飾任何基本資料型別。通常const變數在宣告的同時要進行初始化:
const vec3 zAxis = vec3 (0.0, 0.0, 1.0);
結構體欄位不能使用const修飾嗎,但是變數可以,並通過構造器進行初始化。包含陣列的陣列和結構體不能宣告為常量,因為它們不能被初始化。
4.3.3 Attribute
attribute修飾符用於宣告通過OpenGL ES應用程式傳遞到頂點著色器中的變數值。在其它任何非頂點著色器的著色器中宣告attribute變數是錯誤的。在頂點著色器被程式使用之前,attribute變數是隻讀的。attribute變數的值通過OpenGL ES頂點API或者作為頂點陣列的一部分被傳進頂點著色器。它們傳遞頂點屬性值到頂點著色器,並且在每一個執行的頂點著色器中都會改變。attribute修飾符只能修飾float, vec2, vec3, vec4,mat2,mat3,mat4。attribute變數不能宣告為陣列或結構體。如:
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
大家可能希望圖形硬體有極少量的固定位置來傳遞頂點屬性。所以,OpenGL ES為每一個非矩陣變數賦予了升級到4個浮點數值的空間,如vec4。在OpenGL ES中,可以使用的屬性變數個數是有限制的,如果超過這個限制,將會引起連結錯誤。(聲明瞭但沒有使用的屬性變數不會受到這個限制。)一個浮點數屬性也要受到這個限制,所以你應該儘量將四個毫不相關的float變數打包成一個pack,以優化底層硬體的相容性。一個mat4和使用4個vec4變數是一致的,同理,一個mat3和使用3個vec3變數是一致的,一個mat2和使用2個vec2變數是一致的。著色語言和API隱藏了到底這些空間是如何被矩陣使用的。屬性變數需要被宣告為全域性變數。
4.3.4 Uniform
uniform修飾符用來修飾那些在整個圖元被處理的過程中保持不變的全域性變數。所有的uniform變數都是隻讀的,可以通過應用程式呼叫API命令初始化,或者通過OpenGL ES間接初始化。
uniform vec4 lightPosition;
uniform修飾符可以和任意基本資料型別一起使用,或者包含基本資料型別元素的陣列和結構體。每種型別的著色器的uniform變數的儲存數量是有限制的,如果超過這個限制,將會引起編譯時或連結時錯誤。聲明瞭但是沒有被靜態使用的uniform變數不會受到這個限制。靜態使用(static use)是指著色器包含變數在預處理以後的一個引用。使用者定義的uniform變數和著色器中被靜態使用的內建uniform變數將共同決定有沒有超出可用uniform儲存範圍。
當頂點著色器和片元著色器被連結到一起,它們將共享同一個名稱空間。這就意味著,所有被連線到同一個可執行程式的著色器中的同名變數必須也同時具有相同的型別和精度。
4.3.4 Varying
varying變數提供了頂點著色器,片元著色器和二者通訊控制模組之間的介面。頂點著色器計算每個頂點的值(如顏色,紋理座標等)並將它們寫到varying變數中。頂點著色器也會從varying變數中讀值,獲取和它寫入相同的值。如果從頂點著色器中讀取一個尚未被寫入的varying變數,將返回未定義值。
通過定義,每個頂點的varying變數以一種透視校正的方式被插入到正在渲染的圖元上。如果是單取樣,插值為片元中心。如果是多采樣,插值可以是畫素中的任何地方,包括片元中心或者其中一個片元取樣。
片元著色器會讀取varying變數的值,並且被讀取的值將會作為插值器,作為圖元中片元位置的一個功能資訊。varying變數對於片元著色器來說是隻讀的。
在頂點和片元著色器中都有宣告的同名varying變數的型別必須匹配,否則將引起連結錯誤。
下表總結了頂點和片元著色器匹配的規則:
術語“靜態使用”意思是在預處理之後,著色器至少包含一個訪問varying變數的語句,即使這個語句沒有真正執行過。
varying vec3 normal;
varying修飾符只能用在float, vec2, vec3, vec4, mat2, mat3, mat4和包含這些型別元素的陣列上,不能用於修飾結構體。
varying變數需要宣告為全域性變數。
4.4引數修飾符
函式引數修飾符有如下幾種:
(1)<none: default>,預設情況下,是in
(2)in,作為函式的傳入引數
(3)out,作為函式的傳出引數
(4)inout,即作為傳入引數,又作為傳出引數
4.5精度和精度修飾符
4.5.1範圍和精度
用於儲存和展示浮點數、整數變數的範圍和精度依賴於數值的源(varying,uniform,紋理查詢,等等),是不是頂點或者片元著色器,還有其他一些底層實現的細節。最低儲存需要通過精度修飾符來宣告。典型地,精度操作必須要保留變數包含的精度儲存。僅有的例外是需要大量複雜計算的內建函式,如atan(),返回值的精度低於宣告的精度。
強烈建議頂點語言提供一種匹配IEEE單精度浮點數或更高精度的浮點數的浮點範圍和精度。這就需要頂點語言提供浮點變數的範圍至少是(-2^62, 2^62),精度至少是65536。
頂點語言必須提供一種至少16位,加上一個符號位的整數精度。
片元語言提供與頂點著色器相同的浮點數範圍和精度是很有必要的,但不是必須的。這就需要片元語言提供的浮點數的範圍至少是(-16384,+16384),精度至少是1024。
片元語言必須提供一種至少10為,加上一個符號位的整數精度。
4.5.2精度修飾符
任何浮點數或者整數宣告前面都可以新增如下精度修飾符:
舉例:
lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
精度修飾符聲明瞭底層實現儲存這些變數必須要使用的最小範圍和精度。實現可能會使用比要求更大的範圍和精度,但絕對不會比要求少。
一下是精度修飾符要求的最低範圍和精度:
Floating Point Magnitude Range是非零值量級的範圍。對於Floating Point Precision,relative意思是任何度量的值的精度都是相對於這個值的。對於所有的精度級別,0必須被精確的表示出來。任何不能提供著色器儲存變數所宣告的精度的實現都會引起一個編譯或連結錯誤。
對於高精度和中級精度,整型範圍必須可以準確地轉化成相應的相同精度修飾符所表示的float型。這樣的話,highp int 可以被轉換成highp float, mediump int 可以被轉換成mediump float,但是lowp int 不能轉換成相應的lowp float。
頂點語言要求編譯和連結任何lowp, mediump和highp應用都不能出現錯誤。
片元語言要求編譯和連結任何lowp, mediump應用都不能出現錯誤。但是highp支援是可選的。
字元常量和布林型沒有精度修飾符.當浮點數和整數構造器不含帶有精度修飾符的引數時也不需要精度修飾符。
在這段文件中,操作包含運算子,內建函式和構造器,運算元包含函式引數和構造器引數。
對於精度沒有定義的常量表達式或子表示式,評估的精度結果是所有運算元中的最高精度(mediump或者highp) 。帶評估的常量表達式必須是固定不變的,並且在編譯期進行。
另外,對於沒有精度修飾符的運算元,精度將來自於其他運算元。如果所有的運算元都沒有精度,那麼接著看使用計算結果的其他表示式。這個操作是遞迴的,直到找到一個有精度的操作符為止。如果必要,這個操作也包含賦值運算的左值,初始化宣告的變數,函式形參,函式返回值.如果這樣依然不能決定精度,如果組成表示式的所有運算元都沒有精度,如果結果沒有被賦值,也沒有當作引數傳進函式,那麼將使用預設或更大的型別.當這種情況出現在片元著色器中,預設的精度必須被定義.
比如:
uniform highp float h1;
highp float h2 = 2.3*4.7;操作和結果都是高精度
mediump float m;
m = 3.7*h1*h2;//所有操作都是高精度
h2 = m * h1;//操作是高精度
m = h2 - h1;//操作是高精度
h2 = m + m;//加法和結果都是mediump精度
void f(highp p);
f(3.3);//3.3將作為高精度值傳入函式
4.5.3預設精度修飾符
precision precision-qualifier type;
precision可以用來確定預設精度修飾符。type可以是int或float或採樣器型別,precision-qualifier可以是lowp, mediump, 或者highp。任何其他型別和修飾符都會引起錯誤。如果type是float型別,那麼該精度(precision-qualifier)將適用於所有無精度修飾符的浮點數宣告(標量,向量,矩陣)。如果type是int型別,那麼該精度(precision-qualifier)將適用於所有無精度修飾符的整型數宣告(標量,向量)。包括全域性變數宣告,函式返回值宣告,函式引數宣告,和本地變數宣告等。沒有宣告精度修飾符的變數將使用和它最近的precision語句中的精度。
在頂點語言中有如下預定義的全域性預設精度語句:
precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;
在片元語言中有如下預定義的全域性預設精度語句:
precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;
片元語言沒有預設的浮點數精度修飾符。因此,對於浮點數,浮點數向量和矩陣變數宣告,要麼宣告必須包含一個精度修飾符,要不預設的精度修飾符在之前已經被宣告過了。
4.5.4可用的精度修飾符
內建巨集GL_FRAGMENT_PRECISION_HIGH在支援highp精度的片元語言中是定義過的,但在不支援的系統中是未定義的。一旦定義以後,在頂點和片元語言中都可以使用。
#defien GL_FRAGMENT_PRECISION_HIGH 1;
4.6變異和invariant修飾符
在這部分中,變異是指在不同的著色器中的相同語句返回不同的值的可能性.舉個例子,兩個頂點著色器都使用相同的表示式來設定gl_Position,並且當著色器執行時傳進表示式的值也是一樣的.完全有可能,由於兩個著色器獨立的編譯環境,當著色器執行時賦給gl_Position的值不一定會相同.在這個例子中,會引起多路演算法的幾何對齊問題.
通常,著色器之間的這種變異是允許的.如果想避免這種變異的發生,變數可以使用invariant來宣告.
4.6.1invariant修飾符
為確保一個特定的輸出變數是不變的,可以使用invariant修飾符.它可以修飾之前已經定義過的變數,如:
invariant gl_Position;
也可以用在變數的聲明當中:
invariant varying mediump vec3 Color;
僅如下變數可以宣告為invariant:
(1)頂點著色器中內建的特定輸出變數
(2)頂點著色器中輸出varying變數
(3)片元著色器中特定的輸入變數
(4)片元著色器中的輸入varying變數
(5)片元著色器中內建的輸出變數
invariant後面還可以跟一個用逗號隔開的之前宣告的識別符號列表.
為了確保兩個著色器中特定的輸出變數不發生變異.還應遵循以下規則:
(1)頂點和片元著色器中的輸出變數都宣告為invariant
(2)相同的值必須輸入到賦給輸出變數的表示式或控制流的所有著色器輸入變數.
(3)輸出變數上的任何紋理函式呼叫在使用紋理格式,紋理畫素值和紋理過濾時都需要設定成相同的方式.
(4)所有的輸入變數都以相同的方式操作.
初始時,預設的所有輸出變數被允許變異.如果想強制所有輸出變數都不可變,那麼在著色器所有的變數宣告之前使用
#pragma STDGL invariant(all)
4.6.2著色器中的不變體
當一個值被存到一個變數中,我們通常假設它是一個常量,除非顯示的去更改它的值.然而,在優化處理期間,編譯期可能會重新計算一個值而不是將它存到暫存器中.因為操作的精度沒有被完全指定(如,低精度的操作會被轉成中等精度或高精度),重新計算的值有可能就和原來的值不一致.
在著色器中變體是允許的.如果要避免變體,可以使用invariant修飾符或invariant pragma.
precision mediump;
vec4 col;
vec2 a = ...;
........
col = texture2D(tex, a);//此時a的值假設為a1
..............
col = texture2D(tex, a);//此時a的值假設為a2,但是有可能a1不等於a2
如果強制成常量,可以使用:
#pragma STDGL invariant(all)
例子二:
vec2 m = ...;
vec2 n = ...;
vec2 a = m + n;
vec2 b = m + n;//沒法保證a和b完全相等
4.6.3常量表達式的不變體
常量表達式必須要保證是不變體.一個特定的表示式在相同的還是不同的著色器中都必須有相同的結果.這包括同一個表示式出現在同一個頂點和片元著色器中,或出現在不同的頂點和片元著色器中.
如果滿足以下條件,常量表達式必須得出相同的值:
(1)表示式的輸入值相同
(2)執行的操作相同並且順序也相同
(3)所有操作均以相同的精度執行
4.6.4不變體和連結裝置
在頂點和片元著色器中宣告的不變體varying變數必須要匹配.對於內建的特定變數,當且僅當gl_Position被宣告為invariant時,gl_FragCoord才可以被宣告為invariant.同樣的,當且僅當gl_PositionSize被宣告為invariant時,gl_PointCoord才可以被宣告為invariant.將gl_FrontFacing宣告為invariant是錯誤的.gl_FrontFacing的不變體和gl_Position的不變體是一樣的.
4.7修飾順序
當需要使用多個修飾時,它們必須遵循嚴格的順序:
(1)invariant-qualifier storage-qualifier precision-qualifier
(2)storage-qualifier parameter-qualifier precision-qualifier
varying
修飾符只能用在float, vec2, vec3, vec4, mat2, mat3, mat4和包含這些型別元素的陣列上,不能用於修飾結構體。
uniform
修飾符可以和任意基本資料型別一起使用,或者包含基本資料型別元素的陣列和結構體。
attribute
修飾符只能修飾float, vec2, vec3, vec4,mat2,mat3,mat4。attribute變數不能宣告為陣列或結構體
static use
在OpenGL ES中有一個術語叫靜態使用(static use),什麼叫靜態使用呢?
在寫程式碼中,對於一個變數可能具有以下三種情況:
(1)不宣告,不引用(No Reference),呵呵,那就沒有這個變量了,如一個空語句:
;
(2)宣告,但是不使用(Declared, NO used)
attribute vec4 position;
(3)宣告,並使用(static use)
attribute vec4 position;
...
gl_Position = position;//靜態使用,static use
因此,在官方文件中,對於靜態變數的定義為:在著色器中預處理之後至少有一個語句在使用宣告過的變數,哪怕這一句程式碼從來沒有真正執行過。
著色器的預處理過程
著色器的預處理過程是指在著色程式碼真正開始在記憶體中執行之前的整個過程。那麼預處理過程包含哪些工作呢?
-----------------------------------------------------------------------------------------------------------------------------------------------
(1)建立一個空著色器
(2)連結原始碼字串
(3)將原始碼字串替換空著色器中的原始碼
(4)編譯著色器(頂點、片元著色器)
(5)建立一個空的可執行程式
(6)連結著色器
-----------------------------------------------------------------------------------------------------------------------------------------------
以上即為OpenGL ES的預處理過程
5.1運算元
OpenGL ES著色器語言包含如下操作符.
5.2陣列下標
陣列元素通過陣列下標操作符([ ])進行訪問.這是運算元組的唯一操作符,舉個訪問陣列元素的例子:
diffuseColor += lightIntensity[3] * NdotL;
5.3函式呼叫
如果一個函式有返回值,那麼通常這個函式呼叫會用在表示式中.
5.4構造器
構造器使用函式呼叫語法,函式名是一個基本型別的關鍵字或者結構體名字,在初始化器或表示式中使用.引數被用來初始化要構造的值.構造器可以將一個數據標量型別轉換為另一個標量型別,或者從較小的型別轉換為較大的型別,或者從較大的型別轉為較小的型別.
5.4.1 轉換和標量構造器
標量之間轉換:
int (bool) //將布林型值轉換成整數
int (float) //將浮點數值轉換成整數
float (bool) //將布林型值轉換成浮點數
float(int) //將整型值轉換成浮點數
bool(int) //將整數值轉換成布林型值
bool(float) //將浮點數值轉換成布林型值
當構造器將一個float轉換成int時,浮點數的小數部分將被自動捨棄掉.
當int和float轉換成bool時,0和0.0被轉換成false,非0值將被轉換成true.當構造器將bool值轉換成int或float時,false被轉換成0或0.0, true被轉換成1或1.0.
等價構造器,如float(float)也是合法的,但很少使用到.
如果構造器的引數不是標量,如向量,那麼構造器將取其第一個元素.如float (vec3)構造器將取vec3中的第一個值.
5.4.2向量和矩陣構造器
構造器也可以用來從標量集合,向量,矩陣中建立向量和矩陣.同時可以縮短向量長度.
如果使用一個單一的標量來初始化向量,那麼向量的所有值均使用該值進行初始化.如果使用一個單一的標量來初始化矩陣,那麼矩陣的對角線的所有元素均會被初始化為該值,但其他元素將會被初始化為0.0
如果一個向量通過多個標量,向量或矩陣或這幾種的混合來構造,那麼向量的元素將按照引數列表的順序來初始化.構造器將從引數列表中按從左到右的順序去取引數,如果引數有多個值,那麼再依次從這個引數中將值取出.構造矩陣也是類似的.矩陣元素將按照列為主要順序來構造.構造器將依次從引數列表中取出引數值來構造矩陣的元素.如果引數列表中的值的個數超過矩陣或向量的元素個數的話,將會引起錯誤.
如果使用一個矩陣來構造矩陣的話,那麼,引數矩陣中的元素值將放置到新矩陣的相應位置.
如果基本型別(int , float, bool)作為引數傳進構造器,但是要構造的元素型別和傳進來的資料型別不同,那麼將會使用型別轉換.
vec3(float) // initializes each component of with the float
vec4(ivec4) // makes a vec4 with component-wise conversion
vec2(float, float) // initializes a vec2 with 2 floats
ivec3(int, int, int) // initializes an ivec3 with 3 ints
bvec4(int, int, float, float) // uses 4 Boolean conversions
vec2(vec3) // drops the third component of a vec3
vec3(vec4) // drops the fourth component of a vec4
vec3(vec2, float) // vec3.x = vec2.x, vec3.y = vec2.y, vec3.z = float
vec3(float, vec2) // vec3.x = float, vec3.y = vec2.x, vec3.z = vec2.y
vec4(vec3, float)
vec4(float, vec3)
vec4(vec2, vec2)
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 rgba = vec4(1.0); // sets each component to 1.0
vec3 rgb = vec3(color); // drop the 4th component
mat2(float)
mat3(float)
mat4(float)
mat2(vec2, vec2);
mat3(vec3, vec3, vec3);
mat4(vec4, vec4, vec4, vec4);
mat2(float, float,
float, float);
mat3(float, float, float,
float, float, float,
float, float, float);
mat4(float, float, float, float,
float, float, float, float,
float, float, float, float,
float, float, float, float);
5.4.3結構體構造器
一旦結構體被定義,並給了一個型別名,那麼和其同名的構造器就可以使用了.
struct light {
float intensity;
vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0));
傳進構造器的引數必須和結構體裡面宣告的具有相同的順序和型別.
結構體構造器可以用於初始化器或者用在表示式中.
5.5向量元件
向量中每個元件的名稱都使用一個單獨的字元來表示.常用的位置,顏色,或者紋理座標向量的元件直接和幾個便利的數字相關聯.訪問向量中的元件可以使用向量名(.)元件名的方式.
支援的元件名稱如下:
元件名稱x,r,s在向量中是表示同一個元件的同義詞.
注意,為了不和顏色向量中的r(紅色)混淆,紋理向量中的第三個元件名稱使用了p.
訪問超出向量個數的元件會引起錯誤:
vec2 pos;
pos.x // is legal
pos.z // is illegal
元件選擇語法可以一次選擇多個元件:
vec4 v4;
v4.rgba; // is a vec4 and the same as just using v4,
v4.rgb; // is a vec3,
v4.b; // is a float,
v4.xy; // is a vec2,
v4.xgba; // is illegal - the component names do not come from
// the same set.
通過移動和替換元件可以產生不同的向量:
vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
vec4 swiz= pos.wzyx; // swiz = (4.0, 3.0, 2.0, 1.0)
vec4 dup = pos.xxyy; // dup = (1.0, 1.0, 2.0, 2.0)
元件組符號可以出現在左值中,也可以出現在右值中.
vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
pos.xw = vec2(5.0, 6.0); // pos = (5.0, 2.0, 3.0, 6.0)
pos.wx = vec2(7.0, 8.0); // pos = (8.0, 2.0, 3.0, 7.0)
pos.xx = vec2(3.0, 4.0); // illegal - 'x' used twice
pos.xy = vec3(1.0, 2.0, 3.0); // illegal - mismatch between vec2 and vec3
陣列下標索引語法同樣適用於向量.所以:
vec4 pos;
中pos[2]表示第三個元素,與使用pos.z是等價的。
5.6 矩陣元件
訪問矩陣元件可以使用陣列的下標索引語法。使用一維陣列訪問矩陣表示你要訪問矩陣中對應的那一列元件,即返回相應列所有元素的向量。二位陣列才是具體的訪問某一個元件。由於矩陣是列優先的,因此,使用陣列來索引矩陣元素的時候,陣列的第一維表示列,第二維表示行。
mat4 m;
m[1] = vec4(2.0); // sets the second column to all 2.0
m[0][0] = 1.0; // sets the upper left element to 1.0
m[2][3] = 2.0; // sets the 4th element of the third column to 2.0
如果下標越界,將引起編譯時錯誤。
5.7結構體和欄位
結構體欄位的訪問也是要用到點操作符的(.)。
可用於結構體的操作有如下幾種:
等於和賦值運算子只有兩個運算元型別是同一個結構體時才有意義。即使兩個結構體的名稱和欄位一模一樣,他們也不是相等的。包含矩陣和取樣器型別的結構體的賦值和等於操作是未定義的。
Struct S {int x;};
S a;
{
struct S {int x;};
S b;
a = b; // error: type mismatch
}
5.8 賦值
賦值表示式結構如下:
&nb