WebGL - GLSL_ES 着色器语言

1、概述

着色器语言和C/C++很类似,拥有C语言的语法规范,还有C++的结构类型,GLSL ES语言是在OpenGL的着色器语言GLSL基础上,删减一部分后形成的,GLSL ES的目标平台是电子产品和嵌入式设备。

着色器语言开头必须有一个入口main()函数;

2、数据类型

支持两种数据类型

  • 数值类型:整数、浮点数,没有小数点的是整数,有的则是浮点数;
  • 布尔值类型truefalse

注意:GLSL ES不支持字符串类型

3、变量声明

GLSL ES是强类型语言,因此声明变量时需要指定类型;

<类型><变量名>

如:vec4 a_Position;

因此,在定义和使用变量时需要时刻注意变量的类型;

4、基本类型

  • float:单精度浮点数类型,该类型的变量表示一个单精度浮点数
  • int:整形术,该类型的变量表示一个整数
  • bool:布尔值,truefalse

5、矢量和矩阵

GLSL ES支持矢量和矩阵类型,这两种类型很适合处理计算机图形,矢量和矩阵类型的变量都包含多个元素,每个元素是一个数值(整型数、浮点数或布尔值);

类别 数据类型 描述
矢量 vec2\vec3\vec4,ivec2\ivec3\ivec4,bvec2\bvec3\bvec4 分别具有2,3,4个浮点、整形、布尔值元素的矢量
矩阵 mat2\mat3\mat4 2*2,3*3,4*4的浮点数元素的矩阵

1、赋值和构造

使用=来对矢量和矩阵进行赋值操作,赋值运算符左右两边的变量和数据类型必须要一直,并且元素的个数也必须相同。

通常使用与数据类型同名的内置构造函数来生成变量,如下:

vec4 position = vec4(1.0, 2.0, 3.0, 1.0);

这种专门创建指定类型的变量的函数被称为 构造函数,构造函数的名称和其创建的变量的类型名称总是一致的。

2、访问元素

可以通过点号.或者[]运算符,来访问矩阵和矢量中的元素;

分两名

类别 描述
x,y,z,w 用来获取顶点坐标分量
r,g,b,a 用来获取颜色的分量
s,t,p,q 用来获取纹理坐标分量

例如访问矢量的元素

vec3 v3 = vec3(1.0, 2.0, 3.0);

float f = 0.1;

f = v3.x;//f被赋值为`1.0`

还可以使用[]运算符来访问元素

vec3 v3 = vec3(1.0, 2.0, 1.5);
float f = v3[0];//将1.0赋值给f

[]中只能出现的索引值必须是 常量索引值

常量索引值定义如下

  • 整型字面量
  • const修饰的全局变量或局部变量
  • 循环索引值
  • 由前述三条中的项组成的表达式

注意:不能使用未经 const修饰的变量作为索引值,因为它不是一个常量索引值(除非它是循环索引)

如下是错误的:

int index = 0;
vec4 v4c = m4[index];//错误:inde 不是常量索引值

1、混合

将同一个集合的多个分量名共同置于点运算符后,就可以从矢量中同时抽取出多个分量,这个过程称作 混合

vec3 position = vec3(1.0, 1.5, 2.0);//声明一个矢量
vec2 v2;
v2 = v3.xy;//将v2设置为(1.0, 1.0)
v2 = v3.yz;

3、运算符

对于矢量和矩阵所支持的运算,和基本类型的运算类似

注意:对于矢量和矩阵的比较,只可以使用比较运算符中的==!=

如果需要比较矢量和矩阵的大小,应该使用内置函数,比如lessThan()

矢量和矩阵可用的运算符

运算符 运算 使用数据类型
+、-、*、/ 加减乘除 适用于vec[234]mat[234]
++、– 自增自减 适用于vec[234]mat[234]
= 赋值运算 同上
+=、-=、*=、/= 运算赋值 同上
==、!= 比较 对于==如果两个操作数的每一个分量都相同,那么返回true,对于!=如果两个操作数的任何一个分量不同则返回true

当运算赋值作用于,矢量或者矩阵时,实际上时逐分量的对矩阵或矢量的每一个元素进行独立的运算赋值;

下面是一些矢量和矩阵在运算时的常见情形

// 假如定义以下变量
vec3 v3a, v3b, v3c;
mat3 m3a, m3b, m3c;
float f;

1、矢量和浮点数的运算

// 加减乘除操作符效果一样
v3b = v3a + f;  // 相当于 
				// v3b.x = v3a.x + f;
				// v3b.y = v3a.y + f;
				// v3b.z = v3a.z + f;
// 例如 v3a = vec3(1.0, 2.0, 3.0); f = 1.0,那么 v3b = (2.0, 3.0, 4.0);

2、矢量运算

//  矢量运算操作发生在矢量的每个分量上
// 加减乘除效果一样
v3c = v3a + v3b;//相当于
				// v3a.x + v3b.x;
				// v3a.y + v3b.y;
				// v3a.z + v3b.z;
// 例如 v3a = vec3(1.0, 2.0, 3.0); v3b = (2.0, 4.0, 6.0),那么结果v3c = (3.0, 6.0, 9.0)

3、矩阵和浮点数的运算

矩阵与浮点数的运算发生在矩阵的每个分量上

// 加减乘除效果类似
m3b = m3a * f;
// 相当于把矩阵,m3b[0].x = m3a[0].x * f;每个分量都乘以f
1、矩阵右乘矢量

矩阵右成矢量的结果是 矢量 其中每个分量都是原矢量中对应分量,乘上矩阵对应行的每个元素的积的加和;

2、矩阵左乘矢量

矩阵左乘矢量也是可以的,但结果与右乘不一样;

3、矩阵与矩阵相乘
m3c = m3a * m3b;

6、结构体

结构体类似java语言中的类class但又有很大不同,GLSL ES支持用户自定义的类型,使用关键子struct将已存在的类型聚合到一起,就可以定义为 结构体;

例如顶一个灯的结构

struct light { // 定义了结构体类型 light,包含两个成员 color和position
    vec4 color;
    vec3 position;
}

light l1,l2;// 声明了 light 类型的变量 l1和l2

C语言不同的是,不需要使用typedef关键子来定义结构体,因为结构体结构体的名称会自动称为类型;

可以在同一条语句中定义和声明改结构体类型的变量

struct light {   	//定义结构体和定义变量同时进行
    vec4 color; 	//光的颜色
    vec3 position;	//光源的位置
} l1;				//结构体类型的变量l1

1、赋值和构造

结构体有标准的构造函数,其名称与结构体名一致,构造函数的参数顺序必须与结构体定义中的成员顺序一致;

例如给刚才创建的light类型l1赋值

ls = light(vec4(0.0, 0.1, 0.1, 1,0),vec3(2.0, 4.0, 2.0));
// 上面的参数是灯光的颜色和光源的位置 light()是结构体构造函数

2、访问成员

在结构体变量名后跟点运算符,然后再加上成员名就可以访问变量的成员

vec4 color = l1.color;//点号访问结构体成员
vec3 position = l1.position;

3、运算符

结构体的成员可以参与其自身类型支持的任何运算,但是结构体本身只支持两种运算,赋值( = )和比较(==和!=)
当且仅当两个结构体变量所对应的所有成员都相等的时候,==运算符才会返回true,如果任意某个成员不相等那么!=运算符返回true

7、数组

GLSL ES只支持一维数组,声明数组只需要在变量名后加上中括号[]和数组的长度;

1、数组声明

float floatArray[4];//声明含有四个浮点数元素的数组
vec4 vec4Array[2];//声明含有两个vec4对象的数组

数组的长度必须时大于0整型常量表达式,满足条件如下:

  • 整型字面量(如01
  • const限定字符修饰的全局变量或者局部变量,不包括函数参数
  • 由前述两条中的项组成的表达式

因此下面的代码会报错

int size = 4;
vec4 vec4Array[size];//错误,因为变量`size`不是通过`const`修饰的,`const int size = 4;` 使用`const`修饰的值是一个常量值

2、访问元素

访问数组元素可以通过下标索引值来访问,只有整型常量表达式和uniform变量,可以用作数组的索引值

float f = floatArray[1];//索引值0开始

3、数组初始化

JavaScriptC不同,数组不能在声明时被一次性的初始化,而必须显示的对每个元素进行初始化;

vec4 vec4Array[2];
vec4Array[0] = vec4(1.0, 1.0, 1.0, 1.0);
vec4Array[1] = vec4(2.0, 2.0, 2.0, 1.0);

数组本身只支持[]运算,但数组的元素能够参与其自身类型支持的任意运算

//将floatArray的第二个元素乘以 3.14
float f = floatArray[1] * 3.14;

8、取样器

GLSL ES支持的一种内置类型,称为 取样器,我们必须通过该类型变量来访问纹理;

两种基本取样器类型:

  • sampler2D
  • samplerCube

取样器变量只能是 uniform变量,或者需要访问纹理的函数的参数;

uniform sampler2D u_Sampler;

唯一能赋值给取样器变量的就是纹理单元编号,而且必须使用webgl方法gl.uniformli()来进行赋值,如下

gl.uniformli(u_Sampler,0);// 将纹理单元编号0传给着色器

除了=、==和!=取样器不可以作为操作数参与运算;

取样器类型的变量受到着色器支持的纹理单元的最大数量限制,

着色器 表示最大数量的内置常量 最小数量
顶点着色器 const mediump int gl_MaxVertexTextureImageUnits 0
片元着色器 const mediump int gl_MaxTextureImageUnits 8

9、函数

GLSE ES定义函数的方式更接近于C语言

返回类型 函数名(type0 arg0,type1 arg1,...,typen argn){
    函数计算
    return 返回值
}

参数的type必须为本章所述的类型之一,或者像main()函数这样没有参数也是允许的,如果函数不返回值那么返回类型必须时void,也可以将自定义的结构体类型指定为返回值类型,结构体的成员汇总不能有数组;

需要注意的时 不能在一个函数内部调用它本身,递归调用时不允许的,这项限制的目的也是为了便于编译器对函数进行内敛展开

1、规范声明

如果函数定义在其调用之后,那么我们必须在进行调用之前,先声明该函数的规范,规范声明会预先告诉webgl系统的参数,参数类型,返回值等等

float luma(vec4);//规范声明

main(){
    float brightness = luma(color);//luma()在定义之前就被调用了
}

float luma(vec4 color){
    return 0.21 * color.r + 0.71 * color.g + 0.07 * color.b;
}

2、参数限定词

GLSL ES中,可以为函数参数指定限定字,以控制参数的行为。

类别 规则 描述
in 向函数中传入值 参数传入函数,函数内可以使用参数的值,也可
const in 向函数中传入值 参数传入函数,函数内可以使用参数的值,但不能修改
out 在函数中被赋值,并被传出 传入变量的引用,若其在函数内被修改,会影响到外部传入的变量
inout 传入函数,同时在函数中被赋值,并被传出 纯如变量的引用,函数会用到变量的初始值,然后修改变量的值,会影响到函数外部传入的变量
<无:默认> 将一个值传给函数 in一样

10、存储限定字

1、const

  • const修饰的值不能被改变
  • 在声明const变量时,需要将const写在类型之前
  • 声明时必须进行初始化
  • 声明之后不能去改变值

const修饰的变量就是所谓常量,常量的值是不允许被改变的,而且数组的索引也需要时常量值;

2、attribute

  • attribute变量只能出现在顶点着色器中
  • 只能被声明为全局变量
  • 用来表示逐顶点的信息

顶点着色器中,能够被容纳的attribute变量的最大数目于设备有关,可以通过内置的全局常量,来获取最大数目,支持webgl的环境都支持至少8attribute变量;

3、uniform

  • 可以用在顶点着色器和片元着色器中
  • 必须是全局变量
  • uniform变量是只读的,可以是除了数组或结构体之外的任意类型
  • 如果顶点着色器和片元着色器中声明了同名的uniform变量,那么它会被两种着色器共享
  • uniform变量包含了 一致(非逐顶点/逐片元的,各顶点或各片元公用)的数据

变换矩阵就不是逐顶点的,而是所有顶点公用的

4、varying

  • varying变量必须是全局变量
  • 作用是从顶点着色器向片元着色器传输数据
  • 必须在两种着色器中声明同名、同类型的varying变量

顶点着色器中赋给varying变量的值,并不是直接传给片元着色器中的varying变量,这其中发生了光栅化的过程,根据绘制的图形,对前者(顶点着色器varying变量)进行内插,然后再传递给后者(片元着色器中的varying变量),正是因为varying变量需要被内插,所以我们需要限制它的数据类型;

下表是attribute变量、uniform变量和varying变量的数目限制

变量类型 内置全局变量(表示最大数量) 最小值
attribute变量 const mediump int gl_MaxVertexAttribs 8
uniform变量(顶点着色器) const mediump int gl_MaxVertexUniformVectors 128
uniform变量(片元着色器) const mediump int gl_MaxFragmentUniformVEctors 16
varying变量 const mediump int gl_MaxVaryingVectors 8

11、精度限定字

GLSL ES新引入了精度限定字,目的是帮助着色器程序提高运行效率,消减内存开支;

精度限定字:用来表示每种数据具有的精度(比特数)

高精度的程序需要更大的开销更大的开销和更久的计算时间,而低精度的程序开销则小得多;

使用精度限定字,你就能精细的控制程序再效果和性能间的平衡;

精度限定字是可选的,如果不确定,可以使用下面这个适中的默认值

#ifdef GL_ES
precision mediump float;
#endif

webgl支持三种精度,其限定字分别为highp(高精度)、mediump(中精度)和lowp(低精度)

精度限定字 描述
highp 高精度,顶点着色器的最低精度
mediump 中精度,片元着色器的最低精度
lowp 低精度,可以表示所有颜色

注意:在某些webgl环境中,片元着色器可能不支持highp精度,其次数值范围和精度实际上也是于系统环境相关的

声明变量精度的例子

mediump float size;  // 中精度浮点型变量
highp vec4 position; // 具有高精度浮点元素的vec4对象
lowp vec4 color;     // 具有低精度浮点元素的vec4对象

也可以使用关键字precision来声明着色器的默认精度,在顶点或者片元着色器的顶部

precision 精度限定字 类型名称;

例如:

precision mediump float;// 所有浮点数默认为中精度

上面代码表示,在着色器中,某种类型的变量其默认精度由精度限定字限定,也就是说接下来所有不以精度限定字修饰的该类型变量,其精度就是默认精度

对于很多类型着色器已经实现了默认的精度,只有片元着色器中的float类型没有默认精度;

数据类型的默认精度

着色器类型 数据类型 默认精度
顶点着色器 int highp
float highp
sampler2D lowp
samplerCube lowp
片元着色器 int mediump
float
sampler2D lowp
samplerCube lowp

事实上 片元着色器中的float类型没有默认精度,需要我们手动指定,如果我们不再片元着色器中限定float类型的精度,就会导致如下的编译错误;

12、预处理指令

GLSL ES支持预处理指令,用来在真正编译之前对代码进行预处理,都以井号开头;

#ifdef GL_ES
precision mediump float;
#endif

上面代码检查了是否已经定义了GL_ES宏,如果是那就执行#ifdef#endif之间的部分;

1、三种预处理指令

#if 条件表达式
如果条件表达式为真,执行这里
#endif

#ifdef 某宏
如果定义了某宏,则执行这里
#endif

#ifndef 某宏
如果没有定义某宏,执行这里
#endif

2、定义宏

可以使用 #define指令进行宏定义,和C语言不同的是,GLSL ES中的宏没有宏参数;

#define 宏名 宏内容

3、解除宏定义

使用 #undef指令可以解除宏定义
#undef 宏名

4、内置宏

可以使用 #else指令配合 #ifdef实现条件匹配

#define NUM 10
#if NUM == 100
如果宏NUM为100,执行这里
#else
否则,执行这里
#endif

预定义的内置宏

描述
GL_ES OpenGL ES 2.0 中定义为1
GL_FRAGMENT_PRECISION_HIGH 片元着色器支持highp精度

所以可以这样使用宏来进行精度限定

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float// 支持高精度,限定浮点型为高精度
#else
precision mediump float// 不支持高精度,限定浮点型为中精度
#endif
#endif

你可能感兴趣的:(WebGL开发)