1. 程式人生 > >Linux OpenGL 實踐篇-13-geometryshader

Linux OpenGL 實踐篇-13-geometryshader

shade 大致 簡單的 就是 過程 In 線段 自己的 表示圖

幾何著色器

  幾何著色器是位於圖元裝配和片元著色器之前的一個著色器階段,是一個可選階段。它的輸入是一個圖元的完整的頂點信息,通常來自於頂點著色器,但如果細分計算著色器啟用的話,那輸入則是細分計算著色器的輸出;相對應的幾何著色器的輸出也是完整的圖元信息。所以簡單的理解幾何著色器就是一個我們可以對圖元信息再次修改的階段。

這個修改可以體現在兩個方面,一個是圖元的數量,一個是圖元的類型。即我們可以輸入一個三角形,然後輸出兩個甚至更多的三角形,當然也可以不輸出三角形;而圖元類型改變則可理解為輸入如果是三角形,輸入可變為點或線的其它圖元類型。

什麽都不做

  首先我們先看一個最簡單的例子,這個幾何著色器什麽都不做,即不輸出圖元。當然,這樣的著色器不具備實用價值。

#version 330 core

layout(points) in;
layout(points, max_vertices=1) out;                                          

void main()
{

}

  這個例子很簡單,main函數是主過程,我們留空,表示什麽都不做。接下來,我們聚焦幾何著色器的關鍵點。第一就是“layout(points) in”這行代碼,表示的是幾何著色器的輸入圖元類型,即幾何著色器逐圖元執行的(頂點著色器逐頂點,片元著色器逐片元);這個圖元類型要與程序繪制時指定的圖元類型兼容,如points對應GL_POINTS,line對應GL_LINES,triangles對應GL_TRIANGLES;第二行聲明幾何著色器輸出圖元類型是點,輸出的最大頂點數是1。註意的是在幾何著色器中,輸出的圖元類型只能是點、多線段條帶和三角形條帶,不能輸出獨立的線段或三角形,也不能循環線或三角形扇面。這是因為條帶可以視為一個獨立圖元類型的的一個超集,即一個獨立的三角形或線段就是一個圖元的條帶而已。我們如果繪制一個三角形就立即結束條帶,那就相當於在繪制一個獨立的三角形。

直接往後傳遞

  下面是把輸入數據原封不動的往下一個階段傳遞。

#version 330 core

layout(points) in;
layout(points, max_vertices=2) out;

out vec4 fColor;

void main()
{
        int n;

        for(n =0; n < gl_in.length();n++)
        {
            gl_Position = gl_in[n].gl_Position;
            EmitVertex();
        }

        EndPrimitive();
}

gl_PrimitiveIDIn

  gl_PrimiiveIDIn是一個幾何著色器階段的glsl的內置變量,可以理解為圖元的唯一標識符。它是一個整形變量,從0開始,與它對應的是片元著色器的輸入gl_PrimitiveID。如果片元著色的gl_PrimitiveID是有效的,並且幾何著色器也是有效的,則幾何著色器必須對gl_PrimitiveID進行賦值。下面是一個只渲染序號為奇數的圖元幾何著色器例子。

#version 330 core

layout(points) in;
layout(points, max_vertices=2) out;

void main()
{
    if((gl_PrimitiveIDIn & 1) == 1)
    {
        int n;

        for(n =0; n < gl_in.length();n++)
        {
            gl_Position = gl_in[n].gl_Position;
            EmitVertex();
        }

        EndPrimitive();
    }

}

gl_layer

  gl_Layer是幾何著色器的一個變量,用於實現分層渲染。這個分層渲染通常是指渲染到幀緩存對象,而幀緩存的附件通常是一個二維數組紋理或者cube map,所以這個層可理解為二維數組紋理的一個片或者cube map的一個面。分層渲染即在幾何著色器中實現對每一層分別進行不同的渲染。下面我們看一個分層渲染的例子:

#version 410 core                                                      

layout(triangles) in;
layout(triangle_strip, max_vertices=128) out;

in VS_GS_VERTEX
{
        vec4 color;
}vertex_in[];

out GS_FS_VERTEX
{
        vec4 color;
}vertex_out;

uniform mat4 proj;
uniform int output_slices;


void main()
{
        for(int j =0; j < output_slices;++j)
        {
                gl_Layer = j;
                for(int i = 0; i < gl_in.length(); ++i)
                {
                        gl_Position = proj * gl_in[i].gl_Position;
                        vertex_out.color = vertex_in[i].color;
                        EmitVertex();
                }
                EndPrimitive();
        }
}

  在這個幾何著色器中,我們通過程序設置output_slices的值來控制層的數量,而這個層的數量是與二維數組紋理的數量或者cube map的面的數量一致。這個著色器中最重要的一步就是寫入gl_Layer,表示我們輸出圖元的層。在這裏我們在所有的層中渲染了同樣的信息,讀者可以根據自己的意願進行設計。所以在這個著色器中我們是對幾何體進行了擴充了,擴充的倍數是output_slices。

  而這個用法最典型的一個應用就是制作一個點光源的陰影紋理。我們知道點光源會向所有的方向發射光,所以我們使用陰影貼圖的方式來產生陰影的話,我們就需要在6個方向上都生成一張陰影貼圖,如果為此我們渲染場景6次,那對性能的影響會比較大,所以可以通過分層渲染的技巧來實現這6張陰影貼圖的繪制。大致的一個流程就是:

1.構建一個cube map;

2.構建幀緩存並把這個cube map關聯到幀緩存的深度緩存附件上;

3.根據點光源的位置信息構建6個觀察矩陣並傳入幾何著色器中;

4.然後分層渲染6次,每一層使用對應的觀察矩陣即可;

其中cube map 6個面的值分別是:

GL_TEXTURE_CUBE_MAP_POSITIVE_X   0

GL_TEXTURE_CUBE_MAP_NEGATIVE_X   1

GL_TEXTURE_CUBE_MAP_POSITIVE_Y  2

GL_TEXTURE_CUBE_MAP_NEGATIVE_Y  3

GL_TEXTURE_CUBE_MAP_POSITIVE_Z  4

GL_TEXTURE_CUBE_MAP_NEGATIVE_Z  5

具體的流程可參考:https://learnopengl-cn.github.io/05%20Advanced%20Lighting/03%20Shadows/02%20Point%20Shadows/

glViewportIndex

  glViewportIndex是幾何著色器中的另一個內置變量,可用於實現多視口渲染。通常在OpenGL中設置視口調用的函數是glViewport,使用它設置的是當前渲染的視口範圍。所以我們有一種實現多視口的方法:使用glViewport設置視口,渲染場景,再次調用glViewport設置視口,渲染場景……。在幾何著色器出現以前,我們實現多視口的方法通常就是它,但在著色器之後我們可以使用另一種方法來實現,就是glViewportIndex。

在介紹glViewportIndex之前,我們要介紹3個設置視口參數的函數:

glViewportIndexedf(GLuint index,GLfloat x,GLfloat y, GLfloat w,GLfloat h);

glViewportIndexedfv(GLuint index, GLfloat* v);

glDepthRangeIndexedf(GLuint index, GLclampd n, GLclampd f);

  其中(x,y)表示是視口的左上角(視口是矩形),w,h表示視口的寬和高。index是視口的索引,因為我們是多視口渲染,需要index來表示設置的是哪個視口。glViewportIndexedfv是向量版,即視口的參數存儲在一個向量中;glDepthRangeIndexedf設置視口的深度範圍,n和f表示視口的近平面和遠平面。

  通過幾何著色器實現多視口渲染兩種方式,一種是通過幾何著色器擴充幾何體,即一個物體可擴充為兩個物體;另一種是幾何著色器的實例化功能,設置請求invocations為指定的數量,如3,然後再在對應的視口中完成幾何體的渲染。

下面我們通過例子介紹第二種方式的實現,第一種方式可類推。

幾何著色器:

#version 410 core

layout(triangles,invocations = 4) in ;
layout(triangle_strip, max_vertices=3) out;

uniform mat4 model_matrix[4];
uniform mat4 proj;                                                                            

out vec4 gs_color;

const vec4 colors[4] = vec4[4]
(
        vec4(1.0,0.7,0.3,1.0),
        vec4(1.0,0.2,0.3,1.0),
        vec4(0.1,0.6,1.0,1.0),
        vec4(0.3,0.7,0.5,1.0)
);

void main()
{
        for(int i=0;i<gl_in.length();++i)
        {
                gl_ViewportIndex = gl_InvocationID;
                gs_color = colors[gl_InvocationID];

                //gs_normal = (model_matrix[gl_InvocationID] * vec4(vs_normal[i],0.0)).xyz;
                gl_Position = proj * (model_matrix[gl_InvocationID] * gl_in[i].gl_Position);
                EmitVertex();
        }
}

  在這個幾何著色器中,重點是invocations和gl_ViewportIndex的設置。invocations表示圖元處理請求的數量,簡單的理解就是對這個圖元處理幾次,在這裏我們處理4次,其中一個實例化相關的內置變量是gl_InvocationID,表示幾何著色器請求(Invocation)分配的調用值(Invocation),也可以簡單的理解為第幾次調用,通過gl_Invocation來給gl_ViewportIndex賦值表示渲染到哪個視口,而model_matrix是視口渲染所用矩陣,所以這個幾何著色器的流程就是先設置視口索引,然後使用該視口的矩陣變換頂點,輸出的圖元。結果就是在事先設置的視口中按給定的矩陣渲染場景,即實現了多視口渲染。

其中視口參數設置:

void Reshape(int width, int height)
{
        const float wot = float(width) * 0.5f;
        const float hot = float(height) * 0.5f;

        glViewportIndexedf(0,0.0f,0.0f,wot,hot);
        glViewportIndexedf(1,wot,0.0f,wot,hot);
        glViewportIndexedf(2,0.0f,hot,wot,hot);
        glViewportIndexedf(3,wot,hot,wot,hot);
}                                                

  頂點著色器和片元著色器:

#version 410 core                       

layout(location=0) in vec3 iPos;

//uniform mat4 model;
//uniform mat4 view;
//uniform mat4 proj;

void main()
{
        gl_Position = vec4(iPos,1.0);
}

  片元著色器:

#version 410 core         

in vec4 gs_color;
out vec4 color;

void  main()
{
        color = gs_color;
}

Linux OpenGL 實踐篇-13-geometryshader