Linux OpenGL 实践篇-13-geometryshader
几何着色器
几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段。它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的输出;相对应的几何着色器的输出也是完整的图元信息。所以简单的理解几何着色器就是一个我们可以对图元信息再次修改的阶段。
这个修改可以体现在两个方面,一个是图元的数量,一个是图元的类型。即我们可以输入一个三角形,然后输出两个甚至更多的三角形,当然也可以不输出三角形;而图元类型改变则可理解为输入如果是三角形,输入可变为点或线的其它图元类型。
什么都不做
首先我们先看一个最简单的例子,这个几何着色器什么都不做,即不输出图元。当然,这样的着色器不具备实用价值。
#version core layout(points) in;
layout(points, max_vertices=) out; void main()
{ }
这个例子很简单,main函数是主过程,我们留空,表示什么都不做。接下来,我们聚焦几何着色器的关键点。第一就是“layout(points) in”这行代码,表示的是几何着色器的输入图元类型,即几何着色器逐图元执行的(顶点着色器逐顶点,片元着色器逐片元);这个图元类型要与程序绘制时指定的图元类型兼容,如points对应GL_POINTS,line对应GL_LINES,triangles对应GL_TRIANGLES;第二行声明几何着色器输出图元类型是点,输出的最大顶点数是1。注意的是在几何着色器中,输出的图元类型只能是点、多线段条带和三角形条带,不能输出独立的线段或三角形,也不能循环线或三角形扇面。这是因为条带可以视为一个独立图元类型的的一个超集,即一个独立的三角形或线段就是一个图元的条带而已。我们如果绘制一个三角形就立即结束条带,那就相当于在绘制一个独立的三角形。
直接往后传递
下面是把输入数据原封不动的往下一个阶段传递。
#version core layout(points) in;
layout(points, max_vertices=) out; out vec4 fColor; void main()
{
int n; for(n =; n < gl_in.length();n++)
{
gl_Position = gl_in[n].gl_Position;
EmitVertex();
} EndPrimitive();
}
gl_PrimitiveIDIn
gl_PrimiiveIDIn是一个几何着色器阶段的glsl的内置变量,可以理解为图元的唯一标识符。它是一个整形变量,从0开始,与它对应的是片元着色器的输入gl_PrimitiveID。如果片元着色的gl_PrimitiveID是有效的,并且几何着色器也是有效的,则几何着色器必须对gl_PrimitiveID进行赋值。下面是一个只渲染序号为奇数的图元几何着色器例子。
#version core layout(points) in;
layout(points, max_vertices=) out; void main()
{
if((gl_PrimitiveIDIn & ) == )
{
int n; for(n =; n < gl_in.length();n++)
{
gl_Position = gl_in[n].gl_Position;
EmitVertex();
} EndPrimitive();
} }
gl_layer
gl_Layer是几何着色器的一个变量,用于实现分层渲染。这个分层渲染通常是指渲染到帧缓存对象,而帧缓存的附件通常是一个二维数组纹理或者cube map,所以这个层可理解为二维数组纹理的一个片或者cube map的一个面。分层渲染即在几何着色器中实现对每一层分别进行不同的渲染。下面我们看一个分层渲染的例子:
#version core layout(triangles) in;
layout(triangle_strip, max_vertices=) out; in VS_GS_VERTEX
{
vec4 color;
}vertex_in[]; out GS_FS_VERTEX
{
vec4 color;
}vertex_out; uniform mat4 proj;
uniform int output_slices; void main()
{
for(int j =; j < output_slices;++j)
{
gl_Layer = j;
for(int i = ; i < gl_in.length(); ++i)
{
gl_Position = proj * gl_in[i].gl_Position;
vertex_out.color = vertex_in[i].color;
EmitVertex();
}
EndPrimitive();
}
}
在这个几何着色器中,我们通过程序设置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,然后再在对应的视口中完成几何体的渲染。
下面我们通过例子介绍第二种方式的实现,第一种方式可类推。
几何着色器:
#version core layout(triangles,invocations = ) in ;
layout(triangle_strip, max_vertices=) out; uniform mat4 model_matrix[];
uniform mat4 proj; out vec4 gs_color; const vec4 colors[] = vec4[]
(
vec4(1.0,0.7,0.3,1.0),
vec4(1.0,0.2,0.3,1.0),
vec4(0.1,0.6,1.0,1.0),
vec4(0.3,0.7,0.5,1.0)
); void main()
{
for(int i=;i<gl_in.length();++i)
{
gl_ViewportIndex = gl_InvocationID;
gs_color = colors[gl_InvocationID]; //gs_normal = (model_matrix[gl_InvocationID] * vec4(vs_normal[i],0.0)).xyz;
gl_Position = proj * (model_matrix[gl_InvocationID] * gl_in[i].gl_Position);
EmitVertex();
}
}
在这个几何着色器中,重点是invocations和gl_ViewportIndex的设置。invocations表示图元处理请求的数量,简单的理解就是对这个图元处理几次,在这里我们处理4次,其中一个实例化相关的内置变量是gl_InvocationID,表示几何着色器请求(Invocation)分配的调用值(Invocation),也可以简单的理解为第几次调用,通过gl_Invocation来给gl_ViewportIndex赋值表示渲染到哪个视口,而model_matrix是视口渲染所用矩阵,所以这个几何着色器的流程就是先设置视口索引,然后使用该视口的矩阵变换顶点,输出的图元。结果就是在事先设置的视口中按给定的矩阵渲染场景,即实现了多视口渲染。
其中视口参数设置:
void Reshape(int width, int height)
{
const float wot = float(width) * 0.5f;
const float hot = float(height) * 0.5f; glViewportIndexedf(,0.0f,0.0f,wot,hot);
glViewportIndexedf(,wot,0.0f,wot,hot);
glViewportIndexedf(,0.0f,hot,wot,hot);
glViewportIndexedf(,wot,hot,wot,hot);
}
顶点着色器和片元着色器:
#version core layout(location=) in vec3 iPos; //uniform mat4 model;
//uniform mat4 view;
//uniform mat4 proj; void main()
{
gl_Position = vec4(iPos,1.0);
}
片元着色器:
#version core in vec4 gs_color;
out vec4 color; void main()
{
color = gs_color;
}
Linux OpenGL 实践篇-13-geometryshader的更多相关文章
- Linux OpenGL 实践篇-6 光照
经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反 ...
- Linux OpenGL 实践篇-5 纹理
纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...
- Linux OpenGL 实践篇-4 坐标系统
OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...
- Linux OpenGL 实践篇-3 绘制三角形
本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...
- Linux OpenGL 实践篇-2 创建一个窗口
OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...
- Linux OpenGL 实践篇-1 OpenGL环境搭建
本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...
- Linux OpenGL 实践篇-16 文本绘制
文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...
- Linux OpenGL 实践篇-15-图像数据操作
OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...
- Linux OpenGL 实践篇-14-多实例渲染
多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...
随机推荐
- Mertens
题意: 求解$\sum_{i=a}^b{\mu(i)}$. 解法: 由$(\mu * I)(n) = e(n)$ 得 $\sum_{d|n}{\mu(d)} = [n=1]$ 得 $\mu(n) = ...
- python之log日志模块
logging的配置大致有下面几种方式. 1. 通过代码进行完整配置,logging.getLogger()获取logger后,给logger设置各种handler. 2. ...
- 使用SQL访问MongoDB
使用SQL访问MongoDB 简介 使用SQL访问MongoDB有多种解决方案,就我所知的,除了今天要介绍的MongoDB Connector for BI外,还有Studio 3T,但后者只有在企业 ...
- Cannot set headers after they are sent to the client
D:\le\node_modules\mysql\lib\protocol\Parser.js: throw err; // Rethrow non-MySQL errors ^ Error [ERR ...
- MVC 基本工具(Visual Studio 的单元测试、使用Moq)
3.Visual Studio 的单元测试 有很多.NET单元测试包,其中很多是开源和免费的.本文打算使用 Visual Studio 附带的内建单元测试支持,但其他一些.NET单元测试包也是可用的. ...
- 如何解决启动Error:com.intellij.util.indexing.StorageException问题?
启动tomcat时idea出现如下错误: Error:com.intellij.util.indexing.StorageException: com.intellij.util.indexing.S ...
- PyCharm的一些使用技巧
定位到函数定义 在函数名处 Ctrl + B 就会快速定位到函数定义处 在Console中执行文件 全选内容后,右键菜单 Execute Selection in Console 或者快捷键 Alt ...
- Codevs 1425 最长公共子串
1425 最长公共子串 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 青铜 Bronze 题解 题目描述 Description 输入N(2<=N<= ...
- 实现easyui combobox中textField字段的拼接
开发过程中遇到这样的一个需求: 从后台得到的两个字段aa.bb拼接为一个字段aabb显示在easyui combobx的下拉选项中. 实现方法: 利用formatter属性定义如何呈现行: 页面代码: ...
- 浅谈H5技术
1.什么是H5:HTML5将成为HTML.XHTML以及HTML DOM的新标准.目前仍处于完善之中.然而,大部分现代浏览器已经具备了某些HTML5支持. 2.背景:HTML5 是 W3C 与 WH ...