學習OpenGL ES for Android(二十四)— 幾何着色器


在本章之前還有兩章:高級數據高級GLSL,都是比較理論的知識,有興趣的話可以自己學習。

簡介

幾何着色器(Geometry Shader)是一個可選功能,他介於在頂點和片段着色器之間,接收一組頂點數據,可以對數據進行處理,而且可以根據數據生成不止一個圖形,假如你想繪製四個頂點,按照以前的方式,需要for循環四次,每次都頂點數據進行處理,最後傳入頂點着色器中。而集合着色器就做了這樣一件事。在移動平臺上,幾何着色器需要OpenGL ES 3.2版本(android 7之上),同樣的我們可以先參考文檔或其他相關資料學習3.0新特性。

3.0變化

這裏簡單學習一下3.0和2.0比較重要的變化

  1. attribute和varying,取而代之的是 in和out

  2. 文件需要添加#version 300 es (如果是3.2則是320,不加則默認2.0)

  3. 還有紋理 texture2D和texture3D統統改爲 texture

  4. 內置函數gl_FragColor和gl_FragData刪除,如果片段着色器要輸出用out聲明字段輸出。不過保留了gl_Position

  5. 還有的是layout的作用:可以直接指定位置

    // opengl 2.0
    uniform  float intensity;
    // 代碼 賦值
    GLES20.glUniform1f(GLES20.glGetAttribLocation(program, "intensity"), 1f)
    
    //opengl 3.0
    layout (location = 1) uniform  float intensity;
    //直接寫上對應的layout的值就可以賦值
    GLES30.glUniform1f(1,1f)
    

幾何着色器

繪製點

先看一個幾何着色器的例子

#version 320 es
layout (points) in; // 輸入
layout (points, max_vertices = 4) out; //輸出

in VS_OUT {
    vec3 color;
} gs_in[];

out vec3 fColor;
void build_point(vec4 position);

void main() {
    build_point(gl_in[0].gl_Position);
}

void build_point(vec4 position){
    fColor = gs_in[0].color;
    gl_Position = position + vec4(-0.5, 0.5, 0.0, 0.0);// 1:左上角
    gl_PointSize = 20.0;
    EmitVertex();
    gl_Position = position + vec4(0.5, 0.5, 0.0, 0.0);// 2:右上角
    EmitVertex();
    gl_Position = position + vec4(-0.5, -0.5, 0.0, 0.0);// 3:左下角
    gl_PointSize = 10.0;
    EmitVertex();
    gl_Position = position + vec4(0.5, -0.5, 0.0, 0.0);// 4:右下角
    fColor = vec3(1.0, 1.0, 1.0);
    EmitVertex();
    EndPrimitive();
}

這個幾何着色器的作用是:輸入一個頂點的數據,輸出四個頂點顯示在屏幕上;前兩個點大小爲20,後兩個點大小爲10;前三個點顏色爲紅色,最後一個是白色。

在幾何着色器的頂部,我們需要聲明從頂點着色器輸入的類型。這需要在in關鍵字前聲明一個佈局修飾符(Layout Qualifier)。這個輸入佈局修飾符可以從頂點着色器接收下列任何一個值:

  • points:繪製GL_POINTS(1)
  • lines:繪製GL_LINES或GL_LINE_STRIP時(2)
  • lines_adjacency:GL_LINES_ADJACENCY或GL_LINE_STRIP_ADJACENCY(4)(3.0新增類型)
  • triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN(3)
  • triangles_adjacency:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY(6)(3.0新增類型)

以上是能提供給glDrawArrays渲染函數的幾乎所有圖形了。如果我們想要將頂點繪製爲GL_GL_POINTS,我們就要將輸入修飾符設置爲points。括號內的數字表示的是一個圖形所包含的最小頂點數。

接下來,我們還需要指定幾何着色器輸出的類型,這需要在out關鍵字前面加一個佈局修飾符。和輸入佈局修飾符一樣,輸出佈局修飾符也可以接受幾個值:

  • points
  • line_strip
  • triangle_strip

有了這3個輸出修飾符,我們就可以使用輸入數據創建幾乎任意的形狀了。要生成一個點的話,我們將輸出定義爲points,並輸出max_vertices個頂點,如果你生成的頂點數大於max_vertices則不會顯示。

下面是接口塊(參考高級GLSL)VS_OUT的定義,用來接收頂點着色器中傳來的數據(當然也可以定義變量來傳遞,但是用接口塊更方便)。

接下來是使用傳入的頂點數據,從3.0版本開始GLSL提供了一個內建(Built-in)變量,它的結構大概如下(參考官方文檔

 in gl_PerVertex {
	highp vec4 gl_Position;
	highp float gl_PointSize;
} gl_in[];

可以通過gl_in數組來取我們傳入的頂點數據,例如傳入兩個頂點,根據索引取出數據即可(gl_in[0]和gl_in[1])。

EmitVertex方法,指當前一個頂點已經設置完成,包括頂點位置,大小,顏色等等。
EndPrimitive方法,指當前所有頂點這種完成,開始進行繪製。
頂點着色器和片段着色器的代碼比較簡單,如下

// 頂點着色器用來傳遞頂點和顏色數據
#version 320 es
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out VS_OUT {
    vec3 color;
} vs_out;

void main(){
    vs_out.color = aColor;
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
// 片段着色器
#version 320 es
precision mediump float;

out vec4 FragColor;

in vec3 fColor;

void main(){
    FragColor = vec4(fColor, 1.0);
}

我們傳入一個頂點屏幕中心(0,0),顏色傳入紅色,即可得到如下效果圖
繪製點

繪製線

我們可以通過對點處理生成線。這裏我們傳入四個頂點,

points = new float[]{
	-0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // 左上
	0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
	0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 右下
	-0.5f, -0.5f, 1.0f, 1.0f, 0.0f  // 左下
};

然後修改幾何着色器的代碼,每個頂點生成兩個點,左頂點x方向減0.1,右頂點x方向加0.1,把輸出的類型改爲line_strip,最大值爲0,代碼如下

#version 320 es
layout (points) in;
layout (line_strip, max_vertices = 2) out;

in VS_OUT {
    vec3 color;
} gs_in[];

out vec3 fColor;
void build_line(vec4 position);

void main() {
    build_line(gl_in[0].gl_Position);
}

void build_line(vec4 position){
    fColor = gs_in[0].color;
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); // 左頂點
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0); // 右頂點
    EmitVertex();
    EndPrimitive();
}

可繪製出四條線,分別在屏幕的四個角,效果如下
繪製線

繪製房子

一個小房子的樣子如下圖,
房子
因爲幾何着色器的三角形輸出只有triangle_strip(因爲它更節省節點),我們根據傳入的頂點分別生成從1到5五個頂點即可繪製成小房子的樣式,輸入的頂點座標和繪製線的相同,我們只需要修改幾何着色器的代碼,輸出類型改爲triangle_strip,數量爲5。修改後的代碼如下

#version 320 es
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;

in VS_OUT {
    vec3 color;
} gs_in[];

out vec3 fColor;
void build_house(vec4 position);

void main() {
    build_house(gl_in[0].gl_Position);
}

void build_house(vec4 position){
    fColor = gs_in[0].color;
    gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);// 1:bottom-left
    EmitVertex();
    gl_Position = position + vec4(0.2, -0.2, 0.0, 0.0);// 2:bottom-right
    EmitVertex();
    gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0);// 3:top-left
    EmitVertex();
    gl_Position = position + vec4(0.2, 0.2, 0.0, 0.0);// 4:top-right
    EmitVertex();
    gl_Position = position + vec4(0.0, 0.4, 0.0, 0.0);// 5:top
    fColor = vec3(1.0, 1.0, 1.0);
    EmitVertex();
    EndPrimitive();
}

效果圖如下
房子

爆破物體

物體的爆炸效果,在有些遊戲中比較常見,比如當炮彈擊中物體時,物體會破碎掉。這樣一種效果用幾何着色器是可以模擬的(當然效果比較粗糙),我們需要計算繪製的三角形的法向量,然後沿着法向量移動一小段距離,然後根據時間不斷的移動或恢復。
計算法向量的代碼

vec3 GetNormal(){
    vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);
    vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
    return normalize(cross(a, b));
}

計算爆炸效果的代碼

vec4 explode(vec4 position, vec3 normal){
    float magnitude = 2.0;
    vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude;
    return position + vec4(direction, 0.0);
}

最後是整個幾何着色器的代碼,

#version 320 es
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

in VS_OUT {
    vec2 texCoords;
} gs_in[];

out vec2 TexCoords;

uniform float time;

vec4 explode(vec4 position, vec3 normal);
vec3 GetNormal();

vec4 explode(vec4 position, vec3 normal){
……
}

vec3 GetNormal(){
……
}

void main() {
    vec3 normal = GetNormal();

    gl_Position = explode(gl_in[0].gl_Position, normal);
    TexCoords = gs_in[0].texCoords;
    EmitVertex();
    gl_Position = explode(gl_in[1].gl_Position, normal);
    TexCoords = gs_in[1].texCoords;
    EmitVertex();
    gl_Position = explode(gl_in[2].gl_Position, normal);
    TexCoords = gs_in[2].texCoords;
    EmitVertex();
    EndPrimitive();
}

找出我們之前的模型加載中加載納米裝的代碼,傳入頂點和紋理,運行代碼即看到如下的效果,因爲圖片被壓縮過效果不太好,可以下載源碼來查看。
爆炸效果

法向量可視化

在學習光照效果時,處理法向量時可能出現錯誤,但是因爲glsl無法調試,無法找到問題所在。這裏我們可以利用幾何着色器把法向量可視化,進行調試。
法向量可視化需要先繪製物體,然後繪製法向量,還是以納米裝爲例,繪製納米裝不再贅述,繪製法向量則只需要傳入頂點和法向量即可,然後在幾何着色器中進行處理,根據傳入的頂點座標和經過處理的法向量繪製一條線(每個三角形三條線),代碼如下

#version 320 es
layout (triangles) in;
layout (line_strip, max_vertices = 6) out;

in VS_OUT {
    vec3 normal;
} gs_in[];

const float MAGNITUDE = 0.2;
void GenerateLine(int index);

void GenerateLine(int index){
    gl_Position = gl_in[index].gl_Position;
    EmitVertex();
    gl_Position = gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE;
    EmitVertex();
    EndPrimitive();
}

void main(){
    GenerateLine(0); // 第一個頂點的法向量
    GenerateLine(1); // 第二個頂點的法向量
    GenerateLine(2); // 第三個頂點的法向量
}

最後的效果圖如下
法向量可視化
這樣就能幫助我們來判斷模型的法向量是否正確了,同樣可以用來實現其他功能,例如毛髮效果。

本章對應的文檔地址,可以參考進行理論學習。
本章源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章