OpenGL基础41:几何着色器

 

在顶点着色器之后,片段着色器之前,还有几何着色器,它是可选的,在《OpenGL基础3:渲染管线》这一章中就有提到了,有了几何着色器后可以做很多骚操作,更容易实现很多有意思的效果

一、最简单的几何着色器(Geometry Shader)

几何着色器的输入:一个或多个表示单独基本图形(primitive)的顶点,这个基本图形可以是点、线或者三角形

输出:处理过后的基本图形顶点,这些顶点无论是大小还是位置,甚至是数量、基本图形单元都可以被改变、拼装

最“简单”的几何着色器如下:

#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT
{
    vec2 texIn;
    vec3 normalIn;
    vec3 fragPosIn;
}vs_in[];
out vec2 texIn;
out vec3 normalIn;
out vec3 fragPosIn;
void main()
{
    for (int i = 0; i <= 2; i++)
    {
        gl_Position = gl_in[i].gl_Position;
        texIn = vs_in[i].texIn;
        normalIn = vs_in[i].normalIn;
        fragPosIn = vs_in[i].fragPosIn;
        EmitVertex();
    }
    EndPrimitive();
}

其实可以看出来,它也不是最简单的几何着色器,因为它接受和输出的顶点都是三角形,而不是点,并且对每个顶点都进行了传输,甚至包括了顶点着色器要传递的值。那之所以拿这个来举例子,是因为它可以应用于链接之前物体的顶点着色器和片段着色器,并且不会对结果进行任何改变

了解了这个几何着色器,就可以很轻松的在这基础上做一些效果了

几何着色器中的 layout 修饰符:

前面两个 layout 标识符是不能多也不能少的,分别声明了输入的基本图元类型,以及要输出的基本图元类型

输入的基本图元类型可以是以下几种:

  • points:绘制 GL_POINTS 基本图形的时候
  • lines:绘制 GL_LINES 或 GL_LINE_STRIP 基本图形的时候
  • lines_adjacency:同上,GL_LINES_ADJACENCY 或 GL_LINE_STRIP_ADJACENCY
  • triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP 或 GL_TRIANGLE_FAN
  • triangles_adjacency:GL_TRIANGLES_ADJACENCY 或 GL_TRIANGLE_STRIP_ADJACENCY

具体哪一个要和绘制时的 glDrawElements 方法对应

输出的基本图元类型就是以下3种:

  • points:点
  • line_strip:线
  • triangle_strip:三角形

对于上面的样例,就可以理解为输入为最基础的三角形图元,并原样输出

可以看到,第二个layout中还设置了 max_vertices = 3,它限制了输出的顶点数量的最大值,如果超出了这个数值,OpenGL就会忽略剩下的顶点

 

上面描述的所有东西,都可以用一张图来举例:

已知绘制时填入的基本图形是GL_POINTS,并且顶点着色器会正确的输出上图中5个顶点的位置,那么如何通过几何着色器将这5个顶点按照先后的顺序依次相连成线并输出至片段着色器呢?

那么显然,此时输入的基本图元类型就要定义为points,输出图元类型要定义为line_strip,并且设置最大顶点数量max_vertices = 5,假设错误的设置了max_vertices为2,那么就只会绘制第一段线段,如果错误的设置了输出图元类型或者输入图元类型,那么就会直接报错或得到非常离谱的渲染结果

 

二、内建变量gl_in[]

这是一个非常重要的内建变量,几何着色器正是从中获得顶点着色器传来的顶点信息的,它也是一个默认的接口块,内容如下:

in gl_Vertex
{
    vec4 gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
} gl_in[];

这3个变量也都是顶点着色器的内建变量,前2个在《OpenGL基础39:GLSL内建变量与接口块》这一章有详细介绍,其中gl_Position就是位置信息,大部分情况下也只需要用到它(第三个参数先不管)

它被声明为一个数组,因为大多数渲染基本图形由一个以上顶点组成,几何着色器接收一个基本图形的所有顶点作为它的输入,如果你接受的基本图形是三角形,那么这个数组大小就是3

着色器之间传数据通过 in 和 out 关键字,只需要在当前着色器中声明一个输出,在下一个着色器中声明一个同类型同名字的输入就可以,当然,现在在中间多了一个几何着色器,那么顶点着色器中的数据就不会直接传入片段着色器,而是会被传入几何着色器,那么:

  • 在这里几何着色器是中介,要把顶点着色器传入的信息再传入片段着色器
  • 有了顶点着色器的这些信息,当然也可以拿来进行数据的处理

正如上面的第一份代码,里面就将顶点着色器的三个数据(接口块),拆掉并传入片段着色器

下面这份代码,就可以将接受的4个顶点,输出为4条线段,每条线段长度为0.2:

glDrawArrays(GL_POINTS, 0, 4);
#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;
void main()
{
    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();
}

 

三、绘制规则

到这里,几何着色器还有两个非常重要的函数 EmitVertex() 和 EndPrimitive():

  • EmitVertex():每当调用一次,当前设置到gl_Position的向量就会被添加到基本图形上,可以理解为输出一个顶点
  • EndPrimitive():结束当次图元输出

对于左图,顶点①⑤之间并没有连线,对于右图,顶点①②④并没有连成三角形,这也说明了一点,生成线段和三角形的顺序必须要保证正确

  • 对于线段模式:每次EmitVertex(),都会连接当前顶点和上一个顶点
  • 对于三角形模式:每次EmitVertex(),都会生成贴着前一个三角形的新三角形,也就是连接当前顶点和上两个顶点
  • 只要EndPrimitive(),就会“另起一段”开启下一次绘制,也就是前面说的“上x个顶点”就不存在了,下一个顶点就会是“第一个”顶点,重新开始

也就是说,对于右图顶点的顺序正是数字顺序

 

四、一个例子

尝试修改下最上面的片段着色器,看下能有什么好玩的效果:

①例如将所有的三角形片元都沿着它的法向量方向移动一段距离:

#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT
{
    vec2 texIn;
    vec3 normalIn;
    vec3 fragPosIn;
}vs_in[];
out vec2 texIn;
out vec3 normalIn;
out vec3 fragPosIn;
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));
}
void main()
{
    for (int i = 0; i <= 2; i++)
    {
        gl_Position = gl_in[i].gl_Position + vec4(GetNormal(), 1.0) * 0.1f;
        texIn = vs_in[i].texIn;
        normalIn = vs_in[i].normalIn;
        fragPosIn = vs_in[i].fragPosIn;
        EmitVertex();
    }
    EndPrimitive();
}

②又或者新增一套着色器,来实现物体的法向量可视化:

#version 420 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
out VS_OUT
{
    vec3 normalIn;
}vs_out;
uniform mat4 model;             //模型矩阵
layout (std140, binding = 0) uniform Matrices
{
    mat4 view;                  //观察矩阵
    mat4 projection;            //投影矩阵
};
void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0);
    vs_out.normalIn = mat3(transpose(inverse(model))) * normal;
}

//

#version 330 core
layout (triangles) in;
layout (line_strip, max_vertices = 6) out;
in VS_OUT
{
    vec3 normalIn;
}vs_in[];
void main()
{
    for (int i = 0; i <= 2; i++)
    {
        gl_Position = gl_in[i].gl_Position;
        EmitVertex();
        gl_Position = gl_in[i].gl_Position + vec4(normalize(vs_in[i].normalIn), 0.0f) * 0.2f;
        EmitVertex();
        EndPrimitive();
    }
}

//

#version 330 core
out vec4 color;
void main()
{
    color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

最后:要改写之前的Shader.h:

#ifndef SHADER_H
#define SHADER_H
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
#include<opengl/glew.h>

class Shader
{
    public:
        GLuint Program;
        Shader(const GLchar* vertexPath, const GLchar* fragmentPath, const GLchar* geometryPath = "")
        {
            std::string vertexCode;
            std::string geometryCode;
            std::string fragmentCode;
            std::ifstream vShaderFile;
            std::ifstream gShaderFile;
            std::ifstream fShaderFile;
            vShaderFile.exceptions(std::ifstream::badbit);
            gShaderFile.exceptions(std::ifstream::badbit);
            fShaderFile.exceptions(std::ifstream::badbit);
            try
            {
                vShaderFile.open("Shader/" + (std::string)vertexPath);
                fShaderFile.open("Shader/" + (std::string)fragmentPath);
                std::stringstream vShaderStream, fShaderStream;
                vShaderStream << vShaderFile.rdbuf();
                fShaderStream << fShaderFile.rdbuf();
                vShaderFile.close();
                fShaderFile.close();
                vertexCode = vShaderStream.str();
                fragmentCode = fShaderStream.str();
                if ((int)((std::string)geometryPath).length() >= 3)
                {
                    std::stringstream gShaderStream;
                    gShaderFile.open("Shader/" + (std::string)geometryPath);
                    gShaderStream << gShaderFile.rdbuf();
                    gShaderFile.close();
                    geometryCode = gShaderStream.str();
                }
            }
            catch (std::ifstream::failure e)
            {
                std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
            }
            const GLchar* vShaderCode = vertexCode.c_str();
            const GLchar* fShaderCode = fragmentCode.c_str();

            GLuint vertex, geometry, fragment;
            GLint success;
            GLchar infoLog[512];

            vertex = glCreateShader(GL_VERTEX_SHADER);
            glShaderSource(vertex, 1, &vShaderCode, NULL);
            glCompileShader(vertex);
            glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(vertex, 512, NULL, infoLog);
                std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
            }

            geometry = -1;
            if (geometryCode.size() != 0)
            {
                const GLchar* gShaderCode = geometryCode.c_str();
                geometry = glCreateShader(GL_GEOMETRY_SHADER);
                glShaderSource(geometry, 1, &gShaderCode, NULL);
                glCompileShader(geometry);
                glGetShaderiv(geometry, GL_COMPILE_STATUS, &success);
                if (!success)
                {
                    glGetShaderInfoLog(geometry, 512, NULL, infoLog);
                    std::cout << "ERROR::SHADER::GEOMETRY::COMPILATION_FAILED\n" << infoLog << std::endl;
                }
            }

            fragment = glCreateShader(GL_FRAGMENT_SHADER);
            glShaderSource(fragment, 1, &fShaderCode, NULL);
            glCompileShader(fragment);
            glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(fragment, 512, NULL, infoLog);
                std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
            }

            this->Program = glCreateProgram();
            glAttachShader(this->Program, vertex);
            glAttachShader(this->Program, fragment);
            if (geometry != -1)
                glAttachShader(this->Program, geometry);
            glLinkProgram(this->Program);
            glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
                std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
            }
            glDeleteShader(vertex);
            glDeleteShader(fragment);
            if (geometry != -1)
                glDeleteShader(geometry);
        }
        void Use()
        {
            glUseProgram(this->Program);
        }
};
#endif

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值