Shader其实就是一段执行在GPU上的程序,此程序使用OpenGL ES SL语言来编写。它是一个描述顶点或像素特性的简单程序。在opengles中常用的shader有两种:vertex shader和fragment shader。Geometry Shader(几何着色器)是继Vertex Shader和Fragment Shader之后,由Shader Model 4引入的新的着色器。还有一个compute Shader由Shader Model 5引入的提供通用计算能力的着色器。
1.1 Vertex Shader
对于发送给GPU的每一个Vertex(顶点),都要执行一次Vertex Shader。其功能是把每个顶点在虚拟空间中的三维坐标变换为可以在屏幕上显示的二维坐标,并带有用于z-buffer的深度信息。Vertex Shader可以操作的属性有:位置、颜色、纹理坐标,但是不能创建新的顶点。
vertex shader主要完成以下工作:1).基于点操作的矩阵乘法位置变换;2).根据光照公式计算每点的color值;3).生成或者转换纹理坐标。
Vertex Shader输入数据如下:
1).Attributes:由 vertext array 提供的顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,它是针对每一个顶点的数据。属性只在顶点着色器中才有,片元着色器中没有属性。属性可以理解为针对每一个顶点的输入数据。OpenGL ES 2.0 规定了所有实现应该支持的最大属性个数不能少于 8 个。
注:Vertex Attributes 是每点的属性数据。与一个index序号绑定。外部程序可通过 glBindAttribLocation将一个attribute 名与一个index绑定起来。当然,OPENGL ES 内部会自动绑定所有attributes.外部程序只需通过 glGetAttribLocation获取指定attribute名的index。 给Attribute传值可以通过 glVertexAttribPointer函数或者glVertexAttrib4fv
2).Uniforms:uniforms保存由应用程序传递给着色器的只读常量数据。在顶点着色器中,这些数据通常是变换矩阵,光照参数,颜色等。由 uniform 修饰符修饰的变量属于全局变量,该全局性对顶点着色器与片元着色器均可见,也就是说,这两个着色器如果被连接到同一 个program Object,则它们共享同一份 uniform 全局变量集。因此如果在这两个着色器中都声明了同名的 uniform 变量,要保证这对同名变量完全相同:同名+同类型,因为它们实际是同一个变量。此外,uniform 变量存储在常量存储区,因此限制了 uniform 变量的个数,OpenGL ES 2.0 也规定了所有实现应该支持的最大顶点着色器 uniform 变量个数不能少于 128 个,最大的片元着色器 uniform 变量个数不能少于 16 个。
3).Samplers:一种特殊的 uniform,在vertex shader中是可选的,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
4).Shader program:由 main 声明的一段程序源码,描述在顶点上执行的操作:如坐标变换,计算光照公式来产生 per-vertex 颜色或计算纹理坐标。
Vertex Shader输出为:
1).Varying:varying 变量用于存储顶点着色器的输出数据,当然也存储片元着色器的输入数据,varying 变量最终会在光栅化处理阶段被线性插值。顶点着色器如果声明了 varying 变量,它必须被传递到片元着色器中才能进一步传递到下一阶段,因此顶点着色器中声明的 varying 变量都应在片元着色器中重新声明同名同类型的 varying 变量。OpenGL ES 2.0 也规定了所有实现应该支持的最大 varying 变量个数不能少于 8 个。
2).在顶点着色器阶段至少应输出位置信息-即内建变量:gl_Position,是每个点固有的Varying,表示点的空间位置。其它两个可选的变量为:gl_FrontFacing 和 gl_PointSize。
1.2 Fragment Shader
Pixel Shader(像素着色器)就是众所周知的Fragment Shader(片元着色器),它计算每个像素的颜色和其它属性。它通过应用光照值、凹凸贴图,阴影,镜面高光,半透明等处理来计算像素的颜色并输出。它也可改变像素的深度(z-buffering)或在多个渲染目标被激活的状态下输出多种颜色。一个Pixel Shader不能产生复杂的效果,因为它只在一个像素上进行操作,而不知道场景的几何形状。
Fragment Shader输入数据如下:
1).Varyings:这个在前面已经讲过了,顶点着色器阶段输出的 varying 变量在光栅化阶段被线性插值计算之后输出到片元着色器中作为它的输入,即上图中的 gl_FragCoord,gl_FrontFacing 和 gl_PointCoord。OpenGL ES 2.0 也规定了所有实现应该支持的最大 varying 变量个数不能少于 8 个。
2).Uniforms:前面也已经讲过,这里是用于片元着色器的常量,如雾化参数,纹理参数等;OpenGL ES 2.0 也规定了所有实现应该支持的最大的片元着色器 uniform 变量个数不能少于 16 个。
3).Samples:一种特殊的 uniform,用于呈现纹理。
4).Shader program:由 main 申明的一段程序源码,描述在片元上执行的操作。
FragmentShader输出为:
在顶点着色器阶段只有唯一的 varying 输出变量-即内建变量:gl_FragColor
1.3 Geometry Shader
Geometry Shader(几何着色器)是继Vertex Shader和Fragment Shader之后,由Shader Model 4(第四代显卡着色架构)正式引入的第三个着色器。它在Driect3D 10和OpenGL 3.2中开始引入,在OpengGL 2.0+中作为扩展使用,在OpenGL3.x中也成为核心。它的输入为:点、线和三角形;其输出为点、线带和三角形带。
在Geometry Shader里,我们处理的单元是Primative。虽然根本上都是顶点的处理,但进入vertex shader里的是一次一个的顶点,而进入Geometry Shader的是一次一批的顶点,Geometry Shader掌握着这些顶点所组成的图元的信息。Geometry Shader的处理阶段处于流水线的栅格化之前,也在视锥体裁剪和裁剪空间坐标归一化之前。虽说裁剪过程会剔除部分图元也会分割某些图元, 但就目前来说,不会有其他流水线的可编程阶段会在Geometry Shader之后提供出影响图元的性质(形式和数量)——这是Geometry Shader鉴于其位置的特殊性而拥有的一个重要特点。
Geometry Shader程序在Vertex Shader程序执行之后执行。
2.顶点着色与片元着色在编程上的差异
1).精度上的差异
着色语言定了三种级别的精度:lowp, mediump, highp。我们可以在 glsl 脚本文件的开头定义默认的精度。如下代码定义在 float 类型默认使用 highp 级别的精度
precision highp float;
在顶点着色阶段,如果没有用户自定义的默认精度,那么 int 和 float 都默认为 highp 级别;而在片元着色阶段,如果没有用户自定义的默认精度,那么就真的没有默认精度了,我们必须在每个变量前放置精度描述符。此外,OpenGL ES 2.0 标准也没有强制要求所有实现在片元阶段都支持 highp 精度的。我们可以通过查看是否定义 GL_FRAGMENT_PRECISION_HIGH 来判断具体实现是否在片元着色器阶段支持 highp 精度,从而编写出可移植的代码。当然,通常我们不需要在片元着色器阶段使用 highp 级别的精度,推荐的做法是先使用 mediump 级别的精度,只有在效果不够好的情况下再考虑 highp 精度。
2).attribute 修饰符只可用于顶点着色。这个前面已经说过了。
3).或由于精度的不同,或因为编译优化的原因,在顶点着色和片元着色阶段同样的计算可能会得到不同的结果,这会导致一些问题(z-fighting)。因此 glsl 引入了 invariant 修饰符来修饰在两个着色阶段的同一变量,确保同样的计算会得到相同的值。