几何着色器

  几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段。它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的输出;相对应的几何着色器的输出也是完整的图元信息。所以简单的理解几何着色器就是一个我们可以对图元信息再次修改的阶段。

这个修改可以体现在两个方面,一个是图元的数量,一个是图元的类型。即我们可以输入一个三角形,然后输出两个甚至更多的三角形,当然也可以不输出三角形;而图元类型改变则可理解为输入如果是三角形,输入可变为点或线的其它图元类型。

什么都不做

  首先我们先看一个最简单的例子,这个几何着色器什么都不做,即不输出图元。当然,这样的着色器不具备实用价值。

  1. #version core
  2.  
  3. layout(points) in;
  4. layout(points, max_vertices=) out;
  5.  
  6. void main()
  7. {
  8.  
  9. }

  这个例子很简单,main函数是主过程,我们留空,表示什么都不做。接下来,我们聚焦几何着色器的关键点。第一就是“layout(points) in”这行代码,表示的是几何着色器的输入图元类型,即几何着色器逐图元执行的(顶点着色器逐顶点,片元着色器逐片元);这个图元类型要与程序绘制时指定的图元类型兼容,如points对应GL_POINTS,line对应GL_LINES,triangles对应GL_TRIANGLES;第二行声明几何着色器输出图元类型是点,输出的最大顶点数是1。注意的是在几何着色器中,输出的图元类型只能是点、多线段条带和三角形条带,不能输出独立的线段或三角形,也不能循环线或三角形扇面。这是因为条带可以视为一个独立图元类型的的一个超集,即一个独立的三角形或线段就是一个图元的条带而已。我们如果绘制一个三角形就立即结束条带,那就相当于在绘制一个独立的三角形。

直接往后传递

  下面是把输入数据原封不动的往下一个阶段传递。

  1. #version core
  2.  
  3. layout(points) in;
  4. layout(points, max_vertices=) out;
  5.  
  6. out vec4 fColor;
  7.  
  8. void main()
  9. {
  10. int n;
  11.  
  12. for(n =; n < gl_in.length();n++)
  13. {
  14. gl_Position = gl_in[n].gl_Position;
  15. EmitVertex();
  16. }
  17.  
  18. EndPrimitive();
  19. }

gl_PrimitiveIDIn

  gl_PrimiiveIDIn是一个几何着色器阶段的glsl的内置变量,可以理解为图元的唯一标识符。它是一个整形变量,从0开始,与它对应的是片元着色器的输入gl_PrimitiveID。如果片元着色的gl_PrimitiveID是有效的,并且几何着色器也是有效的,则几何着色器必须对gl_PrimitiveID进行赋值。下面是一个只渲染序号为奇数的图元几何着色器例子。

  1. #version core
  2.  
  3. layout(points) in;
  4. layout(points, max_vertices=) out;
  5.  
  6. void main()
  7. {
  8. if((gl_PrimitiveIDIn & ) == )
  9. {
  10. int n;
  11.  
  12. for(n =; n < gl_in.length();n++)
  13. {
  14. gl_Position = gl_in[n].gl_Position;
  15. EmitVertex();
  16. }
  17.  
  18. EndPrimitive();
  19. }
  20.  
  21. }

gl_layer

  gl_Layer是几何着色器的一个变量,用于实现分层渲染。这个分层渲染通常是指渲染到帧缓存对象,而帧缓存的附件通常是一个二维数组纹理或者cube map,所以这个层可理解为二维数组纹理的一个片或者cube map的一个面。分层渲染即在几何着色器中实现对每一层分别进行不同的渲染。下面我们看一个分层渲染的例子:

  1. #version core
  2.  
  3. layout(triangles) in;
  4. layout(triangle_strip, max_vertices=) out;
  5.  
  6. in VS_GS_VERTEX
  7. {
  8. vec4 color;
  9. }vertex_in[];
  10.  
  11. out GS_FS_VERTEX
  12. {
  13. vec4 color;
  14. }vertex_out;
  15.  
  16. uniform mat4 proj;
  17. uniform int output_slices;
  18.  
  19. void main()
  20. {
  21. for(int j =; j < output_slices;++j)
  22. {
  23. gl_Layer = j;
  24. for(int i = ; i < gl_in.length(); ++i)
  25. {
  26. gl_Position = proj * gl_in[i].gl_Position;
  27. vertex_out.color = vertex_in[i].color;
  28. EmitVertex();
  29. }
  30. EndPrimitive();
  31. }
    }

  在这个几何着色器中,我们通过程序设置output_slices的值来控制层的数量,而这个层的数量是与二维数组纹理的数量或者cube map的面的数量一致。这个着色器中最重要的一步就是写入gl_Layer,表示我们输出图元的层。在这里我们在所有的层中渲染了同样的信息,读者可以根据自己的意愿进行设计。所以在这个着色器中我们是对几何体进行了扩充了,扩充的倍数是output_slices。

  而这个用法最典型的一个应用就是制作一个点光源的阴影纹理。我们知道点光源会向所有的方向发射光,所以我们使用阴影贴图的方式来产生阴影的话,我们就需要在6个方向上都生成一张阴影贴图,如果为此我们渲染场景6次,那对性能的影响会比较大,所以可以通过分层渲染的技巧来实现这6张阴影贴图的绘制。大致的一个流程就是:

1.构建一个cube map;

2.构建帧缓存并把这个cube map关联到帧缓存的深度缓存附件上;

3.根据点光源的位置信息构建6个观察矩阵并传入几何着色器中;

4.然后分层渲染6次,每一层使用对应的观察矩阵即可;

其中cube map 6个面的值分别是:

GL_TEXTURE_CUBE_MAP_POSITIVE_X   0

GL_TEXTURE_CUBE_MAP_NEGATIVE_X   1

GL_TEXTURE_CUBE_MAP_POSITIVE_Y  2

GL_TEXTURE_CUBE_MAP_NEGATIVE_Y  3

GL_TEXTURE_CUBE_MAP_POSITIVE_Z  4

GL_TEXTURE_CUBE_MAP_NEGATIVE_Z  5

具体的流程可参考:https://learnopengl-cn.github.io/05%20Advanced%20Lighting/03%20Shadows/02%20Point%20Shadows/

glViewportIndex

  glViewportIndex是几何着色器中的另一个内置变量,可用于实现多视口渲染。通常在OpenGL中设置视口调用的函数是glViewport,使用它设置的是当前渲染的视口范围。所以我们有一种实现多视口的方法:使用glViewport设置视口,渲染场景,再次调用glViewport设置视口,渲染场景……。在几何着色器出现以前,我们实现多视口的方法通常就是它,但在着色器之后我们可以使用另一种方法来实现,就是glViewportIndex。

在介绍glViewportIndex之前,我们要介绍3个设置视口参数的函数:

glViewportIndexedf(GLuint index,GLfloat x,GLfloat y, GLfloat w,GLfloat h);

glViewportIndexedfv(GLuint index, GLfloat* v);

glDepthRangeIndexedf(GLuint index, GLclampd n, GLclampd f);

  其中(x,y)表示是视口的左上角(视口是矩形),w,h表示视口的宽和高。index是视口的索引,因为我们是多视口渲染,需要index来表示设置的是哪个视口。glViewportIndexedfv是向量版,即视口的参数存储在一个向量中;glDepthRangeIndexedf设置视口的深度范围,n和f表示视口的近平面和远平面。

  通过几何着色器实现多视口渲染两种方式,一种是通过几何着色器扩充几何体,即一个物体可扩充为两个物体;另一种是几何着色器的实例化功能,设置请求invocations为指定的数量,如3,然后再在对应的视口中完成几何体的渲染。

下面我们通过例子介绍第二种方式的实现,第一种方式可类推。

 几何着色器:

  1. #version core
  2.  
  3. layout(triangles,invocations = ) in ;
  4. layout(triangle_strip, max_vertices=) out;
  5.  
  6. uniform mat4 model_matrix[];
  7. uniform mat4 proj;
  8.  
  9. out vec4 gs_color;
  10.  
  11. const vec4 colors[] = vec4[]
  12. (
  13. vec4(1.0,0.7,0.3,1.0),
  14. vec4(1.0,0.2,0.3,1.0),
  15. vec4(0.1,0.6,1.0,1.0),
  16. vec4(0.3,0.7,0.5,1.0)
  17. );
  18.  
  19. void main()
  20. {
  21. for(int i=;i<gl_in.length();++i)
  22. {
  23. gl_ViewportIndex = gl_InvocationID;
  24. gs_color = colors[gl_InvocationID];
  25.  
  26. //gs_normal = (model_matrix[gl_InvocationID] * vec4(vs_normal[i],0.0)).xyz;
  27. gl_Position = proj * (model_matrix[gl_InvocationID] * gl_in[i].gl_Position);
  28. EmitVertex();
  29. }
  30. }

  在这个几何着色器中,重点是invocations和gl_ViewportIndex的设置。invocations表示图元处理请求的数量,简单的理解就是对这个图元处理几次,在这里我们处理4次,其中一个实例化相关的内置变量是gl_InvocationID,表示几何着色器请求(Invocation)分配的调用值(Invocation),也可以简单的理解为第几次调用,通过gl_Invocation来给gl_ViewportIndex赋值表示渲染到哪个视口,而model_matrix是视口渲染所用矩阵,所以这个几何着色器的流程就是先设置视口索引,然后使用该视口的矩阵变换顶点,输出的图元。结果就是在事先设置的视口中按给定的矩阵渲染场景,即实现了多视口渲染。

其中视口参数设置:

  1. void Reshape(int width, int height)
  2. {
  3. const float wot = float(width) * 0.5f;
  4. const float hot = float(height) * 0.5f;
  5.  
  6. glViewportIndexedf(,0.0f,0.0f,wot,hot);
  7. glViewportIndexedf(,wot,0.0f,wot,hot);
  8. glViewportIndexedf(,0.0f,hot,wot,hot);
  9. glViewportIndexedf(,wot,hot,wot,hot);
  10. }

  顶点着色器和片元着色器:

  1. #version core
  2.  
  3. layout(location=) in vec3 iPos;
  4.  
  5. //uniform mat4 model;
  6. //uniform mat4 view;
  7. //uniform mat4 proj;
  8.  
  9. void main()
  10. {
  11. gl_Position = vec4(iPos,1.0);
  12. }

  片元着色器:

  1. #version core
  2.  
  3. in vec4 gs_color;
  4. out vec4 color;
  5.  
  6. void main()
  7. {
  8. color = gs_color;
  9. }

Linux OpenGL 实践篇-13-geometryshader的更多相关文章

  1. Linux OpenGL 实践篇-6 光照

    经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反 ...

  2. Linux OpenGL 实践篇-5 纹理

    纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...

  3. Linux OpenGL 实践篇-4 坐标系统

    OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...

  4. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  5. Linux OpenGL 实践篇-2 创建一个窗口

    OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...

  6. Linux OpenGL 实践篇-1 OpenGL环境搭建

    本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...

  7. Linux OpenGL 实践篇-16 文本绘制

    文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...

  8. Linux OpenGL 实践篇-15-图像数据操作

    OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...

  9. Linux OpenGL 实践篇-14-多实例渲染

    多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...

随机推荐

  1. 理解复杂的const和typedef和指针的关系

    // container.cpp : 定义控制台应用程序的入口点. //   #include "stdafx.h" #include<iostream> #inclu ...

  2. g2o使用bug总结

    g2o进行3d2d优化的时候,设置优化图的边时,注意setVertex()中顶点的顺序. void setVertex(size_t i, Vertex* v) { assert(i < _ve ...

  3. 基于Jenkins自动构建系统开发

    1  绪论 1.1 课题的研究背景 随着IT行业的不断发展,软件开发的复杂度也随着不断提高.与此同时,软件的开发团队也越来越庞大,而如何更好地协同整个团队进行高效准确的工作,从而确保软件开发的质量已经 ...

  4. 洛谷1303 A*B Problem 解题报告

    洛谷1303 A*B Problem 本题地址:http://www.luogu.org/problem/show?pid=1303 题目描述 求两数的积. 输入输出格式 输入格式: 两个数 输出格式 ...

  5. 模板 - n个数的乘法逆元

    这道题里面不用保存 inva[i] ,而且还卡常.事实证明快读快到飞起, #include<bits/stdc++.h> using namespace std; typedef long ...

  6. 洛谷P3292 [SCOI2016]幸运数字(倍增+线性基)

    传送门 不知道线性基是什么东西的可以看看蒟蒻的总结 第一眼:这不会是个倍增LCA暴力合并线性基吧…… 打了一发……A了? 所以这真的是个暴力倍增LCA合并线性基么…… ps:据某大佬说其实可以离线之后 ...

  7. [HNOI2010] 物品调度 fsk

    标签:链表+数论知识. 题解: 对于这道题,其实就是两个问题的拼凑,我们分开来看. 首先要求xi与yi.这个可以发现,x每增加1,则pos增加d:y每增加1,则pos增加1.然后,我们把x与y分别写在 ...

  8. mysql引擎问题研究

    mysql引擎问题研究 数据库引擎 缺省情况下,MYSQL支持三个引擎:ISAM,MYISAM和HEAP.还存在MYSQL+API的引擎例如InnoDB. 数据库引擎特点 ISAM:执行读取操作速度很 ...

  9. 关于Dictionary的优化用法

    今天突然想到了解一下Dictionary,于是在博客园上看到了一篇关于用TryGetValue的文章,原来用TryGetValue要比用ContainsKey更快,快一倍.

  10. Nginx 405 not allowed最简单快速解决办法

    Apache.IIS.Nginx等绝大多数web服务器,都不允许静态文件响应POST请求,否则会返回“HTTP/1.1 405 Method not allowed”错误. server { list ...