如何编写着色器小程序?
需要用到一门新的语言,GLSL,它是类似C风格的语言。
GLSL全称为OpenGL Shading Language,是为了实现着色器的功能
而向开发人员提供的一种开发语言,对其只要能理解到这个层次就可以
了。
GLSL语法与内建函数
- 修饰符
float? 浮点型
vec2? 含两个浮点型数据的向量,相当于一个数组
vec4? 含四个浮点型数据的向量(xyzw,rgba,stpq)
sampler2D? 2D纹理采样器(代表一层纹理)
修饰符
- const:用于声明非可写的编译时常量变量。
- attribute:属性变量。用于经常更改的信息,只能用于顶点着色器中。 一般用该变量来表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。
- uniforms:一致变量。用于不经常更改的信息,可用于顶点着色器和片元着色器。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。
- varying:用于修饰从顶点着色器向片元着色器传递的变量。
基本数据类型
int、float、bool,这些与C语言都是一致的,需要强调的一点就是,这里面的float是有一个修饰符的,即可以指定精度。三种修饰符的范围(范围一般视显卡而定)和应用情况具体如下。
- highp:32bit,一般用于顶点坐标(vertex Coordinate)。
- medium:16bit,一般用于纹理坐标(texture Coordinate)。
- lowp:8bit,一般用于颜色表示(color)。
向量类型
向量类型是Shader中非常重要的一个数据类型,因为在做数据传递的时候需要经常传递多个参数,相较于写多个基本数据类型,使用向量类型是非常好的选择。列举一个最经典的例子,要将物体坐标和纹理坐标传递到Vertex Shader中,用的就是向量类型,每一个顶点都是一个四维向量,在Vertex Shader中利用这两个四维向量即可完成自己的纹理坐标映射操作。
声明方式如下(GLSL代码)
attribute vec4 position;
矩阵类型
矩阵类型在Shader的语法中也是一个非常重要的类型,有一些效果器需要开发者传入矩阵类型的数据,比如后面会接触到的怀旧效果器,就需要传入一个矩阵来改变原始的像素数据。声明方
式如下(GLSL代码):
uniform lowp mat4 colorMatrix;
上面的代码表示了一个4×4的浮点矩阵,如果是mat2就是2×2的浮点矩阵,如果是mat3就是3×3的浮点矩阵。若要传递一个矩阵到实际的Shader中,则可以直接调用如下函数(客户端代码):
glUniformMatrix4fv(mColorMatrixLocation, 1, false, mColorMatrix,0);
纹理类型
一般仅在Fragment Shader中使用这个类型,二维纹理的声明方式如下(GLSL代码):
uniform sampler2D texSampler;
当客户端接收到上面这个句柄时,就可以为它绑定一个纹理,代码如下(客户端代码):
//激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 图像数据
// 正常:GLES20.GL_TEXTURE_2D
// surfaceTexure的纹理需要
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
//传递参数 0:需要和纹理层GL_TEXTURE0对应
GLES20.glUniform1i(vTexture,0);
注意上述代码中第一行激活的是哪一个纹理句柄,第三行代码中的第二个参数需要传递对应的Index,就像代码中激活的纹理句柄是GL_TEXTURE0,对应的Index就是0,如果激活的纹理句柄是GL_TEXTURE1,那么对应的Index就是1,在不同的平台上句柄的个数也不一样,但是一般都会在32个以上。
特殊的传递类型
在GLSL中有一个特殊的修饰符就是varying,这个修饰符修饰的变量均用于在Vertex Shader和
Fragment Shader之间传递参数。首先在顶点着色器中声明这个类型的变量代表纹理的坐标点,并且对这个变量进行赋值,代码如下:
//传给片元着色器 像素点
varying vec2 aCoord;
void main(){
//内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
gl_Position = vPosition;
// 经过测试 和设备有关, 计算顶点坐标
aCoord = (vMatix * vCoord).xy;
}
紧接着在Fragment Shader中也声明同名的变量,然后使用texture2D方法取出二维纹理中该纹理坐标点上的纹理像素值,代码如下(GLSL代码):
//采样点的坐标
varying vec2 aCoord;
//采样器
uniform samplerExternalOES vTexture;
void main(){
//变量 接收像素值
// texture2D:采样器 采集 aCoord的像素
//赋值给 gl_FragColor 就可以了
gl_FragColor = texture2D(vTexture,aCoord);
}
取出了该坐标点上的像素值之后,就可以进行像素变化操作了,比如说提高对比度,最终将改变的像素值赋值给gl_FragColor。
GLSL的内置函数与内置变量
首先来看内置变量,最常见的是两个Shader的输出变量。先来看Vertex Shader的内置变(GLSL代码):
原型
vec4 gl_position;
使用
// 把顶点坐标给这个变量, 确定要画画的形状
attribute vec4 vPosition;
void main(){
//内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
gl_Position = vPosition;
}
上述代码用来设置顶点转换到屏幕坐标的位置,Vertex Shader一定要去更新这个数值。另外还有一个内置变量,代码如下(GLSL代码):
原型
float gl_pointSize;
其次是Fragment Shader的内置变量,代码如下(GLSL代码):
原型
vec4 gl_FragColor;
上述代码用于指定当前纹理坐标所代表的像素点的最终颜色值。
然后是内置函数,具体的函数可以去官方文档中查询,这里仅介绍几个常用的函数。
- abs(genType x):绝对值函数。
- floor(genType x):向下取整函数。
- ceil(genType x):向上取整函数。
- mod(genType x,genType y):取模函数。
- min(genType x,genType y):取得最小值函数。
- max(genType x,genType y):取得最大值函数。
- clamp(genType x,genType y,genType z):取得中间值函数。
- step(genType edge,genType x):如果x
- smoothstep(genType edge0,genType edge1,genType x):如果x≤edge0,则返回0.0;如果x≥edge1,则返回1.0;如果edge0
- mix(genType x,genType y,genType a):返回线性混合的x和y,用公式表示为:x(1-a)+ya,这个函数在mix两个纹理图像的时候非常有用。
其他的角度函数、指数函数、几何函数在这里就不再赘述了,可以去官方文档进行查询。
对于一个语言的语法来讲,剩下的就是控制流部分了,而GLSL的控制流与C语言非常类似,既可以使用for、while以及do-while实现循环,也可以使用if和if-else进行条件分支的操作。
GLSL的常用语法部分已经讲解得差不多了,毕竟程序(Shader)都是运行在GPU上的,那么在CPU上运行的程序(应用程序)应该如何将这一组Shader交给OpenGL ES的渲染管线呢?下面就来介绍如何在应用程序中使用Shader。