OpenGL ES for Android(几何着色器)

作者: 不正经的创作者 | 来源:发表于2020-05-27 16:47 被阅读0次

    简介

    几何着色器(Geometry Shader)是一个可选功能,他介于在顶点和片段着色器之间,接收一组顶点数据,可以对数据进行处理,而且可以根据数据生成不止一个图形,假如你想绘制四个顶点,按照以前的方式,需要for循环四次,每次都顶点数据进行处理,最后传入顶点着色器中。而集合着色器就做了这样一件事。在移动平台上,几何着色器需要OpenGL ES 3.2版本(android 7之上),同样的我们可以先参考文档或其他相关资料学习3.0新特性。

    3.0变化

    这里简单学习一下3.0和2.0比较重要的变化

    • attribute和varying,取而代之的是 in和out

    • 文件需要添加#version 300 es (如果是3.2则是320,不加则默认2.0)

    • 还有纹理 texture2D和texture3D统统改为 texture

    • 内置函数gl_FragColor和gl_FragData删除,如果片段着色器要输出用out声明字段输出。不过保留了gl_Position

    • 还有的是layout的作用:可以直接指定位置

      // opengl 2.0
      uniform  float intensity;
      // 代码 赋值
      GLES20.glUniform1f(GLES20.glGetAttribLocation(program, 
      "intensity"), 1f)
    
      //opengl 3.0
      layout (location = 1) uniform  float intensity;
      //直接写上对应的layout的值就可以赋值
      GLES30.glUniform1f(1,1f)
    

    几何着色器

    绘制点

    先看一个几何着色器的例子:

      #version 320 es
      layout (points) in; // 输入
      layout (points, max_vertices = 4) out; //输出
    
      in VS_OUT {
          vec3 color;
      } gs_in[];
    
      out vec3 fColor;
      void build_point(vec4 position);
    
      void main() {
          build_point(gl_in[0].gl_Position);
      }
    
      void build_point(vec4 position){
          fColor = gs_in[0].color;
          gl_Position = position + vec4(-0.5, 0.5, 0.0, 0.0);// 1:左上角
          gl_PointSize = 20.0;
          EmitVertex();
          gl_Position = position + vec4(0.5, 0.5, 0.0, 0.0);// 2:右上角
          EmitVertex();
          gl_Position = position + vec4(-0.5, -0.5, 0.0, 0.0);// 3:左下角
          gl_PointSize = 10.0;
          EmitVertex();
          gl_Position = position + vec4(0.5, -0.5, 0.0, 0.0);// 4:右下角
          fColor = vec3(1.0, 1.0, 1.0);
          EmitVertex();
          EndPrimitive();
      }
    

    这个几何着色器的作用是:输入一个顶点的数据,输出四个顶点显示在屏幕上;前两个点大小为20,后两个点大小为10;前三个点颜色为红色,最后一个是白色。

    在几何着色器的顶部,我们需要声明从顶点着色器输入的类型。这需要在in关键字前声明一个布局修饰符(Layout Qualifier)。这个输入布局修饰符可以从顶点着色器接收下列任何一个值:

    • points:绘制GL_POINTS(1)
    • lines:绘制GL_LINES或GL_LINE_STRIP时(2)
    • lines_adjacency:GL_LINES_ADJACENCY或* * * GL_LINE_STRIP_ADJACENCY(4)(3.0新增类型)
    • triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN(3)
    • triangles_adjacency:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY(6)(3.0新增类型)

    以上是能提供给glDrawArrays渲染函数的几乎所有图形了。如果我们想要将顶点绘制为GL_GL_POINTS,我们就要将输入修饰符设置为points。括号内的数字表示的是一个图形所包含的最小顶点数。

    接下来,我们还需要指定几何着色器输出的类型,这需要在out关键字前面加一个布局修饰符。和输入布局修饰符一样,输出布局修饰符也可以接受几个值:

    • points
    • line_strip
    • triangle_strip

    有了这3个输出修饰符,我们就可以使用输入数据创建几乎任意的形状了。要生成一个点的话,我们将输出定义为points,并输出max_vertices个顶点,如果你生成的顶点数大于max_vertices则不会显示。

    下面是接口块(参考高级GLSL)VS_OUT的定义,用来接收顶点着色器中传来的数据(当然也可以定义变量来传递,但是用接口块更方便)。

    接下来是使用传入的顶点数据,从3.0版本开始GLSL提供了一个内建(Built-in)变量,它的结构大概如下(参考官方文档)

      in gl_PerVertex {
            highp vec4 gl_Position;
            highp float gl_PointSize;
      } gl_in[];
    

    可以通过gl_in数组来取我们传入的顶点数据,例如传入两个顶点,根据索引取出数据即可(gl_in[0]和gl_in[1])。

    EmitVertex方法,指当前一个顶点已经设置完成,包括顶点位置,大小,颜色等等。

    EndPrimitive方法,指当前所有顶点这种完成,开始进行绘制。
    顶点着色器和片段着色器的代码比较简单,如下:

      // 顶点着色器用来传递顶点和颜色数据
      #version 320 es
      layout (location = 0) in vec2 aPos;
      layout (location = 1) in vec3 aColor;
    
      out VS_OUT {
          vec3 color;
      } vs_out;
    
      void main(){
          vs_out.color = aColor;
          gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
      }
    
    
      // 片段着色器
      #version 320 es
      precision mediump float;
    
      out vec4 FragColor;
    
      in vec3 fColor;
    
      void main(){
          FragColor = vec4(fColor, 1.0);
      }
    

    我们传入一个顶点屏幕中心(0,0),颜色传入红色,即可得到如下效果图:

    绘制线

    我们可以通过对点处理生成线。这里我们传入四个顶点:

      points = new float[]{
          -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // 左上
          0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
          0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 右下
          -0.5f, -0.5f, 1.0f, 1.0f, 0.0f  // 左下
      };
    

    然后修改几何着色器的代码,每个顶点生成两个点,左顶点x方向减0.1,右顶点x方向加0.1,把输出的类型改为line_strip,最大值为0,代码如下:

      #version 320 es
      layout (points) in;
      layout (line_strip, max_vertices = 2) out;
    
      in VS_OUT {
          vec3 color;
      } gs_in[];
    
      out vec3 fColor;
      void build_line(vec4 position);
    
      void main() {
          build_line(gl_in[0].gl_Position);
      }
    
      void build_line(vec4 position){
          fColor = gs_in[0].color;
          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();
      }
    

    可绘制出四条线,分别在屏幕的四个角,效果如下:

    绘制房子

    一个小房子的样子如下图:

    因为几何着色器的三角形输出只有triangle_strip(因为它更节省节点),我们根据传入的顶点分别生成从1到5五个顶点即可绘制成小房子的样式,输入的顶点坐标和绘制线的相同,我们只需要修改几何着色器的代码,输出类型改为triangle_strip,数量为5。修改后的代码如下:

      #version 320 es
      layout (points) in;
      layout (triangle_strip, max_vertices = 5) out;
    
      in VS_OUT {
          vec3 color;
      } gs_in[];
    
      out vec3 fColor;
      void build_house(vec4 position);
    
      void main() {
          build_house(gl_in[0].gl_Position);
      }
    
      void build_house(vec4 position){
          fColor = gs_in[0].color;
          gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);// 1:bottom-left
          EmitVertex();
          gl_Position = position + vec4(0.2, -0.2, 0.0, 0.0);// 2:bottom-right
          EmitVertex();
          gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0);// 3:top-left
          EmitVertex();
          gl_Position = position + vec4(0.2, 0.2, 0.0, 0.0);// 4:top-right
          EmitVertex();
          gl_Position = position + vec4(0.0, 0.4, 0.0, 0.0);// 5:top
          fColor = vec3(1.0, 1.0, 1.0);
          EmitVertex();
          EndPrimitive();
      }
    

    效果图如下:

    爆破物体

    物体的爆炸效果,在有些游戏中比较常见,比如当炮弹击中物体时,物体会破碎掉。这样一种效果用几何着色器是可以模拟的(当然效果比较粗糙),我们需要计算绘制的三角形的法向量,然后沿着法向量移动一小段距离,然后根据时间不断的移动或恢复。

    计算法向量的代码:

      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));
      }
    

    计算爆炸效果的代码

      vec4 explode(vec4 position, vec3 normal){
          float magnitude = 2.0;
          vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude;
          return position + vec4(direction, 0.0);
      }
    

    最后是整个几何着色器的代码,

      #version 320 es
      layout (triangles) in;
      layout (triangle_strip, max_vertices = 3) out;
    
      in VS_OUT {
          vec2 texCoords;
      } gs_in[];
    
      out vec2 TexCoords;
    
      uniform float time;
    
      vec4 explode(vec4 position, vec3 normal);
      vec3 GetNormal();
    
      vec4 explode(vec4 position, vec3 normal){
      ……
      }
    
      vec3 GetNormal(){
      ……
      }
    
      void main() {
          vec3 normal = GetNormal();
    
          gl_Position = explode(gl_in[0].gl_Position, normal);
          TexCoords = gs_in[0].texCoords;
          EmitVertex();
          gl_Position = explode(gl_in[1].gl_Position, normal);
          TexCoords = gs_in[1].texCoords;
          EmitVertex();
          gl_Position = explode(gl_in[2].gl_Position, normal);
          TexCoords = gs_in[2].texCoords;
          EmitVertex();
          EndPrimitive();
      }
    

    找出我们之前的模型加载中加载纳米装的代码,传入顶点和纹理,运行代码即看到如下的效果,因为图片被压缩过效果不太好,可以下载源码来查看。

    法向量可视化

    在学习光照效果时,处理法向量时可能出现错误,但是因为glsl无法调试,无法找到问题所在。这里我们可以利用几何着色器把法向量可视化,进行调试。

    法向量可视化需要先绘制物体,然后绘制法向量,还是以纳米装为例,绘制纳米装不再赘述,绘制法向量则只需要传入顶点和法向量即可,然后在几何着色器中进行处理,根据传入的顶点坐标和经过处理的法向量绘制一条线(每个三角形三条线),代码如下:

      #version 320 es
      layout (triangles) in;
      layout (line_strip, max_vertices = 6) out;
    
      in VS_OUT {
          vec3 normal;
      } gs_in[];
    
      const float MAGNITUDE = 0.2;
      void GenerateLine(int index);
    
      void GenerateLine(int index){
          gl_Position = gl_in[index].gl_Position;
          EmitVertex();
          gl_Position = gl_in[index].gl_Position + 
      vec4(gs_in[index].normal, 0.0) * MAGNITUDE;
          EmitVertex();
          EndPrimitive();
      }
    
      void main(){
          GenerateLine(0); // 第一个顶点的法向量
          GenerateLine(1); // 第二个顶点的法向量
          GenerateLine(2); // 第三个顶点的法向量
      }
    

    最后的效果图如下:

    这样就能帮助我们来判断模型的法向量是否正确了,同样可以用来实现其他功能,例如毛发效果。

    本章对应的文档地址,可以参考进行理论学习。
    本章源码地址

    有看过文章的朋友可以选择性点个赞,关注下,相互学习。

    相关文章

      网友评论

        本文标题:OpenGL ES for Android(几何着色器)

        本文链接:https://www.haomeiwen.com/subject/fyowahtx.html