1. 程式人生 > >【GLSL教程】(三)在OpenGL中向shader傳遞資訊

【GLSL教程】(三)在OpenGL中向shader傳遞資訊

引言

一個OpenGL程式可以用多種方式和shader通訊。注意這種通訊是單向的,因為shader的輸出只能是渲染到某些目標,比如顏色和深度快取。

OpenGL的部分狀態可以被shader訪問,因此程式改變OpenGL某些狀態就可以與shader進行通訊了。例如一個程式想把光的顏色傳給shader,可以直接呼叫OpenGL介面,就像使用固定功能流水線時做的那樣。

不過,使用OpenGL狀態並不是設定shader中使用資料的直觀方式。比如一個shader需要一個表示時間變化的變數來計算動畫,在OpenGL狀態中就沒有現成的變數可用。當然,你可以使用沒有用到的“鏡面光截止角度(cutoffangle)”這樣一個變量表示時間,但顯然讓人難以接受。

幸運的是,GLSL允許使用者自定義變數,實現OpenGL應用程式與shader通訊。有了這個功能,你就可以命名一個叫做timeElapsed的變量表示經過的時間。

上文的討論涉及到了GLSL提供的兩種型別修飾符(更多的型別將在後面提到):

·一致變數(Uniform)

·屬性(Attribute)

在shader中定義的變數如果用這兩種型別修飾符,表示對shader來說,它們是隻讀的。下面將詳細講述怎樣使用這些型別的變數。

還有一種將變數送給shader的方法:使用紋理。一個紋理不止可以表示一張圖片,它還可以表示一個數組。事實上,你完全可以決定如何在shader中解釋紋理資料,即使它真是一幅圖片。

 

資料型別和變數

下面是GLSL中的基本資料型別:

·float

·bool

·int

浮點型別與C中類似,布林型別可以為true或false。這些基本型別可以組成2、3或4維向量,如下所示:

·vec{2,3,4} a vector of 2,3,or 4 floats

·bvec{2,3,4} bool vector

·ivec{2,3,4} vector of integers

GLSL還包括2×2、3×3或4×4型矩陣,因為這些矩陣型別在圖形處理中很常用:

·mat2

·mat3

·mat4

此外,還有一組用來實現紋理訪問的特殊型別,它們被稱為取樣器(sampler),在讀取紋理值(也稱為紋素texel)時用到。下面就是紋理取樣用到的資料型別:

·sampler1D – for 1D textures

·sampler2D – for 2D textures

·sampler3D – for 3D textures

·samplerCube – for cube map textures

·sampler1DShadow – for shadow maps

·sampler2DShadow – for shadow maps

在GLSL中,可以像C一樣宣告和訪問陣列,但是不能在宣告時初始化陣列。GLSL還可以定義結構體:

  1. struct dirlight
  2. {
  3. vec3 direction;
  4. vec3 color;
  5. };
變數

宣告一個基本型別變數的方法與C類似,你還可以在宣告它的同時進行初始化。

  1. float a,b; // two vector (yes, the comments are like in C)
  2. int c = 2; // c is initialized with 2
  3. bool d = true; // d is true
宣告其它型別變數也是按照這種方法,但是初始化與C語言有區別。GLSL非常依賴建構函式實現初始化和型別轉換。

  1. float b = 2; // incorrect, there is no automatic type casting
  2. float e = ( float) 2; // incorrect, requires constructors for type casting
  3. int a = 2;
  4. float c = float(a); // correct. c is 2.0
  5. vec3 f; // declaring f as a vec3
  6. vec3 g = vec3( 1.0, 2.0, 3.0); // declaring and initializing g
在GLSL中使用一些變數初始化其它變數是非常靈活的。你只需要給出需要的資料成員即可。請看下面的例子:

  1. vec2 a = vec2( 1.0, 2.0);
  2. vec2 b = vec2( 3.0, 4.0);
  3. vec4 c = vec4(a,b) // c = vec4(1.0,2.0,3.0,4.0);
  4. vec2 g = vec2( 1.0, 2.0);
  5. float h = 3.0;
  6. vec3 j = vec3(g,h);
矩陣的初始化也是類似方法,矩陣包含很多種建構函式,下面的例子給出了一些初始化矩陣的建構函式:

  1. mat4 m = mat4( 1.0) // initializing the diagonal of the matrix with 1.0
  2. vec2 a = vec2( 1.0, 2.0);
  3. vec2 b = vec2( 3.0, 4.0);
  4. mat2 n = mat2(a,b); // matrices are assigned in column major order
  5. mat2 k = mat2( 1.0, 0.0, 1.0, 0.0); // all elements are specified

下面的例子給出了初始化結構體的方法:

  1. struct dirlight // type definition
  2. {
  3. vec3 direction;
  4. vec3 color;
  5. };
  6. dirlight d1;
  7. dirlight d2 = dirlight(vec3( 1.0, 1.0, 0.0),vec3( 0.8, 0.8, 0.4));
在GLSL中還有一些實用的選擇子(selector),可以簡化我們的操作並讓程式碼更簡潔。訪問一個向量可以使用如下的方法:

  1. vec4 a = vec4( 1.0, 2.0, 3.0, 4.0);
  2. float posX = a.x;
  3. float posY = a[ 1];
  4. vec2 posXY = a.xy;
  5. float depth = a.w
在上面的程式碼片段中,可以使用x、y、z、w來訪問向量成員。如果是顏色的話可以使用r、g、b、a,如果是紋理座標的話可以使用s、t、p、q。注意表示紋理座標通常是使用s、t、r、q,但r已經表示顏色中的紅色(red)了,所以紋理座標中需要使用p來代替。

矩陣的選擇子可以使用一個或兩個引數,比如m[0]或者m[2][3]。第一種情況選擇了第一列,第二種情況選擇了一個數據成員。

對於結構體來說,可以像在C語言中一樣訪問其成員。所以訪問前面定義的結構體,可以使用如下的程式碼:

d1.direction = vec3(1.0,1.0,1.0);
變數修飾符

修飾符給出了變數的特殊含義,GLSL中有如下修飾符:

·const – 宣告一個編譯期常量。

·attribute– 隨不同頂點變化的全域性變數,由OpenGL應用程式傳給頂點shader。這個修飾符只能用在頂點shader中,在shader中它是一個只讀變數。

·uniform– 隨不同圖元變化的全域性變數(即不能在glBegin/glEnd中設定),由OpenGL應用程式傳給shader。這個修飾符能用在頂點和片斷shader中,在shader中它是一個只讀變數。

·varying –用於頂點shader和片斷shader間傳遞的插值資料,在頂點shader中可寫,在片斷shader中只讀。


一致變數(Uniform Variables)

不同於頂點屬性在每個頂點有其自己的值,一個一致變數在一個圖元的繪製過程中是不會改變的,所以其值不能在glBegin/glEnd中設定。一致變數適合描述在一個圖元中、一幀中甚至一個場景中都不變的值。一致變數在頂點shader和片斷shader中都是隻讀的。

首先你需要獲得變數在記憶體中的位置,這個資訊只有在連線程式之後才可獲得。注意,對某些驅動程式,在獲得儲存位置前還必須使用程式(呼叫glUseProgram)。

獲取一個一致變數的儲存位置只需要給出其在shader中定義的變數名即可:

  1. GLint glGetUniformLocation(GLuint program, const char *name);
  2. 引數:
  3. ·program – the hanuler to the program
  4. ·name – the name of the variable
返回值就是變數位置,可以用此資訊設定變數的值。根據變數的資料型別不同,有一系列函式可以用來設定一致變數。用來設定浮點值的一組函式如下:

  1. void glUniform1f(GLint location, GLfloat v0);
  2. void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
  3. void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
  4. void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
  5. 或者
  6. GLint glUniform{ 1, 2, 3, 4}fv(GLint location, GLsizei count, GLfloat *v);
  7. 引數:
  8. ·location – the previously queried location
  9. ·v0,v1,v2,v3 – float values
  10. ·count – the number of elements in the array
  11. ·v – an array of floats

對integer型別也有一組類似的函式,不過要用i替換函式中的f。對bool型別沒有專門的函式,但可以使用整數的0和1來表示真假。一旦你使用了一致變數陣列,那麼就必須使用向量版本的函式。

對sampler變數,使用函式glUniform1i和glUniform1iv。

矩陣也是一種GLSL的資料型別,所以也有一組針對矩陣的函式:

  1. GLint glUniformMatrix{ 2, 3, 4}fv(GLint location, GLsizei count, GLboolean transpose, GLfloat *v);
  2. 引數:
  3. location – the previously queried location.
  4. count – the number of matrices. 1 if a single matrix is being set, or n for an array of n matrices.
  5. transpose – wheter to transpose the matrix values. A value of 1 indicates that the matrix values are specified in row major order, zero is column major order
  6. v – an array of floats.

還有一點要注意的是:使用這些函式之後,變數的值將保持到程式再次連線之時。一旦進行重新連線,所有變數的值將被重置為0。

最後是一些示例程式碼。假設一個shader中使用瞭如下變數:

  1. uniform float specIntensity;
  2. uniform vec4 specColor;
  3. uniform float t[ 2];
  4. uniform vec4 colors[ 3];

在OpenGL程式中可以使用下面的程式碼設定這些變數:

  1. GLint loc1,loc2,loc3,loc4;
  2. float specIntensity = 0.98;
  3. float sc[ 4] = { 0.8, 0.8, 0.8, 1.0};
  4. float threshold[ 2] = { 0.5, 0.25};
  5. float colors[ 12] = { 0.4, 0.4, 0.8, 1.0,
  6. 0.2, 0.2, 0.4, 1.0,
  7. 0.1, 0.1, 0.1, 1.0};
  8. loc1 = glGetUniformLocation(p, "specIntensity");
  9. glUniform1f(loc1,specIntensity);
  10. loc2 = glGetUniformLocation(p, "specColor");
  11. glUniform4fv(loc2, 1,sc);
  12. loc3 = glGetUniformLocation(p, "t");
  13. glUniform1fv(loc3, 2,threshold);
  14. loc4 = glGetUniformLocation(p, "colors");
  15. glUniform4fv(loc4, 3,colors);
例子程式碼的下載地址:

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

注意設定一個數組(例子中的t)與設定四元向量(例子中的colors和specColor)的區別。中間的count引數指在shader中宣告的陣列元素數量,而不是在OpenGL程式中宣告的。所以雖然specColor包含4個值,但glUniform4fv函式中的引數是1,因為它只是一個向量。另一種設定specColor的方法:

  1. loc2 = glGetUniformLocation(p, "specColor");
  2. glUniform4f(loc2,sc[ 0],sc[ 1],sc[ 2],sc[ 3]);
GLSL中還可以獲取陣列中某個變數的地址。比如,可以獲得t[1]的地址。下面的程式碼片段展示了設定t陣列元素的另一種方法:

  1. loct0 = glGetUniformLocation(p, "t[0]");
  2. glUniform1f(loct0,threshold[ 0]);
  3. loct1 = glGetUniformLocation(p, "t[1]");
  4. glUniform1f(loct1,threshold[ 1]);

注意在glGetUniformLocation中使用方括號指示的變數。


屬性變數(Attribute Variables)

在前一節提到,一致變數只能針對一個圖元全體,就是說不能在glBegin和glEnd之間改變。

如果要針對每個頂點設定變數,那就需要屬性變量了。事實上屬性變數可以在任何時刻更新。在頂點shader中屬性變數是隻讀的。因為它包含的是頂點資料,所以在片斷shader中不能直接應用。

與一致變數相似,首先你需要獲得變數在記憶體中的位置,這個資訊只有在連線程式之後才可獲得。注意,對某些驅動程式,在獲得儲存位置前還必須使用程式。

  1. GLint glGetAttribLocation(GLuint program,char *name);
  2. 引數:
  3. program – the handle to the program.
  4. name – the name of the variable
上述函式呼叫的返回變數在儲存器中的地址。下面就可以為它指定一個值,類似一致變數,每種資料型別都有對應的函式。

  1. void glVertexAttrib1f(GLint location, GLfloat v0);
  2. void glVertexAttrib2f(GLint location, GLfloat v0, GLfloat v1);
  3. void glVertexAttrib3f(GLint location, GLfloat v0, GLfloat v1,GLfloat v2);
  4. void glVertexAttrib4f(GLint location, GLfloat v0, GLfloat v1,,GLfloat v2, GLfloat v3);
  5. 或者
  6. GLint glVertexAttrib{ 1, 2, 3, 4}fv(GLint location, GLfloat *v);
  7. 引數:
  8. location – the previously queried location.
  9. v0,v1,v2,v3 – float values.
  10. v – an array of floats.
對於integer型別,也有一組類似的函式。與一致變數不同,這裡向量版的函式並不支援對向量陣列的賦值,所以函式引數用向量或是分別指定的效果沒有太大區別,就好像OpenGL中glColor3f和glColor3fv的關係。

下面是一個簡單的例子,假定頂點shader中聲明瞭一個名為height的浮點屬性變數,在程式連線之後可以進行如下操作:

loc = glGetAttribLocation(p,"height");
在執行渲染的程式碼中間可以為shader中的變數賦值:

  1. glBegin(GL_TRIANGLE_STRIP);
  2. glVertexAttrib1f(loc, 2.0);
  3. glVertex2f( -1, 1);
  4. glVertexAttrib1f(loc, 2.0);
  5. glV