OpenGL ES for Android(几何着色器)

简介

几何着色器(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); // 第三个顶点的法向量
  }

最后的效果图如下:

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

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

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

日记本

猜你喜欢

转载自blog.csdn.net/ajsliu1233/article/details/106386203