OpenGL之几何着色器(Geometry Shader)详解

一、啰嗦几句

这几天做效果,遇到一些效果不能单纯用片元着色器实现,所以考虑换个方向,使用几何着色器去实现。为何说是换个方向呢?我主要玩纹理,就从纹理来讲,如果我们需要对纹理做个偏移或者其他一系列操作,只用片元去实现的话,在片元里是通过经过操作后的效果去求原像素坐标,这是逆向的;如果用几何着色器实现的话,则是通过原像素坐标经过系列操作求到操作后的坐标,这是正向的。所以有时候片元里求原像素坐标不好求解就要考虑使用几何着色器了。

二、解剖几何着色器

关于几何着色器的基础使用,可以参考官方教程:LearnOpenGL-几何着色器

这里将介绍一些教程里没有的东西,我玩纹理较多,就从加载纹理来介绍。

1.几何着色器语法解释

layout (points) in;
layout (points, max_vertices = 256) out; //最大支持256*4位,包括传出数据


void main()
{
    gl_Position = gl_in[0].gl_Position;
    EmitVertex();
    
    EndPrimitive();
}

这是最简单的几何着色器,啥也不干,传入点,输出点。

layout (points) in; --表示传入的是点,由于几何着色器执行顺序位于顶点着色器与片元着色器之间,因此一般传入都是点;

layout (points, max_vertices = 256) out; --表示传出的类型,这里是点,可根据自己情况更改,支持三种类型:points,line_strip,triangle_strip。max_vertices最大为256,这是几何着色器限制,表示传出的最大点数为256,经过测试,传出别的数据也包括,因此按传出的每个点为vec4,即4位计算,传出数据最大位数为256*4;

gl_Position = gl_in[0].gl_Position; --gl_Position是内置的传出点,与顶点着色器里一样;gl_in[0].gl_Position是内置的传入点,其实就是顶点着色器里传进来的那个,需要注意的是它的第4位必须为1,否则会出问题;

EmitVertex(); -- 每次调用一次,表示输出一个点即当前gl_Position;

EndPrimitive(); --每次调用一次,表示输出一个图元,如果输出类型是三角形,那么就输出一个三角形带。

2.几何着色器使用

绑定传入顶点的代码:

        float points[] = {
        0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 
        };

        glGenVertexArrays(1, &VAO[0]);
        glBindVertexArray(VAO[0]);
        // Step2: 创建并绑定VBO对象
        glGenBuffers(1, &VBO[0]);
        // Step3: 分配空间 传送数据
        glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
        // Step4: 指定解析方式  并启用顶点属性
        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (GLvoid*)(4 * sizeof(GL_FLOAT)));
        glEnableVertexAttribArray(1);
        glBindVertexArray(0);

由于是点,所以这里不需要EBO,EBO用于解释三角形中点的索引,如果是三角形还需要写EBO

绘制:

glDrawArrays(GL_POINTS, 0, 1);

如果是三角形使用了EBO,那么就需要用glDrawElements绘制。

3.几何着色器改变纹理坐标

根据之前经验,没有几何着色器的话,纹理坐标是从顶点着色器传出,片元着色器接入,而顶点着色器的纹理坐标是在外部与顶点一一对应的,所以我们可以知道,怎么传不重要,重要的是到片元那里接入的纹理坐标值与顶点一一对应就OK了。因此我们可以把外部做的工作移到几何着色器里就好。

几何着色器代码:

layout (points) in;
layout (triangle_strip, max_vertices = 256) out; //最大支持256*4位,包括传出数据

out vec2 aTex; //传出纹理坐标,给片元

void main()
{
    gl_Position = gl_in[0].gl_Position + vec4(-1.0,-1.0,0.0,0.0);//左下角坐标
    aTex = vec2(0.0,0.0);
    EmitVertex();
    gl_Position = gl_in[0].gl_Position + vec4(-1.0,1.0,0.0,0.0); //左上角坐标
    aTex = vec2(0.0,1.0);
    EmitVertex();
    gl_Position = gl_in[0].gl_Position + vec4(1.0,-1.0,0.0,0.0); //右下角坐标
    aTex = vec2(1.0,0.0);
    EmitVertex();
    gl_Position = gl_in[0].gl_Position + vec4(1.0,1.0,0.0,0.0); //右上角坐标
    aTex = vec2(1.0,1.0);
    EmitVertex();
    
    EndPrimitive();
}

这里需要注意的是,顶点坐标在窗口的范围是-1到1的,而纹理坐标是0到1。另外点的顺序也是有讲究的,输出类型是三角形,那么三角形带对点的排列规则是这样的,比如输出4个点,1234,那么会构成两个三角形123和234。为了让构成的两个三角形覆盖整个正方形,需要好好排列的。

4.几何着色器细分曲面

虽然玩纹理,但是依然可以出现曲面的效果,比如我对纹理做个变形,让他扭曲,对于几何着色器来说就是多个面了。对于多个面,几何着色器也就是多几条三角形带,几何着色器只能传出256*4位数据,加上纹理坐标,最多支持不到200个点,要做到细分还是太难,整出来的图像就不好看了。那么有什么办法呢?

既然一个点的细分范围有限,何不多分一些点呢?把图像分成16块试试,传入16个点。

这里将点的三位和四位用作图像块的序号。

        float points[] = {
        -0.75f,  -0.75f, 0.0f, 0.0f, 0.0f, 0.0f, //第一列
        -0.75f,  -0.25f, 0.0f, 1.0f, 0.0f, 0.0f, //第一列
        -0.75f,  0.25f, 0.0f, 2.0f, 0.0f, 0.0f, //第一列
        -0.75f,  0.75f, 0.0f, 3.0f, 0.0f, 0.0f, //第一列

        -0.25f,  -0.75f, 1.0f, 0.0f, 0.0f, 0.0f, //第二列
        -0.25f,  -0.25f, 1.0f, 1.0f, 0.0f, 0.0f, //第二列
        -0.25f,  0.25f, 1.0f, 2.0f, 0.0f, 0.0f, //第二列
        -0.25f,  0.75f, 1.0f, 3.0f, 0.0f, 0.0f, //第二列

        0.25f,  -0.75f, 2.0f, 0.0f, 0.0f, 0.0f, //第三列
        0.25f,  -0.25f, 2.0f, 1.0f, 0.0f, 0.0f, //第三列
        0.25f,  0.25f, 2.0f, 2.0f, 0.0f, 0.0f, //第三列
        0.25f,  0.75f, 2.0f, 3.0f, 0.0f, 0.0f, //第三列

        0.75f,  -0.75f, 3.0f, 0.0f, 0.0f, 0.0f, //第四列
        0.75f,  -0.25f, 3.0f, 1.0f, 0.0f, 0.0f, //第四列
        0.75f,  0.25f, 3.0f, 2.0f, 0.0f, 0.0f, //第四列
        0.75f,  0.75f, 3.0f, 3.0f, 0.0f, 0.0f //第四列
        };

在绘制的时候记得将点数改为16:glDrawArrays(GL_POINTS, 0, 16);

几何着色器代码:

layout (points) in;
layout (triangle_strip, max_vertices = 256) out; //最大支持256*4位,包括传出数据

out vec2 aTex; //传出纹理坐标,给片元

void main()
{   
    EndPrimitive();
    float Num = 8;
    for(int j = 0;j<Num;j++)
    {
        for(int i = 0;i<Num+1;i++)
        {
            float x = gl_in[0].gl_Position.x + 0.5*float(i)/Num;
            float y = gl_in[0].gl_Position.y + 0.5*float(j)/Num;
            gl_Position = vec4(x, y, 0.0, 1.0);
            aTex = 0.25*gl_in[0].gl_Position.zw + vec2(0.25*float(i)/Num,0.25*float(j)/Num);
            EmitVertex();
            y += 0.5/Num;
            gl_Position = vec4(x, y, 0.0, 1.0);
            aTex = 0.25*gl_in[0].gl_Position.zw + vec2(0.25*float(i)/Num,0.25*float(j+1)/Num);
            EmitVertex();
        }
        EndPrimitive();
    }
}

每个方块里面再进行8*8的细分,这样应该就够用了。由于这里没做任何变形处理,虽然绘制的是多个面,但效果出来还是同一个面。稍微做一下变形就可以有曲面的效果。

另外,这里是利用了顶点数据的三四位作为方块的索引号,有时候可能三四位被占用,那么就只能另外传参了,比如这里的五六位,在顶点着色器里传出,然后在几何着色器里接入,需要提一下的是,几何着色器里接入的参数声明,必须是数组,比如这样:

in vec2 aPosIndex[]; //必须是数组

刚开始我没注意这个东西,以为传入的是点,就没管它,吃了不少亏,没想通。猜想了下,着色器里才不会去判断你传入的是啥类型,这是它的标准,为了适用所有的类型。

三、总结

几何着色器的利:计算是正向思维,不需要绕路。

几何着色器的弊:导出点数量有限,曲线需要细分多次才能看起来没瑕疵,而且这个次数视情况而定。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值