接下來開始介紹Geometry shader的語法.
在網路上看到的GS(Geometry shader)範例開頭大多會先看到這一行:
#version 120
代 表之後的Shader 程式是符合GLSL 1.2規範的程式.不過根據Spec以及我在Nvidia的平台上實際測試過,GS不用加這一行也是可以使用,原因大概是因為GLSL 1.0里面本來就有追加這個extension.接下來就是在GS的程式加上這一行以開啟Geometry Shader的功能:
#extension GL_EXT_geometry_shader4 : enable
當然這個原因是因為GS在OpenGL2.1里面仍然是屬於Extension的範圍,所以必須要開啟才能使用.
接下來便是開始正式寫GS的程式.
在GS裡面,和VS(vertex shader)與PS(fragment shader)一樣,都可以直接讀取Uniform的參數.
例如:
gl_ModelViewMatrix;
gl_ModelViewProjectionMatrix;
gl_ProjectionMatrix;
…..等等
對於Attribute變數而言,GS一樣可以以primitive為單位,存取Vertex的Attribute參數.
對 於Varying的變數.原本在只有VS跟PS的情況下,Varying變數用來在VS與PS之間傳遞參數使用,但是在中間多了GS之後,VS與PS的 Varying變數宣告方式沒有改變,但是在GS裡面,變成由VS接到Varying的變數,然後送出Varying變數到PS.所以在GS裡 面,Varying變數分成Varying in與Varying out兩種宣告方式,分別代表由VS接收的Varying in變數與送到PS的Varying out變數.
然後除了自己宣告的變數之外,在GS裡面也有內建的變數,有如自己所宣告的Varying in一樣,
如下:
* gl_PositionIn[#]
* gl_NormalIn[#]
* gl_TexCoordIn[ ][#]
* gl_FrontColorIn[#]
* gl_BackColorIn[#]
* gl_PointSizeIn[#]
* gl_LayerIn[#]
* gl_PrimitiveIDIn[#]
由 於GS是從VS接收到Primitive,所以上述的Array個數大小"#"便是代表單一Primitive的Vertex數目.在GS裡面,也有一個 內建的const int變數gl_VerticesIn來代表這個值.而針對這些變數,只能讀取,不能做寫入的動作.
例如:
Type gl_VerticesIn大小
* GL_POINTS 1
* GL_LINES 2
* GL_LINES_ADJACENCY_EXT 4
* GL_TRIANGLES 3
* GL_TRIANGLES_ADJACENCY_EXT 6
由於GS的用途在於接收VS處理過的Vertex,然後以primitive為單位對於vertex再經過一次處理,併重組輸出1個以上的primitive.所以GS的使用跟VS有點類似.
在VS裡面,可以根據gl_Vertex以及宣告的attribute變數,經過計算寫入到gl_Position.
所以在GS裡面,便是透過gl_PositionIn[]可以一次取複數個Vertice的資料與attribute,然後像VS一樣輸出Vertex的varying變數.只是在GS裡面輸出的是複數個Vertice的Varying out.
然後開始介紹GS裡面獨有的兩個內建function.
EmitVertex()
EmitPrimitive()
EmitVertex()這個function代表將之前所有Varying out變數(包含內建的與自己宣告的)依照現在的值作為一個Vertex的資料輸出.
EmitPrimitive()當然就是將之前所有呼叫EmitVertex()的Vertex組成一個Primitive.
所以在GS的程式,就是根據Varying in變數如gl_PositionIn與其他Vertex Attribute等,計算出
gl_Position與其他Varying out變數之後,呼叫EmitVertex().然後再多次EmitVertex()之後,呼叫
EmitPrimitive()組成Primitive.然後可以反覆多次根據同一組VS送進來的Primitive送出一組以上的Primitive.
另外,由於GS可以視作是針對VS再做進一步的處理.所以原本在VS裡面針對Vertex作的動作,如呼叫ftransform()作vertex的transform,可以視需求挪到GS再處理.其他在GS裡面的程式語法(syntax),則跟VS與PS相同.
下面是一個簡單的範例,應用到一張2D texture,一張1D Texture等,用來將點轉成線:
Vertex Shader部分:
#version 120
uniform sampler2D CTable2D;
uniform sampler1D CTable1D;
uniform float Step;
uniform float MaxHeight;
uniform float MaxStep;
varying float HeightIn;
void main ( void )
{
vec2 tex;
tex.s = gl_MultiTexCoord0.s;
tex.t = Step/MaxStep;
HeightIn = texture2D(CTable2D,tex).r / MaxHeight;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_Vertex;
}
在Vertex Shader裡,透過Step作為texture coordinate針對CTabl2D這個texture取得HeightIn,
作為Varying參數送到GS.然後在Vertex的座標部分,沒有作transform.
Geometry Shader部分:
#version 120
#extension GL_EXT_geometry_shader4 : enable
uniform sampler2D CTable2D;
uniform sampler1D CTable1D;
uniform float Step;
uniform float MaxHeight;
uniform float MaxStep;
varying in float HeightIn[];
varying out float Height;
void DrawLine()
{
int i;
vec4 xyzw;
for(i=0; i< gl_VerticesIn; i )
{
gl_Position = gl_ModelViewProjectionMatrix * gl_PositionIn[i];
gl_FrontColor = gl_FrontColorIn[i];
Height = 0;
EmitVertex();
xyzw.xyzw = gl_PositionIn[i].xyzw;
xyzw.z = HeightIn[i];
gl_Position = gl_ModelViewProjectionMatrix * xyzw;
gl_FrontColor = gl_FrontColorIn[i];
Height = HeightIn[i];
EmitVertex();
}
EndPrimitive();
}
void main(void)
{
DrawLine();
}
Geometry Shader的階段,在Shader外面的OpenGL程式是設定成輸入Primitive為GL_POINTS,輸出的Primitive為 GL_LINE_STRIPS.而這個Shader只是簡單的將輸出的點,變成一條線,線的一個端點是原本的點,另一個端點則是在Z軸加上 HeightIn的高度.
所以在Geometry Shader裡面,由於gl_PotitionIn在VS時沒有作transform,所以在GS作transform.第一個端點直接由 gl_PositionIn作Transform寫入到gl_Position,就執行EmitVertex()送出端點.
而第二個點就加上HeightIn的高度後作Transform再送出端點.如此便透過GS將點變成一條線.
另外由於線的顏色打算根據高度決定顏色與漸層色,所以在EmitVertex()之前,也要設定好Height的值,才能做完Rasteration的interpolation後送到Fragment Shader裡面.
Fragment Shader部分:
#version 120
uniform sampler2D CTable2D;
uniform sampler1D CTable1D;
uniform float Step;
uniform float MaxHeight;
uniform float MaxStep;
varying float Height;
void main()
{
vec4 texel = texture1D (CTable,Height);
gl_FragColor = texel;
}
而在Fragment Shader裡面,就根據Height的值,作為貼圖座標從1D的Color Table去Sample顏色出來.
文章很好,学习了