GLSL详细的学习;
一、概述
GLSL ES语言大小写敏感,每一条语句都要以分号结尾;
着色器程序必须有且只有一个 main 函数;
注释,单行注释:// ;多行注释: /* 注释内容 */;这个和js一致;
数据值类型:数值(整型、浮点型)和布尔值(true、false)类型;不支持字符串类型;
变量名:字母、数字和下划线,首字母不能是数字,不能以gl_、webgl_或_webgl_开头,不能包含关键字和保留字(这里就不列举了);一般用 “类型缩写+下划线+变量名” 的形式就没什么问题;
GLSL ES 语言是强类型的,用 “类型名 变量名” 来声明变量;
因为是强类型,赋值时数据必须要和变量类型保持一致;数据类型之间可进行转换,内置转换函数如下:
- int(value):value可以是float或bool类型,float类型直接删掉小数部分,bool中,true为1,false为0;
- float(value):value可以是int或bool类型;
- bool(value):value可以是int或float类型,0为false,非0为true;
运算符:+、-、*、/、-(取负)、!、++、–、=、+=、-=、*=、/=、<、>、<=、>=、==、!=、&&、||、^^、condition?exp1:exp2(三元运算);
判断相等是两个等号 ==;++/–支持前缀和后缀;^^是逻辑异或,左右两个表达式中有且仅有一个为true时才返回true;
常量索引值
常量索引值只能包含下面四种情况:
- 整型字面量;
- 用const修饰的全局或局部变量,不包括函数参数;
- 循环索引;
- 上述三条中的项组成的表达式;
二、矢量
1、数据类型
- vec2、vec3、vec4:具有2、3、4个浮点数元素的矢量;
- ivec2、ivec3、ivec4:具有2、3、4个整型数元素的矢量;
- bvec2、bvec3、bvec4:具有2、3、4个布尔值元素的矢量;
2、构造函数
vec3 v3 = vec3(1.0, 0.0, 0.5); // 传和数据类型匹配的分量个数
vec2 v2 = vec2(v3); // 使用v3的前两个分量创建v2
vec4 v4 = vec4(0.5); // 使用0.5设置第一个分量,其他分量设为默认值,(0.5, 1.0, 1.0, 1.0)
vec4 v4b = vec4(v2, v4); // v2中取两个分量,v4中取剩下的两个,组成v4b
第三种情况中,如果传大于1个分量,少于类型所需的分量数,会报错,其余分量不会自动设置成默认值;比如:
vec4 v4c = vec4(0.5, 1.0); // 报错
3、访问元素
3.1、可使用 . 或者 [ ] 运算符来访问,四个分量名称如下:
- x, y, z, w:顶点坐标分量;
- r, g, b, a:颜色分量;
- s, t, p, q:纹理坐标分量;
上面的分量名称只是为了增强程序的可读性,实际上 GLSL ES 的矢量是同时支持三种名称的,即在一个矢量上取 x,r,s,都能取到第一个元素;
3.2、. 取值:在矢量上访问不存在的分量,则会报错
比如下面这段代码
vec2 v2 = vec2(1.0, 0.6);
float f = v2.z; // 报错,v2中没有第三个分量
3.3、. 取值:取值时,可以同时取多个分量,此时多个分量必须属于同一个集合
vec2 v2;
vec3 v3 = vec3(1.0, 2.0, 3.0);
v2 = v3.yx; // (2.0, 1.0)
v2 = v3.xx; // (1.0, 1.0)
v2 = v3.xs; // 报错
vec4 v4 = vec4(1.0, 2.0, 3.0, 4.0);
v4.xw = vec2(5.0, 6.0); // (5.0, 2.0, 3.0, 6.0)
3.4、[ ] 取值
[0] 访问第一个元素;[ ] 中的索引值只能为常量索引值;
vec4 v4 = vec4(1.0, 2.0, 3.0, 4.0);
float f = v4[0]; // 1.0
const int index = 0;
f = v4[index+1]; // 2.0
int index2 = 0;
f = v4[index2]; // 报错
三、矩阵
1、数据类型
mat2、mat3、mat4:2×2、3×3、4×4浮点数元素的矩阵;
2、构造函数
2.1、用基础类型数据来构造矩阵
mat3 mt3 = mat3( 1.0, 2.0, 3.0, // 1.0 4.0 7.0
4.0, 5.0, 6.0, // 2.0 5.0 8.0
7.0, 8.0, 9.0 ); // 3.0 6.0 9.0
传入值的顺序必须是列主序的;上面代码中,构造的矩阵,第一列是 1.0, 2.0, 3.0,第一行是 1.0, 4.0, 7.0;
2.2、用矢量来构造矩阵
// 使用两个 vec2 对象来创建 mat2 对象
vec v2_1 = vec2(1.0, 3.0);
vec v2_2 = vec2(2.0, 4.0);
mat2 mt2_1 = mat2(v2_1, v2_2); /* 1.0 2.0
3.0 4.0 */
// 使用一个 vec4 对象来创建 mat2 对象
vec4 v4 = vec4(1.0, 3.0, 2.0, 4.0);
mat2 mt2_2 = mat(2); /* 1.0 2.0
3.0 4.0 */
2.3、使用矢量和数值来构造矩阵
mat2 mt2_3 = mat2(1.0, 3.0, v2_2); /* 1.0 2.0
3.0 4.0 */
2.4、向构造函数中传入单个数值
mat3 mt3 = mat3(1.0); /* 1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0 */
生成一个对角线是该值的对角矩阵;传大于1个,但是不够构建矩阵的值时,就会报错;
3、访问元素
通过 [ ] 来访问元素,和矢量相同,[ ] 中的索引值只能为常量索引值;
[0] 可以访问第一列元素(构造里的第一行),返回的是一个矢量;
mat3 mt3 = mat3( 1.0, 2.0, 3.0, // 1.0 4.0 7.0
4.0, 5.0, 6.0, // 2.0 5.0 8.0
7.0, 8.0, 9.0 ); // 3.0 6.0 9.0
vec3 v3 = mt3[0]; // (1.0, 2.0, 3.0)
float f1 = mt3[1][1]; // 5.0
float f2 = mt3[2].z; // 9.0
const int index = 0;
vec3 v3_1 = mt3[index + 1]; // 2.0
int index2 = 0;
vec3 v3_2 = mt3[index2]; // 报错
四、矢量、矩阵的运算
矢量和矩阵支持的运算,与基本数据类型很相似,单矢量和矩阵只能使用比较运算符中的 == 和 !=,不能使用 >、<、>=、<=;比较矩阵和矢量的大小,应该使用内置函数,lessThan()、equal()、notEqual() 等;
下面的例子中,假设变量是这样定义的:
vec3 v3a, v3b, v3c;
mat3 mta, mtb, mtc;
float f;
1、矢量与浮点数运算
每个分量分别和浮点值进行运算;“+、-、*、/” 的效果一致;
v3b = v3a + f; // v3b = ( v3a.x + f, v3a.y + f, v3a.z + f )
2、矢量之间的运算
对应的分量分别进行运算,“+、-、*、/” 的效果一致;
v3c = v3a + v3b; // ( v3a.x + v3b.x, v3a.y + v3b.y, v3a.z + v3b.z )
3、矩阵和浮点数运算
和1类似,矩阵的每个分量分别和浮点值进行运算;“+、-、*、/” 的效果一致;
m3b = m3a * f; /* m3a[0].x * f, m3a[0].y * f, m3a[0].z * f,
m3a[1].x * f, m3a[1].y * f, m3a[1].z * f,
m3a[2].x * f, m3a[2].y * f, m3a[2].z * f
或
m3a[0][0] * f, m3a[0][1] * f, m3a[0][2] * f,
m3a[1][0] * f, m3a[1][1] * f, m3a[1][2] * f,
m3a[2][0] * f, m3a[2][1] * f, m3a[2][2] * f */
4、矩阵右乘矢量
最终结果是矢量类型,其中每个分量都是原矢量中的对应分量,乘上矩阵对应行的每个元素的积的累加;
v3b = m3a * v3a; /* v3b.x = m3a[0].x * v3a.x + m3a[1].x * v3a.y + m3a[2].x * v3a.z;
v3b.y = m3a[0].y * v3a.x + m3a[1].y * v3a.y + m3a[2].y * v3a.z;
v3b.z = m3a[0].z * v3a.x + m3a[1].z * v3a.y + m3a[2].z * v3a.z;
或
v3b[0] = m3a[0][0] * v3a[0] + m3a[1][0] * v3a[1] + m3a[2][0] * v3a[2];
v3b[1] = m3a[0][1] * v3a[0] + m3a[1][1] * v3a[1] + m3a[2][1] * v3a[2];
v3b[2] = m3a[0][2] * v3a[0] + m3a[1][2] * v3a[1] + m3a[2][2] * v3a[2]; */
5、矩阵左乘矢量
结果同样是矢量类型;
v3b = v3a * m3a; /* v3b.x = v3a.x * m3a[0].x + v3a.y * m3a[0].y + v3a.z * m3a[0].z;
v3b.y = v3a.x * m3a[1].x + v3a.y * m3a[1].y + v3a.z * m3a[1].z;
v3b.z = v3a.x * m3a[2].x + v3a.y * m3a[2].y + v3a.z * m3a[2].z;
或
v3b[0] = v3a[0] * m3a[0][0] + v3a[1] * m3a[0][1] + v3a[2] * m3a[0][2];
v3b[1] = v3a[0] * m3a[1][0] + v3a[1] * m3a[1][1] + v3a[2] * m3a[1][2];
v3b[2] = v3a[0] * m3a[2][0] + v3a[1] * m3a[2][1] + v3a[2] * m3a[2][2]; */
6、矩阵之间相乘
m3c = m3a * m3b;
/* m3c[0].x = m3a[0].x * m3b[0].x + m3a[1].x * m3b[0].y + m3a[2].x * m3b[0].z;
m3c[1].x = m3a[0].x * m3b[1].x + m3a[1].x * m3b[1].y + m3a[2].x * m3b[1].z;
m3c[2].x = m3a[0].x * m3b[2].x + m3a[1].x * m3b[2].y + m3a[2].x * m3b[2].z;
m3c[0].y = m3a[0].y * m3b[0].x + m3a[1].y * m3b[0].y + m3a[2].y * m3b[0].z;
m3c[1].y = m3a[0].y * m3b[1].x + m3a[1].y * m3b[1].y + m3a[2].y * m3b[1].z;
m3c[2].y = m3a[0].y * m3b[2].x + m3a[1].y * m3b[2].y + m3a[2].y * m3b[2].z;
m3c[0].z = m3a[0].z * m3b[0].x + m3a[1].z * m3b[0].y + m3a[2].z * m3b[0].z;
m3c[1].z = m3a[0].z * m3b[1].x + m3a[1].z * m3b[1].y + m3a[2].z * m3b[1].z;
m3c[2].z = m3a[0].z * m3b[2].x + m3a[1].z * m3b[2].y + m3a[2].z * m3b[2].z;
或
m3c[0][0] = m3a[0][0] * m3b[0][0] + m3a[1][0] * m3b[0][1] + m3a[2][0] * m3b[0][2];
m3c[1][0] = m3a[0][0] * m3b[1][0] + m3a[1][0] * m3b[1][1] + m3a[2][0] * m3b[1][2];
m3c[2][0] = m3a[0][0] * m3b[2][0] + m3a[1][0] * m3b[2][1] + m3a[2][0] * m3b[2][2];
m3c[0][1] = m3a[0][1] * m3b[0][0] + m3a[1][1] * m3b[0][1] + m3a[2][1] * m3b[0][2];
m3c[1][1] = m3a[0][1] * m3b[1][0] + m3a[1][1] * m3b[1][1] + m3a[2][1] * m3b[1][2];
m3c[2][1] = m3a[0][1] * m3b[2][0] + m3a[1][1] * m3b[2][1] + m3a[2][1] * m3b[2][2];
m3c[0][2] = m3a[0][2] * m3b[0][0] + m3a[1][2] * m3b[0][1] + m3a[2][2] * m3b[0][2];
m3c[1][2] = m3a[0][2] * m3b[1][0] + m3a[1][2] * m3b[1][1] + m3a[2][2] * m3b[1][2];
m3c[2][2] = m3a[0][2] * m3b[2][0] + m3a[1][2] * m3b[2][1] + m3a[2][2] * m3b[2][2]; */
五、结构体
1、结构
用户自定义类型,关键字 struct,可以类比 C 中的结构体,但不需要 C 中的 typeof 定义;
struct light {
vec4 color;
vec3 position;
}
light l_1, l_2;
struct light { // 定义结构体和定义变量同时进行
vec4 color;
vec3 position;
} l_3;
2、赋值和构造
结构体有标准的构造函数,名称与结构体名称一致。构造函数的参数顺序必须与结构体定义的成员顺序一致;
l_1 = light(vec4(0.0, 1.0, 1.0, 0.5), vec3(0.1, 0.2, 0.0));
l_1.position ; // 访问成员
3、运算符
只支持赋值(=)和比较(!= 和 ==);
当且仅当两个结构体变量所对应的所有成员都相等时,== 运算符才会返回 true;
六、数组
只支持一维数组,不支持操作方法如push,创建数组不需要用 new 操作符,直接 float floatArr[4] 即可;
数组的长度必须是大于0的整型常量表达式,包含下面三种情况:
- 整型字面量;
- 用const修饰的全局或局部变量,不包括函数参数;
- 上述两条中的项组成的表达式;
int size = 4;
vec4 vec4Arr[size]; // 报错,size没有用const修饰;
数组不能在声明时一次性初始化,必须显式对么个元素进行初始化;
可以用 [ ] 取值,但 [ ] 内的索引值只能是整型常量表达式和 uniform 变量;
七、取样器(纹理)
有两种基本的取样器类型:sampler2D 和 samplerCube;取样器变量只能是 uniform 变量;
唯一能赋值给取样器变量的就是纹理单元的编号,而且必须使用WebGL方法 gl.uniform1i() 来进行赋值;
除了赋值(=)和比较(只有 == 和 !=),取样器变量不可以作为操作数参与运算;
八、流程控制:分支与循环
分支与循环和js、C中的类似;
if (表达式){ 语句 } else if (表达式){ 语句 } else { 语句 }
for ( 初始化表达式; 条件表达式; 循环步进表达式 ) { 循环体 }
for 循环的一些限制:
- 循环变量 i/index 之类的,只能在初始化表达式中定义,条件表达式可为空;
- 只允许有一个循环变量,循环变量只能是 int 或 float;
- 循环表达式只能是这几种形式:i++、i–、i+=常量表达式、i-=常量表达式;
- 条件表达式必须是循环变量与整型常量(数组中的整型常量)的比较;
- 在循环体内,循环变量不可被赋值;
这些限制是为了使编译器就能够对 for 循环进行内联展开;
在for语句中可使用 continue 和 break;
discard:只能在片元着色器中使用,表示放弃当前片元直接处理下一个片元;
九、函数
1、定义
格式类似C语言中的函数:
返回类型 函数名(type0 arg0, type1 arg1, ……, typen argn){
语句;
return 返回值;
}
不支持递归调用(自己调用自己);
2、参数限定词
为参数指定函数限定词,用来控制函数行为;根据类型,分为三种:
- 传递给参数的;
- 将要在函数中被赋值的;
- 既是传递给函数的,也要在函数中被赋值的;
参数限定词如下:
- in:向函数中传入值,传的是值,不是引用;函数内部可使用该参数,也可以是改变参数的值,但不会影响变量的值;
- const in:向函数中传入值,函数内可以使用,不能修改;
- out:在函数中被赋值,并被传出,传入的是变量的引用;
- inout:传入参数,同时在函数中被赋值,并被传出;传入的是变量的引用;函数会用到变量的初始值,然后修改变量的值;
- 无(默认):将一个值传给函数,和 in 一样;
十、内置函数
1、角度函数
radians:角度转弧度、degrees:弧度转角度;
2、三角函数
sin、cos、tan、asin、acos、atan;
3、指数函数
pow、exp:自然指数、log:自然对数、exp2:2的n次方、log2:以2为底的对数、sqrt:来平方、inversesqrt:开平方的倒数;
4、通用函数
abs、min、max、mod:取余数、sign:取正负号、floor、ceil、clamp:限定范围、mix:线性内插、step:步进函数、smootstep:艾米内插步进、fract:获取小数部分;
5、几何函数
length:矢量长度、distance:两点间的距离、dot:内积、cross:外积、normalize:归一化、reflect:矢量反射、faceforward:使向量“朝前”;
6、矩阵函数
matrixCmpMult:逐元素乘法;
7、矢量函数
lessThan:逐元素小于、lessThanEqual:逐元素小于等于、greaterThan:逐元素大于、greaterThanEqual:逐元素大于等于、equal:逐元素等于、notEqual:逐元素不等、any:任一元素为true则为true、all:所有元素为true则为true、not:逐元素互补;
8、纹理查询函数
texture2D:在二维纹理中获取纹素、textureCube:在立方体纹理中获取纹素;
texture2DProj:texture2D的投影版本、texture2DLod:texture2D的金字塔版本;
textureCubeLod:textureCube 的金字塔版本、texture2DProjLod:texture2DLod的投影版本;
十一、全局和局部变量
全局变量:函数外部声明的变量;
局部变量:函数内部声明的变量;
十二、存储限定字
1、const 变量
const 限定字表示该变量的值不能被修改;
2、attrubute 变量
只能用在顶点着色器中;只能声明为全局变量
只能修饰 float、vec2、vec3、vec4、mat2、mat3、mat4;
不管设备配置如何,支持WebGL的环境都支持至少8个attribute变量;内置全局变量:gl_MaxVertexAttribs;
3、uniform 变量
可用在顶点着色器和片元着色器中,且必须是全局的;
可以修饰除了数组和结构体之外的任意数据;
如果片元着色器和顶点着色器中声明了同名的 uniform 变量,那么它就会被共享;
uniform 变量包含逐片元/像素的数据,即不仅仅用于顶点的数据;
uniform(顶点着色器):最大数量和设备有关,最小值128个;内置全局变量:gl_MaxVertexUniformVectors;
uniform(片元着色器):最大数量和设备有关,最小值16个;内置全局变量:gl_MaxFragmentUniformVectors;
4、varying 变量
只能声明为全局变量;
作用是从顶点着色器往片元着色器传值;
最大支持数量与设备有关,至少支持8个;内置全局变量:gl_MaxVaryingVectors;
十三、精度限定字
1、基本类型
用来表示每种数据的具有的精度(比特数);
引入精度限定字的目的是帮助着色器程序提高运行效率,削减内存开支;高精度的程序需要更大的开销;
精度限定字类型:
- highp:高精度,顶点着色器的最低精度;Float默认范围(-262,262),精度2-16;Int默认范围为(-216,216);
- midiump:中精度,片元着色器的最低精度;Float默认范围(-214,214),精度2-10;Int默认范围为(-210,210);
- lowp:低精度,可以表示所有颜色;Float默认范围(-2,2),精度2-16;Int默认范围为(-28,28);
还有两点需要注意:
- 某些WebGL系统中,片元着色器不支持highp精度,可以用“十四、预处理指令”中的系统内置宏来判断;
- 数值范围和精度实际也是和系统环境相关的,可以使用 gl_getShaderPrecisionFormat() 来检查;
2、使用
精度限定字可以单独使用在变量上,也可以用关键字 precision 来声明着色器的默认精度,这句代码必须在着色器顶部 “precision 精度限定词 类型名称”;
precision mediump float; // 所有浮点数默认为中精度
precision highp int; // 所有整型默认为高精度
mediump float size;
highp vec4 v4;
3、默认精度
着色器有默认精度,顶点着色器和片元着色器不一样;
顶点着色器:
- int:highp;
- float:highp;
- sampler2D:lowp;
- samplerCube:lowp;
片元着色器,float没有默认精度(所以片元着色器第一句要指定float的精度)
- int:midiump;
- float:无;
- sampler2D:lowp;
- samplerCube:lowp;
十四、预处理指令
预处理指令用来在真正编译之前对代码进行预处理,都以#开始;
1、分类
GLES ES中可能用到三种预处理指令
#if 条件表达式
为true时执行的语句;
#endif
#ifdef 某宏
如果定义了宏,需要执行的语句;
#endif
#ifndef 某宏
如果没有定义了宏,需要执行的语句;
#endif
2、宏
可以使用 #define 指令进行宏定义,格式为“#define 宏名 宏内容”;
可以用 #undef 解除定义,格式为“#undef 宏名”;
可以使用 # else 指令配合 # ifdef;
#define NUM 100
#if NUM == 100
执行代码;
#else
执行代码;
#endif
宏的名称任意起,只要不和预定义的宏名相同;默认宏名如下
- GL_ES:在 OpenGL ES 2.0 中定义为1;
- GL_FRAGMENT_PRECISION_HIGH:片元着色器是否支持highup精度;
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float; // 支持高精度,限定浮点型为高精度
#else
precision midiump float; // 不支持高精度,限定浮点型为中精度
#endif
#endif
3、指定GLSL ES版本
可以使用 #version 来指定着色器使用的 GLSL ES 版本,格式为 “#version 版本”,可接受的版本包括 100(GLSL ES 1.00)和 101(GLSL ES 1.01),着色器默认使用 100;
#version指令必须在着色器顶部,在它之前只能有注释和空白;