1. 程式人生 > >opengl shader 入門 超詳細

opengl shader 入門 超詳細

http://bbs.gameres.com/upload/sf_20061018193133.pdf

第三章:語言的定義 

John Kessenich
在這章裡,我們將介紹 OpenGL Shading Language 的所有特性。首先,我們通過一對簡單的
vertex shader  和 fragment shader 的例子來展示它們的基本結構和介面,然後在依次介紹語言的
各個方面。
OpenGL Shading Language 的語法來自 C 語言家族(譯者注:包括 C/C++,JAVA 等)。記號、
識別符號、分號、花括號的巢狀、流程控制以及很多的關鍵字都和 C 語言非常像。// ...和/* ... */  兩
種風格的註釋都可以用。但是它和 C 語言之間還是有很多不同。隨後將討論這些重要的不同之
處。
每個 shader 的例子可能以一個檔案的形式存在,也可能存在在螢幕上(譯者注:就是以字
符串的形式出現在普通的原始碼中)。但是,如同第 7 章描述的,OpenGL API 是以字串的形
式傳遞 shader 的,而不是以檔案的形式。因為 OpenGL 不認為 shader 一定是基於檔案的。[as 
OpenGL does not consider shaders file based.] 
3.1 一對 Shader 的例子
一個程式通常包含兩個 shader,一個 vertex shader 和一個 fragment shader。每種 shader
同時可以有多個。但是所有的 vertex Shader 和所有的 fragment shader 只能有一個 main 函式。
通常,每種型別的 shader 只有一個的話會更加簡單一些。 
下面是一對簡單的 vertex shader 和 fragment shader,他們能用平滑的顏色來表示一個
表面的溫度。溫度的範圍和顏色可以通過引數來指定。首先我們來看 vertex shader,每個頂
點都會讓它執行一次。 
//用 uniform 指定的變數,每個圖元可以有不同的值(changed per primitive) 
uniform float CoolestTemp; 
uniform float TempRange; 
//用 attribute 指定的變數各個頂點可以有不同的值(changed per vertex)
attribute float VertexTemp; 
//用 Varying 指定的變數用來在 vertex shader 和 fragment shader 之間通訊 
varying float Temperature; 
void main() 

   //逐片段的插值計算溫度, 
    // 範圍 [0.0, 1.0]
    Temperature = (VertexTemp - CoolestTemp) /  TempRange; 
/* 在應用程式中用 glVertex()指定的頂點位置,在頂點著色器中可以用內建
的變數 gl_Vertex 來取得。用這個值(gl_Vertex)和當前的模型檢視變換
矩陣來告訴光柵化器這個頂點在哪裡(即在螢幕上的位置). 
*/
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 

這就是 vertex shader.圖元將按照預先給定的步驟(也就是 shader 程式碼)處理。給光柵化器
提供足夠的資訊來建立一個片段。光柵化器對逐頂點計算出來的溫度進行插值計算,為每個片
段產生一個溫度值。接著,每個片段的資訊被輸入到如下的 fragment shader 裡去執行: 
//用 uniform 指定的變數,每個圖元可以有不同的值(changed per primitive) 
//被宣告成一個 vector 型別的向量,它有三個浮點型的成員 
uniform vec3 CoolestColor; 
uniform vec3 HottestColor;
// Temperature 是對 vertex shader 傳過來的值進行逐片段的插值而計算出來的
varying float Temperature; 
void main() 

    //用內建的 mix()函式來得到一個處於最冷和最熱溫度之間的顏色值。 
    vec3 color = mix(CoolestColor, HottestColor, Temperature);
// 給 color 增加一個值為 1.0 的 alpha 分量,生成一個包含 4 個浮點數成員的向量。並
把它設定成噹噹前的片段顏色
    gl_FragColor = vec4(color, 1.0); 
}
兩個 shader 都可以通過宣告一個 uniform 的變數來接受應用程式傳遞過來的自定義的參
數。Vertex shader 可以通過 attribute 屬性的變數來得到和每個頂點關聯的資訊。從 vertex 
shader 傳遞到 fragment shader 的資訊則通過 varying 屬性指定的變數,varying 屬性的變數
在一對 vertex shader 和 fragment shader 之間的定義必須匹配(注:即在 vertex shader 裡
定義了一個,則在 fragment shader 裡也必須有一個)。處在 vertex shader 和 fragment shader
之間的固定功能管道將對 varying 變數進行插值。當一個 fragment shader 讀取一個 varying
變數的時候,它得到的值是已經插值過的。 
Shader 通過讀取內建的變數來和 OpenGL fixed functionality pipeline 進行互動。這些
內建的變數都有一個統一的字首"gl_"。在前面的例子裡。把值寫到 gl_Postion 裡是告訴 OpenGL 
pipeline 這個頂點變換後的位置。把值寫到 gl_FragColor 裡是告訴 OpenGL pipeline 這個片
元是什麼顏色。 
處理一個圖元會多次執行前面的 shader。Vertex shader 是每個頂點執行一次,fragment
則是每個片段執行一次。很多這樣的相同的 shader 執行過程可以是並行的。這些執行過程沒有
直接的聯絡和順序。頂點和頂點之間,以及片段和片段之間是無法進行通訊的。 3.2  資料型別
我們已經在前面的例子裡使用了浮點型的 vector。還有其它很多可用的內建型別可以使圖
形處理變的更加的容易。布林(Boolean),整數(integer),矩陣(matrices),其他的資料型別的
向量(vector),結構體(structure),陣列(array)等都包含在裡面。它們將在隨後的小節裡討
論。字串(string)和字元(character)型別並沒有出現,因為在處理頂點和片元的時候用處非
常少。 
3.2.1  標量
以下是標量的型別 
float 宣告單個的浮點數 
int 宣告單個的整數 
bool 宣告單個的 boolean 型別的變數。 
類似於 C/C++,這些型別用來宣告變數。 
float f; 
float g, h = 2.4; 
int NumTextures = 4; 
bool skipProcessing; 
和原生的 C 語言不同。因為沒有預設的型別,你必須提供型別名稱。變數可以隨用隨定義,而
不是隻能在花括號({})後邊定義.在這點和 C++是一樣的。  
浮點數的宣告文法(也就是表示方式)也是和 C 一樣的,只是不需要用字尾(如 C 裡的 f )來
表示精度,因為 GLSL 裡只有一種浮點數的精度 
3.14159 
3. 
0.2 
.609 
1.5e10 
0.4E-4 
etc. 
通常,浮點數的操作和運算規則和 C 語言裡的是一樣的 
整型數和 C 語言不同,There is no requirement that they appear to be backed in hardware 
by a fixed-width integer register. 從而,當一個定寬的整數在運算過程中發生上溢和下溢
的時候,它的迴繞(wrapping)行為是沒有定義的。位操作,比如說左移(<<)和與(&)操作也不支
持. 那整數都有些什麼特性呢?我們可以保證整數有至少 16-bits 的精度,它可以是真的,負的,
也可以是 0。在給定的範圍內進行的數值運算都可以得到期望的結果。主要到精度是 16 位,再
加上一個符號位,整數可以表達的範圍為[-65535, 65535],或者更大(因為精度至少是 16 位)
和 C 語言裡的整數一樣,GLSL 的整數描述文法可以是十進位制,八進位制,或者十六進位制。 
42    //十進位制整數 
052   //八進位制整數 
0x2A  //十六進位制整數 
同樣的,因為只有一種整數型別,所以也不需要後最來指定整數的精度,整數在表達一個結構
或者一個數組的大小以及迴圈的計數器的時候非常有用。在 Shader 裡,和圖形有關的量,比如
顏色、位置等最好用浮點型別來表達。 
Boolean 型別就是 C++裡的 bool。它只能是兩個值裡的一個:true 或者 false.true 和 false 在
就是 Boolean 型別的兩種常量表示。和它相關的操作,比如 小於(<),邏輯與(&&)等,都會產生
一個 Boolean 型別的值。流程控制結構 if-else 只能接受一個 Boolean 型別的表示式.有了這點
保證,OpenGL Shading Language 比 C++更嚴格. 
3.2.2  向量 (Vectors) 
浮點數,整數以及布林型別的向量是內建的資料型別,它們可以有兩個,三個或者四個分量。
名字如下表: 
vec2 有 2 個浮點型分量的向量(2D 向量) 
vec3 有 3 個浮點型分量的向量(3D 向量) 
vec4 有 4 個浮點型分量的向量(4D 向量) 
ivec2 有 2 個整型分量的向量 
ivec3 有 3 個整型分量的向量 
ivec4 有 4 個整型分量的向量 
bvec2 有 2 個布林型分量的向量 
bvec3 有 3 個布林型分量的向量 
bvec4 有 4 個布林型分量的向量 
內建的向量型別非常有用,用它來儲存和操作顏色、位置、紋理座標非常的方便。內建的變數
和函式都用到了這種型別,它們還支援一些特定的操作。Finally, hardware is likely to have 
vector-processing capabilities that mirror vector expressions in shaders.
注意到語言並不區分顏色向量和位置向量以及浮點向量的其他用途。這就是從語言的角度的向
量(並不以功能區分)。 我們可以通過訪問欄位(field)的形式來訪問向量的分量(類似與結構體 structure),也可以
以陣列的形式訪問。舉個例子,如果一個 vec3 型的 postion 變數,我們可以把它當成
vector(x,y,z),position.x 就是第一個成員。 
總之,以下的都是用來訪問一個向量的分量的: 
x, y, z, w  把一個向量當作位置來看 
r, g, b, a  把一個向量當作顏色來看 
s, t, p, q  把一個向量當作紋理座標來看 
沒有顯式的方法區分一個向量是用來表示顏色、位置還是一個紋理座標。分量的選取名字
(selection names)僅僅是為了讓 Shader 有更好的可讀性。編譯的時候,只是檢查向量的大
小是不是足夠儲存指定的分量(一個 vecector3 是沒有.a 和.w 分量的)。而且,如果一次選擇
了多個分量(swizzling,在第 3.7.2 節裡討論)的情況也是相同的(也是僅僅檢查向量是不是足
夠的大). 
向量也可以通過一個以 0 開始的數字下標(index)來索引分量,即向量也可以被看成一個數組。
Position[2]返回的是 position 的第三個分量。下標也可以一個變數,這就允許我們通過迴圈
的方式來檢索一個向量的所有分量。向量的乘法有專門的含義,向量和矩陣的乘表示一個線性
變換。Swizzling,索引,以及其他的操作將在 3.7 節裡詳細介紹。 
3.2.3  矩陣
內建型別裡還有浮點型別的矩陣,有 2 x 2 , 3 x 3, 和 4 x 4 的矩陣。 
mat2 2 x 2 浮點型別的矩陣 
mat3 3 x 3 浮點型別的矩陣 
mat4 4 x 4 浮點型別的矩陣 
矩陣用來儲存線性變換非常有用,語義上我們把它當成矩陣,一個向量和一個矩陣相乘時,等
效於在向量上施加了一個對應的線性變換。相應的,在 OpenGL 裡,矩陣是按列優先
(Column-Major)的方式組織的。 
你可以把一個矩陣當作一個列向量的陣列來使用,如果 transform 是一個 mat4 型變數,
transform[2]是 transform 的第三個列向量,transform[2]的型別是一個 vec4,Column 0 是
第一個列向量(這點又是 C 語言的習慣),因為 transformp[2]是一個向量,你也可以把一個向
量當成陣列來看,transform[3][1]就是第四個列向量的第二個分量。所以我們可以把一個矩陣
當作一個二維陣列來用。記住第一個下標表示的是列,而不是行,第二個下標指示的才是行。 3.2.4 取樣器
紋理資料的檢索需要一些資訊來明確是檢索什麼紋理或者哪一個紋理單元,OpenGL Shading 
Language 並不真正關心紋理單元的實現和其它形式的紋理檢索硬體的細節。因此,它提供了一
個不透明的操作來封裝紋理的操作方式,這個操作被成為取樣器(SAMPLERS)。取樣器的型別
有以下幾種: 
sampler1D 訪問一個 1D 的紋理 
sampler2D 訪問一個 2D 的紋理 
sampler3D 訪問一個 3D 的紋理 
samplerCube 訪問一個 CubeMap 的紋理 
sampler1DShadow 訪問一個 1D 深度紋理,帶比較操作 
sampler2DShadow 訪問一個 2D 深度紋理,帶比較操作 
當應用程式初始化了一個取樣器的時候,OpenGL 的實現(譯註 OpenGL 是個規範,需要各個廠
家去實現)將查詢一個紋理所需要的資訊都儲存到取樣器裡。Shaders 本身並不能初始化一個
取樣器,也不能把它(sampler)傳遞個使用者或者是內建的函式。作為一個引數,取樣器不能被
修改,所以 shader 不能改變一個取樣器的值。 
舉個例子,一個取樣器可以以這樣的方式來宣告: 
uniform sampler2D Grass; 
(Uniform限定符將在3.5節詳細介紹.) 
這個變數可以被傳遞給相應的紋理檢索函式來訪問紋理: 
vec4 color = texture2D(Grass, coord); 
coord 是一個 vec2,儲存了 2 維的、草的紋理的座標。紋理檢索的結果是一個顏色值。OpenGL API 
和編譯器會檢查 Grass 是不是真的裝入了一個二維紋理以及 Grass 是不是傳遞給 2 維的紋理檢
索函式(texture2D)。 
Shaders 並不操作取樣器的值。舉個例子,Grass+1 是非法的。如果一個 shader 想要組合多個
紋理,可以使用下面介紹的取樣器陣列的方法: 
const int NumTextures = 4; 
uniform sampler2D textures[NumTextures]; 
These can be processed in a loop: 
for (int i = 0; i < NumTextures; ++i) 
    ... = texture2D(textures[i], ...); Grass+1 這樣的慣用方法相應的變成了類似 textures[GrassIndex + 1],這是一種操作使用哪一
個紋理取樣器的合法途徑。 
3.2.5  結構體
OpenGL Shading Language 提供了一種類似與 C 語言的自定義結構體。舉個例子, 
struct light 

    vec3 position; 
    vec3 color; 
}; 
和 C++一樣,結構體的名字就是自定義型別的名字,不要 typedef。事實上,typedef 也是個保
留字,但是目前還不需要它,一個前面例子中定義的 light 型變數宣告如下: 
light ceilingLight; 
結構體的其他方面和 C 語言也一樣,可以內嵌(也稱區域性結構體,C++裡有區域性類)和巢狀定義。
內嵌結構體的型別名字和結構體的宣告的語句塊(the structure they are declared in)有
相同的作用域。最後一點,和 C 語言一樣,每個層次上的結構體裡成員都有它們自己的名字空
間(這個名字空間就是這個結構體)。 
不支援 Bit-Fields,bit-fields 是用來宣告一個有指定 bit 位數的整數的方法。 
目前,結構體是唯一的自定義資料型別,關鍵字 union ,enum 和 class 都是保留字,一滿足將
來的需求。 
3.2.6 Arrays 
可以建立一個任何資料型別的陣列, 
vec4 points[10]; 
建立了一個有 10 個 vec4 的陣列,下標從 0 開始。GLSL 裡沒有指標的概念,宣告陣列唯一的方
法就是用一對方括號。陣列不一定需要宣告一個大小,如下的宣告 
vec4 points[]; 
是允許的,直到以下的兩種情況成立: 
1.在使用陣列前,我們用一個給定的大小重新聲明瞭這個陣列,比如: 
vec4 points[];    // points 是一個大小未知的陣列。 
vec4 points[10];  // points 現在是有 10 個元素的陣列。 
在這以後,就不能重新宣告這個變量了: vec4 points[];    // points 是一個大小未知的陣列 
vec4 points[10];  // points 現在是有 10 個元素的陣列。 
vec4 points[20];  // 非法的宣告 
vec4 points[];    // 這也是非法的宣告 
2.所有操作這個陣列的下標在編譯期均為常量,在這種情況下,編譯器會讓陣列足夠大,
以滿足用到的最大的下標。比如:  
vec4 points[];         //points 一個大小未知的陣列。 
points[2] = vec4(1.0); //points 現在是有 3 個元素的陣列 
points[7] = vec4(2.0); //points 現在是有 8 個元素的陣列 
這種情況下,在執行時(runtime),陣列的大小隻有一個:它是由編輯器檢測到的最大下標決定
的。 
這種特性在操作內建的紋理座標陣列的時候非常有用,在 GLSL 內部,紋理座標陣列的宣告如
下: 
varying vec4 gl_TexCoord[]; 
如果程式(Shader 程式)在編譯時只用到了下標 0 和 1,那麼這個陣列就隱式的的等效於
g_TexCoord[2]。如果需要用一個變數來索引陣列,那麼 shader 必須事先明確的宣告陣列的大
小。當然,儘量的讓陣列的大小最小化也是非常重要的,尤其是對 varying 型的變數,因為它
們的資源是受到硬體的限制的。 
多個 Shader 共享一個相同的陣列的時候,它們可以各自宣告為不同的大小,聯結器(linker)
在連線的時候會自動取他們中最大的那個。 
3.2.7 Void 
Void 型提供了一種宣告沒有返回值的函式的方法,比如,如果 main 函式沒有返回型別,它必
須這樣宣告: 
void main() 

    ... 

除了宣告沒有返回值的函式以外,void 型別沒有其他的用途。 
3.2.8 宣告和作用域
變數的宣告和 C++非常的像,可以隨用隨定義,以及和 C++一樣的作用域規則,比如: 
float f; 
f = 3.0; 
vec4 u, v; for (int i = 0; i < 10; ++i) 
    v = f * u + v; 
一個在for語句裡定義的變數,作用域只到迴圈語句的結束。可是,變數可能並不在if語句裡定
義,simplifying implementation of scoping across the else sub-statement, with little 
practical cost. 
如同 C 語言,變數名是大小寫敏感的,必須以字母或者下劃線(_)開始,並且只能由字母、數
字、和下劃線(_)。使用者定義的變數不能以”gl_”開始,因為這是給 OpenGL 保留的(如所有
的內建變數都是由 gl_打頭的)。包含了連續兩個下劃線(__)的的變數也作為保留字。 
3.2.9 型別匹配和提高(Promotion)
OpenGL Shading Language 是要求嚴格匹配型別的。通常,賦值的型別必須匹配,傳遞給函式
的實參必須和函式形參的型別一致,傳遞給運算子的型別也必須符合運算子的要求。型別不會
自動提升(promotion)為另外一種型別。這有時會讓 Shader 有額外的限制。但是,這讓語言
更加簡單,避免了一些常見混淆。例如,在呼叫一個函式的時候,不會因為過載問題而不知道
選擇哪個函式。 
3.3 Initializers and Constructors 
Shader 的變數在宣告的時候可能被初始化,如同 C 語言一樣,以下的例子裡,b 在宣告的時候
初始化,而 a,c 沒有: 
float a, b = 3.0, c; 
用 constant 限定符修飾的變數必須被初始化。 
const int Size = 4;  // 必須初始化 
Attribute, uniform, 和 varying 變數在宣告的時候不能初始化 
attribute float Temperature;  // 不允許初始化, 
                              // vertex API 會正確設定它 
uniform int Size;             //不允許初始化, 
                              // uniform 設定 API 會設定它 
varying float density;        //不允許初始化, vertex 
                              // shader 必須在程式裡設定這個變數 
初始化集合型別(vector,matrix 等)時,不管是在宣告時還是其他時刻,構造器(CONSTRUCTORS)
將被使用。這裡沒有和 C 語言裡的花括號"{…}"類似的初始化方法,只有構造器。在詞義上,
構造器看上去像個函式呼叫,只是原本寫函式名的位置是型別的名字(和 C++裡的建構函式一
樣),例如:把一個 vec4 初始化成(1.0, 2.0, 3.0, 4.0)的程式碼如下: vec4 v = vec4(1.0, 2.0, 3.0, 4.0); 
另外,無論是在初始化或者在其他地方,構造器在詞義上都是一樣的。 
vec4 v; 
v = vec4(1.0, 2.0, 3.0, 4.0); 
和結構體一樣,所有的內建型別都有構造器(除了取樣器),例如: 
vec4 v = vec4(1.0, 2.0, 3.0, 4.0); 
ivec2 c = ivec2(3, 4); 
vec3 color = vec3(0.2, 0.5, 0.8); 
mat2 m = mat2(1.0, 2.0, 3.0, 4.0); 
struct light 

    vec4 position; 
    struct lightColor 
    { 
        vec3 color; 
        float intensity; 
    } 
} light1 = light(v, lightColor(color, 0.9)); 
矩陣的各個分量一列優先的方式填寫,前面的例子裡的變數 m 就一個矩陣。 
到目前為止,我們介紹的構造器為變數的每個分量都賦了一個值,vector 的內建構造器可以只
接受一個引數,其它分量的值將從這個值複製。 
vec3 v = vec3(0.6);和 vec3 v = vec3(0.6, 0.6, 0.6)是等效的。 
這僅僅是對向量型別而言的,結構體在構造的時候必須為每個成員都指定一個值。矩陣構造器
也可有隻接受一個引數的形式,但是在這種情況下,只初始化矩陣的對角線,其他的分量都被
初始化成了 0。 
mat2 m = mat2(1.0);  // 初始化了一個 2x2 的單位矩陣 
等效於 
mat2 m = mat2(1.0, 0.0, 0.0, 1.0);    //初始化了一個 2x2 的單位矩陣 
構造器也可以使用向量和矩陣做引數。唯一的規則就是引數必須有足夠的分量來初始化變數的
所有成員。 
vec4 v = vec4(1.0); 
vec2 u = vec2(v);  //  v 的前兩個分量初始化了 u 
mat2 m = mat2(v); vec2 t = vec2(1.0, 2.0, 3.0);  // 這是允許的,3.0 簡單的被忽略了。 
矩陣的各個分量以列優先的方式被讀出來、以列優先的方式被填寫。多餘的分量或者引數被簡
單的忽略掉。這在想收縮(shrinking)一個值的是非常有用,比如把一個顏色的 alpha 分量或者
位置的 w 分量消除掉。 
3.4 Type Conversions 型別轉化
顯式的型別轉化通過構造器完成,比如: 
float f = 2.3; 
bool b = bool(f); 
將把 b 設定為 true,這點在流程控制上非常有用,類似 if,which 需要一個布林型別的值,布林
型別的構造器把一個非 0 的值轉化為 true,把值為 0 的轉化為 false. 
OpenGL Shading Language 不提供類似於 C++的型別轉化,C++裡的型別轉化經常會引擎混淆:是
轉化為另外一種型別呢?還是重新解釋為另外一種型別。事實上,GLSL 裡沒有把一種型別重新
解釋為另外一種型別的方法,這裡沒有指標,沒有 union,沒有隱式的型別轉化,也沒有
reinterpret cast。我們只能使用構造器來代替型別轉化,傳給構造器的引數將被轉化為構造
出來的型別。因此,以下的方式是允許的: 
float f = float(3);  // 把整型的 3 轉化為浮點型的 3.0 
float g = float(b);  // 把布林型別的 b 轉化為浮點型的 g 
vec4 v = vec4(2);    // 向量 v 的所有分量都被設定成 2.0。 
當轉化一個布林型別的值的時候,true 被轉化 1 或者 1.0,false 被轉化為 0(0.0 浮點的時候,
注意 0 是整數,而 0.0 是浮點數,前面的 1 和 1.0 也相同)。 
3.5 限定符和 Shader 的介面
限定符可以修飾變數和函式的形式引數。可以修飾函式形式引數的限定符(const ,in,out,和
inout)將在第 3.6.2 節討論,這一節將針對其他的限定符,他們的大部分組成了 shader 的接
口。以下是除了函式形參以外的所有限定符的列表: 
attribute 經常改變的資訊,從應用程式傳遞到 vertex shader 
uniform 不經常改變的資訊,vertex 和 fragment shader 都有 
varying 從 vertex shader 到 fragment shader 傳遞一個需要插值的資訊 
const 和 C 語言一樣,宣告一個只讀的,編譯時刻的常量 
從一個 shader 裡傳入和傳出資料在典型的程式設計環境裡是很不一樣的。從一個 shader 傳入和傳
出資料是通過讀寫內建變數和使用者定義的 attribute,uniform,和 varying 變數來實現的。最常
見的變數在本章開頭的例子裡介紹過了。它們分別是是 gl_Position,用來輸出頂點位置的齊次座標,以及 gl_FragColor,Fragment Shader 用它來輸出片段的顏色。所有的內建變數的列
表在第四章裡有提供,attribute,uniform ,和 varying 變數在本長開頭的例子裡有簡單的介紹,
我們使用它們來給 shader 傳遞資訊和讀取資訊。在這節裡,我們將逐個的討論。 
變數的限定符,attribute,uniform,和 varying 必須宣告為全域性,這是非常明顯的,因為它們都
需要在 shader 以外使用。針對單個的程式(一個程式可能由多個 shader 組成),它們都共享
一個相同的名字空間 
限定符通常在變數的型別名前指定,因為沒有預設的型別,由限定符修飾的變數一定會有一個
型別。 
attribute float Temperature; 
const int NumLights = 3; 
uniform vec4 LightPosition[NumLights]; 
varying float LightIntensity; 
3.5.1 Attribute  限定符
Attribute 用來指示一個變數或者屬性的資料是由應用程式提供給 shader 的,這些資料需要經
常變動(在應用程式端),它們最多每個頂點變化一次,這種變化由程式直接或者間接的產生,
有內建的屬性,如 gl_Vertex 和 gl_Normal,用來讀取 OpenGL 的傳統狀態,以及其他的自定義
屬性,我們可以自己命名它們的名字。 
Attribute 只能是浮點型別的標量,浮點型別的向量,和浮點型別的矩陣使用。沒有整數,布
爾,結構體,或者 attribute(注:以後 attribute 修飾的變數簡稱為 attribute。Uniform 等
限定符也類似)陣列。這是為了讓 OpenGL 系統改變 attribute 的時候更加的有效,attribute
在 shader 裡不能被修改。 
限定符也不能在 fragment shader 裡使用。 
3.5.2 Uniform  限定符
Uniform 限定符修飾的變數(uniforms),類似於 attribute,它也是在 shader 的外部被設定的,
主要是面向那些不是經常需要改變的資料。Uniforms 變數最多每個圖元改變一次(譯者注:
attribute 是可以由 glVertex、glColor 等函式設定的,所以可以每個頂點改變一次。而設定
uniform 的函式不能在 glBegin/glEnd 之間呼叫,所以只能最多每個圖元改變一次)。Uniform
支援所有的資料型別,以及所有的資料型別的陣列。如果一個程式包含多個 vertex / fragment 
shader,他們共享一個相同的全域性的 uniform 變數的名稱空間。因此,如果一個 uniform 變數
在 vertex program 和 fragment program 裡有相同的名字的話,它們是同一個變數。 
Uinform 在一個 shader 裡是不能被寫如的。這是因為多個處理器可能共享一個相同的資源來保
存一個 Uniform,如果一個 uniform 被改變了,就打破了語義上 unifrom 的”一致性”。(This 
is sensible because an array of processors may be sharing the same resources to hold 
uniforms and other language semantics break down if uniforms could be modified.)回憶一下,除非把一個取樣器(sampler)當作一個函式的引數,否則在宣告一個取樣器的時候
必須使用 uniform 限定符。這是因為取樣器是不透明的,把它們宣告成一個 uniform 的變數允
許系統用相應的紋理很紋理單元把取樣器初始化好。 
3.5.3 Varying  限定符
Varying 限定符修飾的變數(簡稱 Varying),是在 Vertex Shader 和 fragment Shader 之間
通訊的唯一途徑。類似的變數在 fragment shader 和 vertex shader 之間建立了介面。主要意
圖是為了針對繪製圖元時的一些特殊屬性,每個頂點可能又不同的值,這些值在圖元光柵化的
時候需要進行插值。Vertex shader 把這些值寫入到 varying 變數中去,當 fragment shader
讀取這些變數的時候,得到的是經過插值後的資料。如果一個數據在一個圖元中都是相同的(圖
元所有的 fragment中都相同),那麼 vertex shader就沒有必要把這個值傳遞給 fragment shader
進行通訊,而只需要直接把這個值通過 uniform 變數傳遞給 fragment shader 就可以了。 
使用 varying 的例外是當一個值在程式中需要經常改變的時候,無論是每個三角形或者頂點之
類更小的集合內經常變化,這樣的值可能通過一個 attribute 變數傳遞給 vertex shader。然
後通過 varying 變數繼續傳遞下去會比直接使用 uniform 變數兩的更加有效率。 
Varying 變數的自動插值是需要通過透視矯正的。這對無論是什麼型別的資料都是必須的。否
則這些資料在曲面細分的邊緣會變的不平滑。but its derivative would not be. 
一個 varying 變數通常在 vertex shader 中被寫入,然後在一個 fragment shader 裡被讀取。
Vertex shader 也許會讀一個 varying 變數,取回剛剛寫入的資料。如果去讀一個沒有被寫入
資料的 varying 變數,這樣返回值是沒有定義的。 
3.5.4 Constant  限定符
一個變數如果用 constant 修飾的話(除了函式的形參以外),在編譯期間是一個常量,在宣告
這個變數的 shader 以外是不可見的。 我們可以宣告非標量的常量。 
const int numIterations = 10; 
const float pi = 3.14159; 
const vec2 v = vec2(1.0, 2.0); 
const vec3 u = vec3(v, pi); 
const struct light 

    vec3 position; 
    vec3 color; 
} fixedLight = light(vec3(1.0, 0.5, 0.5), vec3(0.8, 0.8, 0.5));
前面定義的變數都是編譯期常量,編譯器將處理這些變數,使用處理器能支援的精度來表示它
們。在執行期間,不需要為 const 型變數分配資源。 3.5.5 Absent 限定符
如果在宣告的時候沒有指定變數的限定符,變數將在這個 shader 裡可讀可寫。無限定符
(Nonqualified)變數在相同型別(vertex shader 和 fragment shader)的、連線到一個 program
的 shader 裡是可以共享的。但是對於 Nonqualified  變數,vertex shader 和 fragment shader
有不同的全域性名字空間。因此,自定義的 nonqualified 變數在 program 以外是不可見的。這
種變數在 program 之外可見的特權被保留給 uniform,attribute 和用來表示 Opengl 狀態的內
建變數。 
Nonqualified 變數的生命週期是 shader 的一個執行期。也沒有類似於 C 語言的 static 修飾
符,可以讓這個變數在這個 shader 執行完到下一次執行時,仍然能保留著上次的被設定的值。
如果這樣的話,將會讓並行處理更加困難,當有幾個執行過程併發的時候,它們就會使用一個
相同的儲存器。通常,可寫的變數必須對每一個例項(這裡是每個 shader 的一個執行過程)都
唯一。因此這樣的變數是不能在兩個執行過程共享。 
因為 vertex shader 和 fragment shader 對 Nonqualified 變數,有不同的全域性名字空間,因
此我們不能使用 nonqualified 變數來進行兩種 shader 之間的通訊。只讀的變數可以使用
uniform 在兩種 shader 間進行通訊。如果一個變數需要在 vertex shader 寫入,在 fragment 
shader 端讀出的話,只能通過 varying 機制。 
3.6 流程控制
流程控制和 C++非常類似。一個 shader 的入口是 main 函式,一個包含了 vertex shader 和
fragment shader 的程式有兩個 main 函式。一個作為 vertex shader 的入口,一個作為 fragment 
shader 的入口。在執行 main 函式前,所有的全域性變數的宣告都將被執行。 
可以用 for ,while 和 do-while 來實現。變數可以在 for 和 while 語句中定義,它們的生命
週期在子語句末尾結束。break 關鍵字的作用和 C 語言裡一樣。  
類似於 C++,選擇可以通過 if / if-else 來實現。一個例外就是在 if 語句裡不能宣告變數。用
(?:)操作符號來進行選取也是可以的,比較嚴格的是第二和第三個運算元的型別必須相同。 
If 和 while 語句裡的表示式,或者用來結束語句的表示式,必須是個 boolean 型別的標量。和
C 語言裡一樣,邏輯與操作符(&&)左邊的表示式為 false 的情況下,右邊的表示式不會被計
算。邏輯或操作符(&&)左邊的表示式為 true 的情況下,右邊的表示式不會被計算。類似的,
在選擇操作符(:?)中,只有一個表示式會被計算。邏輯異或(^^)操作也支援,操作符兩端
的表示式都需要計算。 
一個特殊的分支:discard,能阻止一個片段被寫入到幀快取裡,當流程控制到達 discard 關鍵
字的時候,正在處理的片段被標記為 discard,GL 的實現可能繼續也可能終止這個 shader 的執
行。但是都保證這個片段不會影響到幀快取。 3.6.1 函式
函式呼叫的操作非常類似於 C++,函式的名字可以被過載,用函式的引數來區分,但是不能單獨
以返回值來區分過載函式。在函式被呼叫前,函式必須被定義或者被宣告。函式的引數都需要
經過檢驗。因此一個函式的引數列表為空()的函式宣告並不表示一個函式的宣告是不確定的,
相反,它表示這個函式不接收任何引數。同時,函式的引數必須完全匹配,不會進行任何自動
轉化。因此選擇哪一個過載函式是非常明確的。 
和 C++一樣,使用 return 退出一個函式,一個不是返回空型別(novoid)的函式必須返回一個
值。實際返回值的型別必須和宣告中的返回值型別完全匹配 
函式不能遞迴的呼叫,無論是直接的還是間接的。 
3.6.2 呼叫協定
GLSL 使用傳值(call by value)和傳遞返回值(call by return)的呼叫協定,call by value
和 C 類似,被指定為 input 的引數的值被拷貝一份後,將拷貝傳遞給函式,而不是傳的引用。
因為沒有指標,所以不需要擔心引數和其他變數指向同一段記憶體(aliase)。Call by return 部
分表示一個被修飾為 output 的引數將作為返回值返回給函式的呼叫者,在函式返回時候,被調
用函式把資料回寫到這個變數,並返回給呼叫者。 
為了指明一個引數何時進行拷貝,需要使用關鍵字 in , out 或者 inout 來修飾。對只需要傳
遞給函式,而不需要返回的,使用 in。in 也是預設的修飾符,如果沒有指定,預設為 in。如
果一個引數不是傳遞給函式,而是從函式裡得到返回值的話,使用 out。既要傳遞給函式,也
要從函式得到返回值的話,使用 inout。 
in 傳遞給函式,但是不作為返回值,但是在函式裡依然可寫。 
out 只作為返回值,可讀,但是在函式入口出讀出的資料值無定義。 
inout 傳入和返回。 
Const 限定符也可以在函式的引數上使用,這裡,並不是以為這個變數在編譯期是個常數,它
表示由 const 修飾的變數在這個函式裡不允許被改變。注意到一個普通的,沒有限定符的,僅
僅用做輸入的變數是可寫的,在函式返回的時候不需要傳遞迴資料給呼叫者。因此,在這裡,
const in 和 in(或者沒有限定符的變數)是有區別的,當然 out 和 inout 不能宣告成 const。 
例子:: 
void ComputeCoord(in vec3 normal,  // 'normal'被傳遞給函式,可讀,可寫,但是不能
                                   // 給呼叫者返回資料。 
                  vec3 tangent,    // 等效於有 in 修飾符 
                  inout vec3 coord)// 傳入和傳出 
Or, vec3 ComputeCoord(const vec3 normal,// normal 不可寫 
                  vec3 tangent, 
                  in vec3 coord)    //函式有返回值 
以下的寫法是不允許的。: 
void ComputeCoord(const out vec3 normal, //非法; normal 不可寫 
                  const inout vec3 tang, //非法; tang 不可寫 
                  in out vec3 coord)     //非法; 應該使用 inout 
函式可以返回一個值或者不返回任何東西,如果一個函式不返回任何資料,則必須宣告為 void
型別。如果函式有返回值,返回值可以是出陣列以外的任何型別。結構也可以作為返回值,結
構裡可以包含陣列。 
3.6.3 內建函式
有大量的內建的函式可以使用,他們都在第 5 章裡有詳細介紹。 
這些函式也是可以被過載,提供自定義的實現。過載一個函式的時候,只需要在呼叫時候的定
義域裡提供函式的原型或者實現,編譯器和聯結器會尋找自定義的函式版本來解決這個呼叫。
舉例來說,一個內建的函式宣告如下: 
float sin(float x); 
如果 Shader 想試驗一下精度或者效能,或者在特定的領域特例化一個 sin 函式,我們可以使
用這樣的方式過載: 
float sin(float x) 

    return <.. some function of x..> 

void main() 

    // 呼叫自定義的 sin,而不是內建的 sin 
    float s = sin(x); 

這和標準語言的連線技術類似,使用一個函式庫的時候,我們首先有一個區域性定義域,這個定
義域裡的函式將先於庫裡的函式被使用。如果一個函式在不同的 shader 裡定義,那麼應該保證
有一個原形在使用這個函式前被宣告,否則將使用內建的函式版本。 
3.7 操作符
表 3.1 包含了 GLSL 裡可用的操作符,以操作符號的優先順序排列,優先順序和相互關係和 C 語言一
樣。 表 3.1.  操作符,以操作符的優先順序排序
操作符 描述
[ ] 索引 
.  成員選取和 swizzle 
++ -- 後增/減 
++ -- 前增/減 
- ! 取負和取反 
* / 乘除 
+ - 加減 
< > <= >= 比較操作符 
== != 等於判斷 
&& 邏輯與 
^^ 邏輯異或 
|| 邏輯或 
?: 選擇 
= += -= *= /= 賦值 
,  ,操作符(Sequence) 
3.7.1 索引
向量,矩陣和陣列可以用下標操作符來索引,所有的索引都是從 0 開始的。第一個元素在索引
0 的位置。索引一個數組的方法和 C 語言一樣。 
對向量進行索引將返回一個標量形式的分量。這允許給每個分量一個數字形式的名字:0,1,…,
同時也允許用名字索引的形式來訪問它的分量。例如: 
vec4 v = vec4(1.0, 2.0, 3.0, 4.0); 
float f = v[2];  // f takes the value 3.0 
這裡, v[2] 的值為浮點形式的標量 3.0, v[2]被賦值給 f。 
矩陣的索引結果是一個列向量。例如: 
mat4 m = mat4(3.0);  // 把對角線位置初始化為 3.0 
vec4 v; v = m[1];   // v 的值現在是(0.0, 3.0, 0.0, 0.0)  
這裡 m 的第二列,m[1]被當作一個向量被賦值給 v。 
如果用一個小於 0,或者大於物件大小的下標去索引陣列、向量或者矩陣,這樣的行為是未定
義的。 
3.7.2 Swizzling 
標準的結構成員選擇操作符(.)也可以用來 SWZIZZLE 一個向量的分量。在選擇的
時候,只要在 Swzille 操作符(.)後按不同的順序列出向量的分量,分量可以以重新
排列後的順序被選擇。例如: 
vec4 v4; 
v4.rgba;  // 一個 vec4 變數,和 v4 這樣的用法相同 
v4.rgb;   // is a vec3, 
v4.b;     //一個浮點, 
v4.xy;    // 一個 vec2, 
v4.xgba;  // 非法,名字不是來自於一個相同的名字集合。 
分量的名字順序可以重新排列,甚至可以重複: 
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) 
最多有四個分量的名字可以以 swizzle 的方式列出。這種方法將產生出一種不存在的型別。
Swizzling 的規則在作為左值(用來寫)和右值(用來讀)的時候,是有一些細微的區別的。
作為右值的時候,可以任意組合。作為左值的時候,分量名字不能重複出現。例如: 
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); //非法,x 出現兩次。 
作為右值,這樣的語法可以用在任何一個可以產生向量型別的表示式上。比如,從
一個紋理查詢結果中產生一個有 2 個分量的向量: 
vec2 v = texture1D(sampler, coord).xy; 
內建函式 textureID 返回一個 vec4. 
3.7.3 逐分量操作
有點例外的地方是。當一個對一個運算子號被施加在一個向量上時候,等效於這個操作符施加
在向量的每個分量上。例如: 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; 
如果一個二元運算子被施加在一個向量和一個標量上,那麼這個標量將和向量的每個分量進行
運算。如果兩個向量參加運算。它們的大小(維數)必須相同。 
一個例外的情況是當一個向量和一個矩陣相乘,或者兩個矩陣相乘,他們遵照標準的線性代數
的規則,而不是逐分量的運算。 
遞增和遞減運算子(++和—),以及取反運算子的作用和規則和 C 語言裡的一樣。如果被施加到
一個向量和矩陣上的時候,將會進行逐分量的遞增和遞減操作。這幾個運算子只能對整數或者
浮點型別上。 
加(+)、減(-)、乘(*)、除(/)運算子的作用和C語言裡一樣。都進行逐分量的運算。除了前面提
到的線性代數裡的乘法一樣。 
vec4 v, u; 
mat4 m; 
v * u;  // 逐分量相乘 
v * m;  // 線性代數裡的行向量乘矩陣。 
m * v;  //線性代數裡的矩陣乘列向量。 
m * m;  //線性代數裡的矩陣相乘 
其他的運算子都是逐分量的。 
邏輯非(!),邏輯與(&&),邏輯或(||)以及邏輯異或運算子,只能對 boolean 型標量進行操作。
它們的運算結果也是個 boolean 型的變數。它們不能對向量進行操作。沒有一個內建的函式,
可以用來計算 boolean 型向量的邏輯運算。 比較運算子(<,>,<=,和>=)只能對浮點和整數型別進行操作,運算結果是一個 boolean 型。有內
建的函式,比如 lessThanEqual,可以用來逐分量的比較兩個向量,其結果是一個 boolean 向
量。 
相等運算子(==,!=)可以對除了陣列以外的任何型別進行操作,它對每個分量或者結構體的每
個成員進行測試,測試結果是個 boolean 型,用來指示兩個運算元是不是相等。如果兩個操作
數相等,首先,它們的型別必須相等。它們的每個分量或者成員必須相等。如果要得到一個逐
分量比較的結果,就得使用內建的函式 equal 和 notEqual(==和!=返回的是標量,不是一個向
量)。 
等於(==)、不等於(!=) 、比較運算子(<, >, <=, and >=)、邏輯運算子(!)都產生一個boolean
型的標量。因為流程控制語句需要一boolean型的標量。如果一個內建的函式equal等產生了一
個boolean型的向量,我們可以通過內建的函式any和all來把這個向量轉化成一個標量。例如:
要求一個向量中是不是有分量小於另一個向量的對應分量: 
vec4 u, v; 
... 
if (any(lessThan(u, v))) 
    ... 
賦值運算子(=)需要左邊和右邊的型別嚴格的匹配,除了陣列以外的任何型別,都可
以被賦值。其它的賦值運算子(+=, -=, *=, and /=)和C語言類似,但是展開後語義上
必須合法。 
a *= b     a = a * b 
這裡,a*b 在語義上必須是合法的,a*b 結果的型別必須和 a 的型別相同。其他的賦值也
類似 
三重選擇運算子(?:)是一個三目運算子(exp1 ? exp2 : exp3)。這個運算子先計算出第
一個表示式,它必須是個boolean型的標量。如果結果為真,則選擇計算第二個表示式,並將結
果作為選擇符的運算結果。如果為假,則選擇計算第三個表示式,第二和三兩個表示式裡只有
一個會被計算。第二和三兩個表示式的型別必須一樣,它們可以是除了陣列以外的任何型別。
運算子的返回值型別就是第二和三兩個表示式的型別。 
序列運算子(逗號,)返回以逗號隔開的、右邊優先的表示式列表的值和型別,所有的表
達式從左到右的順序計算,返回值為最後一個表示式的值。 
3.8 Preprocessor 前處理器
前處理器和C語言類似,支援如下的預處理指令如下: 
#define 
#undef 
#if #ifdef 
#ifndef 
#else 
#elif 
#endif
as well as the defined operator are exactly as in standard C. This includes macros with 
arguments and macro expansion. Built-in macros are 
__LINE__
__FILE__
__VERSION__
__LINE__ substitutes a decimal integer constant that is one more than the number of 
preceding new-lines in the current source string. 
__LINE__用來代替一個表示當前行號的整型常數, 
__FILE__用來代替一個十進位制整數,表示當前處理的是哪個原始碼字串號。 
__VERSION__用來代替一個十進位制整數,表示當前的OpenGL Shading Language的版本號,本書
裡的OpenGL Shading Language版本號是 100. 
同時還支援以下指令: 
#error message 
#line 
#pragma
#error 輸出資訊到shader的information log裡。如果一個語義錯誤產生的時候。編譯器然後
將收到這個資訊,  
#pragma 是和GLSL的實現相關的,如果一個GLSL實現不支援這個#pragma引數,將簡單的忽略掉。
但是下列pragmas是可以移植的。 
使用優化pragma指令。 
#pragma optimize(on) 
#pragma optimize(off) 
開啟和關閉優化開關,可以幫助開發和除錯shader。只能在函式外部指定,預設情況下,
所有的優化開關都是關閉的。
除錯pragma 
#pragma debug(on) 
#pragma debug(off) 開啟#pragma debug 開關,可以向 shader 寫入 debug 資訊,這些資訊可以被除錯器使用,
#pragma debug 只能在函式以外使用,預設的情況下,該值為 off。 
#line 在巨集展定義的時候,必須是以下兩中形式中的一種。 
#line line 
#line line source-string-number 
這裡的 line 和 source-string-number 都是 constant integer 的表示式,當處理到這個指
令 的 時 候 ( 包 括 下 一 個 新 行 ) , GLSL 的 實 現 將 認 為 當 前 處 理 的 是 源 代 碼 字 串 號為
source-string-number,行號為 line。以後的原始碼字串號將一直為 source-string-number,
直到下一個#line 指令指定新的行號和新的原始碼字串號。 
3.9  前處理器表示式
前處理器表示式可以有表 3.2 裡的運算子: 
表 3.2.  前處理器表示式
運算子 描述
+ - ~ ! defined 一元運算子 
* / % 乘除,求摸 
+ - 加和減 
<< >> 移位 
< > <= >= 比較 
== != 相等測試 
& ^ | 位操作 
&& || 邏輯運算 
優先順序和行為,和標準 C 語言的前處理器一樣 
對前處理器表示式有一點必須記住,它們是在執行編譯器(GLSL 的編譯器)的處理器上執行的,
而不是執行 shader 的圖形處理器。執行編譯器的處理器支援的資料精度都有效。and hence will 
likely be different from the precision available when executing expressions in the core 
language. 在語言的核心,字串(string)型別是不支援的,#,##運算子不支援,sizeof 前處理器指令也
不支援。 
3.10 Error Handling 錯誤處理
編譯器可以接收一些不正常的程式,因為我們不可能讓編譯器檢測到所有的錯誤。例如,非常
精確的檢測一個變數在使用前是不是被初始化是不現實的。類似病態的程式在不同的平臺上執
行起來可能不同。所以,OpenGL Shading Language 的規範只保證正確的程式的可移植性。 
我們鼓勵 GLSL 的編譯器能檢測病態的程式,並輸出診斷資訊,但是這樣的要求不是必須的。編
譯器要求能夠處理詞法,語法,語義上的錯誤,並返回錯誤資訊。Shader 一旦產生這些錯誤,
就不能往下編譯了。用來獲得編譯器診斷資訊的 OpenGL 函式在第 7.5 節討論。 
3.11 Summary 總結
OpenGL Shading Language 是一種針對 OpenGL 環境設計的高階過程語言。這種語言允許程式使
用可程式設計的、並行的圖形硬體。它讓一個熟悉 C/C++的程式設計師能簡潔方便的描述圖形著色演算法。
OpenGL Shading Language 支援標量(scalar),向量(vector),矩陣,結構體,和陣列。取樣
器型別被用來訪問紋理。資料型別限定符用來定義 shader 的輸入和輸出,用來初始化的構造器,
型別轉化以及類似 C/C++的流程控制。 
3.12 更進一步的資料
OpenGL Shading Language在文件《The OpenGL Shading Language, Version 1.051》Kessenich, 
Baldwin, and Rost (2003).裡定義。(注:可以從www.OpenGL.org下載) 
OpenGL Shading Language 的語法全部包括在附錄 A 裡。這兩個文件可以用來當作語言本身的
參考。其他的教程,幻燈片,以及白皮書可以到 3DLabs 的網站去下載 
OpenGL Shading Language 的功能是用擴充套件的形式提供和支援的。閱讀這些擴充套件的規範和 OpenGL
本身的規範有助於更深刻的瞭解支援 OpenGL Shading Language 的系統。OpenGL 參考的最後一
章對了解 OpenGL 也很有幫助。 
C語言的標準參考手冊是《The C Programming Language》Brian Kernighan and Dennis Ritchie 
(1988),由C語言的設計者編寫的。同樣的,C++的參考手冊是C++之父Bjarne Stroustrup 所著
的《The C++ Programming Language》 Bjarne Stroustrup (2000)。還有其他許多著名的書,
可以當作C/C++的參考。 
[1] 3Dlabs developer Web site. http://www.3dlabs.com/support/developer[2] Kernighan, Brian, and Dennis Ritchie, The C Programming Language, Second Edition, 
Prentice Hall, Englewood Cliffs, New Jersey, 1988. 
[3] Kessenich, John, Dave Baldwin, and Randi Rost, The OpenGL Shading Language, Version 
1.051, 3Dlabs, February 2003. http://www.3dlabs.com/support/developer/ogl2
[4] OpenGL Architecture Review Board, ARB_vertex_shader Extension Specification, 
OpenGL Extension Registry. http://oss.sgi.com/projects/ogl-sample/registry
[5] OpenGL Architecture Review Board, ARB_fragment_shader Extension Specification, 
OpenGL Extension Registry. http://oss.sgi.com/projects/ogl-sample/registry
[6] OpenGL Architecture Review Board, ARB_shader_objects Extension Specification, 
OpenGL Extension Registry. http://oss.sgi.com/projects/ogl-sample/registry
[7] Segal, Mark, and Kurt Akeley, The OpenGL Graphics System: A Specification (Version 
1.5), Editor (v1.1): Chris Frazier, Editor (v1.2–1.5): Jon Leech, July 2003. 
http://opengl.org
[8] Stroustrup, Bjarne, The C++ Programming Language (Special 3rd Edition), 
Addison-Wesley, Reading, Massachusetts, 2000.