掌握这些概念,不用担心看不懂OpenGL ES着色语言了

在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。除了==和!=之外,比较运算符(<, <=, >, >=)只能用于标量。下面列举着色语言常用的运算符:

openGL ES着色语言运算符
* ++,-- 递增/递减
/ >,==,< 比较运算符
+ && 逻辑与
- || 逻辑或
= 赋值 <<,>> 移位
+=,-=,/=,*= 算术赋值 &,^,| 按位运算

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。如下表格所示:

openGL ES着色语言输入输出限定符
限定符 描述
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);
}

参考书本:

你可能感兴趣的:(OpenGL)