在OpenGL ES图形学中,着色语言(Shading Language)是一门必修课。在看openGL代码时,都会有着色语言的身影,它有自己的一套语法和格式。提供给我们进行编程的包括顶点着色器和片元着色器,它们都遵循着色语言语法,核心概念包括:向量、矩阵、变量、常量、运算符、采样器、布局限定符、精度限定符、插值限定符、输入输出限定符等。
在顶点着色器中,我们一般看到attribute修饰符,称为属性变量;还有varying修饰符,称为易变变量,连接顶点着色器与片元着色器的桥梁。在片元着色器中,我们会看到uniform修饰符,称为统一变量。接下来,我们详细探讨下着色语言的相关概念。
1、向量
向量vec,分为浮点向量、整数向量、无符号向量、布尔向量四种,默认为浮点向量。向量又分为二维向量vec2、三维向量vec3、四维向量vec4。向量的单独分量可以用两种方式访问:使用“.”运算符或者通过数组下标。根据组成向量的分量数量,每个分量可以通过使用向量坐标{x, y, z, w}、颜色分量{r, g, b, a}、纹理坐标{s, t, p, q}三种方式访问。如下表格是常见的向量类型:
浮点向量 | vec2 | vec3 | vec4 |
整数向量 | ivec2 | ivec3 | ivec4 |
无符号向量 | uvec2 | uvec3 | uvec4 |
布尔向量 | bvec2 | bvec3 | bvec4 |
下面是几个例子,分别通过向量坐标、颜色分量、纹理坐标进行向量的访问:
vec4 mVec = vec(1.0, 1.0, 1.0, 1.0);
vec4 cVec = mVec.rgba;
vec4 tVec = mVec.stpq;
vec4 nVec = mVec.xyzw;
2、矩阵
矩阵mat,和向量的维度类似,分为二维矩阵mat2、三维矩阵mat3、四维矩阵mat4。不同的是,矩阵有多行多列,用m*n表示,而向量只有一列或者一行。矩阵可以进行平移、旋转、缩放等操作。
3、变量
上面提及的向量和矩阵一般声明为变量,而且变量前面加上attribute、varying、uniform等修饰符。其中attribute是属性变量,varying是易变变量,uniform是统一变量。openGL提供对应的API去加载attribute变量和uniform变量:
glGetAttribLocation(int program, String name);//加载attribute变量
glGetUniformLocation(int program, String name);//加载uniform变量
4、常量
在openGL定义常量需要加上const修饰符,必须在声明时进行初始化。例如:
const float pi = 3.1415926;
5、运算符
openGL着色语言的运算符与C语言一样。但是,着色语言对运算符有着严格的类型规则。运算符只能出现在有相同基本类型的变量之间,对于二元运算符加减乘除,变量的类型必须是float或者int。除了==和!=之外,比较运算符(<, <=, >, >=)只能用于标量。下面列举着色语言常用的运算符:
* | 乘 | ++,-- | 递增/递减 |
/ | 除 | >,==,< | 比较运算符 |
+ | 加 | && | 逻辑与 |
- | 减 | || | 逻辑或 |
= | 赋值 | <<,>> | 移位 |
+=,-=,/=,*= | 算术赋值 | &,^,| | 按位运算 |
6、采样器
采样器一般分为二维sampler2D和三维sampler3D,用于片元着色器中:
uniform sampler2D uTexture;
void main() {
gl_FragColor = texture2D(uTexture, fCoord);
}
7、精度限定符
精度限定符是用来指定着色器变量的计算精度。它可以用于指定任何基于float或int变量的精度。精度分为三种:低精度、中等精度和高精度,分别使用lowp、mediump、highp修饰符,并且前面要加上precision。示例代码如下:
precision mediump float;//中等精度
precision highp float;//高精度
precision lowp int;//低精度
8、插值限定符
在没有限定符时,openGL默认的插值 行为是执行平滑着色。也就是说,来自顶点着色器的输出变量在图元中线性插值,片元着色器接收线性插值后的数值作为输入。我们也可以明确地请求平滑着色,如下所示:
//Vertex shader output
smooth out vec3 v_color;
//Input form vertex shader
smooth in vec3 v_color;
openGL ES 3.0还引入另一种插值——平面着色。在平面着色中,图元中的值没有进行插值,而是将其中一个顶点视为驱动顶点,该顶点的值被用于图元的所有片段。平面着色输出/输入如下:
//Vertex shader output
flat out vec3 v_color;
//Input form vertex shader
flat in vec3 v_color;
9、输入输出限定符
输入输出限定符是用于限定函数是否可以修改可变参数,类似Android的AIDL输入输出修饰符。限定符分为三种:in、out、inout。如下表格所示:
限定符 | 描述 |
in | 指定参数按值传递,函数不能修改 |
out | 该变量不被传入函数,但在函数返回时被修改 |
inout | 该变量按引用传入,可以被函数修改 |
10、扩展库
openGL着色语言允许使用扩展库,使用extension关键字进行声明。比如,使用SurfaceTexture进行渲染需要用到GL_OES_EGL_image_external库,示例代码如下:
#extension GL_OES_EGL_image_external : require
//using externalOES sampler
uniform samplerExternalOES vTexture;
11、不变性
openGL ES着色语言引入invariant关键字修饰顶点着色器变量,也就是不变性。与java的volatile关键字类似,禁止编译器指令重排序。因为编译器在编译期间可能对指令进行重排序优化,导致指令执行顺序发生改变。如果用于计算输出位置的数值精度不一样,精度差异会导致伪像,表现为“深度冲突”(Z fighting),每个像素的Z(深度)精度差异导致多遍着色互相之间的微小偏移。
结合上面概念,我们就容易编写出顶点着色器和片元着色器了。示例代码如下:
//Vertex shader
attribute vec4 vPosition;
attribute vec2 vCoord;
varying vec2 fCoord;
void main() {
gl_Position = vPosition;
fCoord = vCoord;
}
//Fragment shader
precision mediump float;
varying vec2 fCoord;
uniform sampler2D vTexture;
void main() {
gl_FragColor = texture2D(vTexture, fCoord);
}
参考书本: