【GLSL教程】(四)shder的簡單示例

vampirem發表於2013-09-17

GLSL的Hello World

這一節中包含一個最基本的shader,它提供如下功能:頂點變換然後使用單一的顏色渲染圖元。

頂點shader

前面已經說過,頂點shader負責完成頂點變換。這裡將按照固定功能的方程完成頂點變換。

固定功能流水線中一個頂點通過模型檢視矩陣以及投影矩陣進行變換,使用如下公式:

  1. vTrans = projection * modelview *incomingVertex  
vTrans = projection * modelview *incomingVertex
首先GLSL需要訪問OpenGL狀態,獲得公式中的前兩個矩陣。前面講過,GLSL可以獲取某些OpenGL狀態資訊的,這兩個矩陣當然包括在內。可以通過預先定義的一致變數來獲取它們:

  1. uniform mat4 gl_ModelViewMatrix;  
  2. uniform mat4 gl_ProjectionMatrix;  
uniform mat4 gl_ModelViewMatrix;
uniform mat4 gl_ProjectionMatrix;
接下來需要得到輸入的頂點。通過預先定義的屬性變數,所有的頂點將可以一個個傳入頂點shader中。

  1. attribute vec4 gl_Vertex;  
attribute vec4 gl_Vertex;
為了輸出變換後的頂點,shader必須寫入預先定義的vec4型變數gl_Position中,注意這個變數沒有修飾符。

現在我們可以寫一個僅僅進行頂點變換的頂點shader了。注意所有其他功能都將喪失,比如沒有光照計算。頂點shader必須有一個main函式,如下面的程式碼所示:

  1. void main()  
  2. {  
  3.     gl_Position =gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;  
  4. }  
void main()
{
    gl_Position =gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
}
上面程式碼中變換每個頂點時,投影矩陣都將乘上模型檢視矩陣,這顯然非常浪費時間,因為這些矩陣不是隨每個頂點變化的。注意這些矩陣是一致變數

GLSL提供一些派生的矩陣,也就是說gl_ModelViewProjectionMatrix是上面兩個矩陣的乘積,所以頂點shader也可以寫成下面這樣:

  1. void main()  
  2. {  
  3.     gl_Position =gl_ModelViewProjectionMatrix * gl_Vertex;  
  4. }  
void main()
{
    gl_Position =gl_ModelViewProjectionMatrix * gl_Vertex;
}
上面的操作能夠獲得和固定功能流水線相同的結果嗎?理論上是如此,但實際上對頂點變換操作的順序可能會不同。頂點變換通常在顯示卡中是高度優化的任務,所以有一個利用了這種優化的特定函式用來處理這個任務。這個神奇的函式如下:

  1. vec4 ftransform(void);  
vec4 ftransform(void);
使用這個函式的另一個原因是float資料型別的精度限制。由於資料精度的限制,當使用不同的順序計算時,可能得到不同的結果,因此GLSL提供這個函式保證獲得最佳效能的同時,還能得到與固定功能流水線相同的結果。

這個函式按照與固定功能相同的步驟對輸入頂點進行變換,然後返回變換後的頂點。所以shader可以重新寫成如下形式:

  1. void main()  
  2. {  
  3.     gl_Position =ftransform();  
  4. }  
void main()
{
    gl_Position =ftransform();
}
片斷shader

片斷shader也有預先定義的變數gl_FragColor,可以向其中寫入片斷的顏色值。下面的程式碼就是一個片斷shader,將所有片斷繪製成淡藍色:

  1. void main()  
  2. {  
  3.     gl_FragColor =vec4(0.4,0.4,0.8,1.0);  
  4. }  
void main()
{
    gl_FragColor =vec4(0.4,0.4,0.8,1.0);
}
可以在此獲得本節例子的原始碼:

http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/glutglsl5_2.0.zip


顏色shader

GLSL可以讀取一些OpenGL狀態,在本節我們將學習如何訪問在OpenGL中設定的glColor變數。

GLSL有一個屬性變數記錄當前顏色,也提供易變變數從頂點shader向片斷shader傳遞顏色值。

  1. attribute vec4 gl_Color;  
  2. varying vec4 gl_FrontColor; // writable onthe vertex shader   
  3. varying vec4 gl_BackColor; // writable onthe vertex shader   
  4. varying vec4 gl_Color; // readable on thefragment shader  
attribute vec4 gl_Color;
varying vec4 gl_FrontColor; // writable onthe vertex shader
varying vec4 gl_BackColor; // writable onthe vertex shader
varying vec4 gl_Color; // readable on thefragment shader
變數使用思想如下:

1、OpenGL程式通過glColor傳送顏色資訊。

2、頂點shader通過屬性gl_Color接收顏色值。

3、頂點shader計算正面和反面的顏色,然後分別儲存在gl_FrontColor和gl_BackColor中。

4、片斷shader接收易變變數gl_Color中儲存的插值產生的顏色,由當前圖元的方向決定顏色是gl_FrontColor還是gl_BackColor插值產生的。

5、片斷shader根據易變變數gl_Color設定gl_FragColor。

前 面說過頂點shader和片斷shader中傳遞的易變變數要有相同的名字,但這裡是個例外,頂點shader中的gl_FrontColor和 gl_BackColor會根據圖元的方向,自動轉變為片斷shader中的gl_Color。還要注意屬性變數gl_Color和易變變數 gl_Color沒有衝突,因為前者只存在於頂點shader,後者只存在於片斷shader。

下面是頂點shader的例子,只計算了正面顏色:

  1. void main()  
  2. {  
  3.     gl_FrontColor =gl_Color;  
  4.     gl_Position =ftransform();  
  5. }  
void main()
{
    gl_FrontColor =gl_Color;
    gl_Position =ftransform();
}

片斷shader更加簡單:

  1. void main()  
  2. {  
  3.     gl_FragColor = gl_Color;  
  4. }  
void main()
{
    gl_FragColor = gl_Color;
}
基於GLEW的原始碼:

http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/colorglut_2.0.zip

 

扁平shader(Flatten Shader)

著色器程式設計讓我們可以探索一些新效果,本節的例子展示了用奇怪的方法操作頂點得到的效果。

首先我們要得到一個扁平的3D模型,只需要在應用模型檢視變換時將模型頂點的z座標設為0就行了。下面是頂點shader的程式碼:

  1. void main(void)  
  2. {  
  3.     vec4 v = vec4(gl_Vertex);  
  4.     v.z = 0.0;  
  5.   
  6.     gl_Position =gl_ModelViewProjectionMatrix * v;  
  7. }  
void main(void)
{
    vec4 v = vec4(gl_Vertex);
    v.z = 0.0;

    gl_Position =gl_ModelViewProjectionMatrix * v;
}
我們先將gl_Vertex變數複製到一個區域性變數v中。gl_Vertex是一個GLSL提供的屬性變數,所以在頂點shader中它是隻讀的。

片斷shader與“GLSL中的Hello World”一節相同,就只用設定一種顏色。

一個扁平的茶壺效果如下:


更進一步,我們需要在z座標上使用一個正弦函式。將z座標作為x座標的函式,這樣茶杯將呈現波浪的效果:

  1. void main(void)  
  2. {  
  3.     vec4 v =vec4(gl_Vertex);  
  4.     v.z = sin(5.0*v.x)*0.25;  
  5.     gl_Position =gl_ModelViewProjectionMatrix * v;  
  6. }  
void main(void)
{
    vec4 v =vec4(gl_Vertex);
    v.z = sin(5.0*v.x)*0.25;
    gl_Position =gl_ModelViewProjectionMatrix * v;
}
最後我們需要加入一些頂點動畫效果。為了達到這個目的我們需要增加一個變數記錄變化的時間,或者幀數。一個頂點shader是無法記錄不同頂點值的,更不用 說記錄不同的幀了。所以我們需要在OpenGL程式中定義這個變數,然後作為一致變數傳遞給shader。假設在OpenGL程式中有一個名為time的 幀計數器,在shader中有個同名的一致變數。

頂點shader的程式碼如下:

  1. uniform float time;  
  2.   
  3. void main(void)  
  4. {  
  5.     vec4 v =vec4(gl_Vertex);  
  6.     v.z = sin(5.0*v.x +time*0.01)*0.25;  
  7.     gl_Position =gl_ModelViewProjectionMatrix * v;  
  8. }  
uniform float time;

void main(void)
{
    vec4 v =vec4(gl_Vertex);
    v.z = sin(5.0*v.x +time*0.01)*0.25;
    gl_Position =gl_ModelViewProjectionMatrix * v;
}
在有關一致變數的小節講過,在OpenGL程式中需要兩個步驟:

·setup: 獲取一致變數的儲存位置

·render: 更新一致變數

設定(setup)步驟只有一條語句:

  1. loc =glGetUniformLocation(p,"time");  
loc =glGetUniformLocation(p,"time");
這裡p是程式的控制程式碼,time與頂點shader中定義的一致變數名稱相同。變數loc是Glint型別的,必須定義在下面的渲染(render)函式也可以訪問到的地方。渲染函式如下所示:

  1. void renderScene(void)  
  2. {  
  3.     glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);  
  4.     glLoadIdentity();  
  5.     gluLookAt(0.0,0.0,5.0,  
  6.           0.0,0.0,0.0,  
  7.           0.0f,1.0f,0.0f);  
  8.     glUniform1f(loc, time);  
  9.   
  10.     glutSolidTeapot(1);  
  11.     time+=0.01;  
  12.     glutSwapBuffers();  
  13. }  
void renderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(0.0,0.0,5.0,
          0.0,0.0,0.0,
          0.0f,1.0f,0.0f);
    glUniform1f(loc, time);

    glutSolidTeapot(1);
    time+=0.01;
    glutSwapBuffers();
}
函式中的變數time在程式一開始初始化,然後每幀都會進行自增運算。

本節的GLEW原始碼:

http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/flatten_2.0.zip

相關文章