Linux OpenGL 实践篇-13-geometryshader

Posted xin_l12

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux OpenGL 实践篇-13-geometryshader相关的知识,希望对你有一定的参考价值。

几何着色器

  几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段。它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的输出;相对应的几何着色器的输出也是完整的图元信息。所以简单的理解几何着色器就是一个我们可以对图元信息再次修改的阶段。

这个修改可以体现在两个方面,一个是图元的数量,一个是图元的类型。即我们可以输入一个三角形,然后输出两个甚至更多的三角形,当然也可以不输出三角形;而图元类型改变则可理解为输入如果是三角形,输入可变为点或线的其它图元类型。

什么都不做

  首先我们先看一个最简单的例子,这个几何着色器什么都不做,即不输出图元。当然,这样的着色器不具备实用价值。

#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的主要内容,如果未能解决你的问题,请参考以下文章

Linux OpenGL 实践篇-2 创建一个窗口

Linux OpenGL 实践篇-11-shadow

Linux OpenGL 实践篇-9 模型

Linux OpenGL 实践篇-14-多实例渲染

Linux OpenGL 实践篇-6 光照

Linux OpenGL 实践篇-12-ProceduralTexturing