什么是Geometry Shader
GS存在于vertext shader和固定功能vertex post-processing stage之间,它是可选的不是必要的。
GS的输入是单个primitive,输出可能是0个或多个primitive.
GS的作用
GS的主要作用就是从已有的primitive中生成新的primitive,它可以“无中生有”的生成新的顶点
OpenGL官网上提到两种用法:
Layered rendering: 对一个primitive,不改变rendertarget渲染出多个图片
Transform Feedback: 用来执行GPU的计算任务(pre-compute shader)
下图是个例子,给GS输入一个三角形,GS输出4个三角形
Layered Rendering
上面说到了,GS的一大用处体现在layerd rendering,即多层渲染。
试想一下,对于多层纹理而言,每一层的纹理坐标实际上是相同的。层号如何指定呢?固然可以在建立VBO时给纹理坐标第三个维度值,但是这并不会让OpenGL画到你想画的层上:Fragment的过程就是将顶点像素化的过程,而这个过程是二维的……最后拿到的二维图像必然没有深度,层数无从谈起。
但是Geometry却有个关键的内建输出变量:out int gl_Layer
,正是它能够指定绘制的层数。我们不妨先将我写出来的geometry shader的内容贴出来,注意显卡至少要支持到OpenGL 4.0,#version
这句至少是400:
1 #version 450 2 layout (triangles, invocations = 2) in; //输入三角形,2次调用 3 layout (triangle_strip, max_vertices = 3) out; //输出三角形 4 in vec2 gTexCoord[]; //从Vertex传过来的纹理坐标 5 out vec2 fTexCoord; //传到Fragment去的纹理坐标 6 out int gl_Layer; //层数的标记 7 void main() 8 { 9 for(int k=0; k<gl_in.length(); k++) //针对三角形每个顶点 10 { 11 gl_Layer = gl_InvocationID; //用调用编号标记层号 12 fTexCoord = gTexCoord[k]; //纹理坐标传递 13 gl_Position = gl_in[k].gl_Position; //顶点坐标传递 14 EmitVertex(); //开始传递顶点信息,对每个顶点调用一次 15 } 16 EndPrimitive(); //结束一个primitive,一个primitive调用一次 17 }
需要注意的是,triangles
意味着你在OpenGL的绘制指令必须是GL_TRIANGLE
、GL_TRIANGLE_STRIP
或者GL_TRIANGLE_FAN
。其他的对应关系可以在Wiki查到。三角形有3个顶点,这一组3个顶点将同时进入Geometry中,因此在Geometry中能拿到一个gl_in[]的内建数组,这个数组的大小应该跟绘制时一组顶点的数量一致,三角形就是3。而在这里我不需要增加顶点,因此输出也还是3个顶点。同时纹理坐标也理所当然地变成了数组。因此需要一个循环来对三角形的每个顶点进行操作。
这里顶点坐标和纹理坐标都不必改,因此直接传递过去了。重点在于gl_Layer这一句。gl_Layer
这个内建变量用于指示当前绘制的层号,这个值将影响像素化后像素绘制到哪一层上。OpenGL要求一组顶点(这里是一个三角形)内部的gl_Layer必须一致。这里赋值之后顺便把它传到Fragment里作为标志。
关键的一步在于第二行的invocations = 2
和gl_InvocationID
这个内建变量。invocations=n
指示Geometry对每组顶点做n次运算,用gl_InvocationID
来标记每次运算的序号。我们可以将这个序号送到gl_Layer
来作为层号的值!这样geometry就会将这一组顶点重复发送两次,而这两次是发送到不同层上的,从而实现不同层的绘制!
接下来只要在Fragment里拿到这个gl_Layer,根据需要分层作处理就可以了。