WebGL笔记(四)GLSL ES 着色器语言

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语句中可使用 continuebreak

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指令必须在着色器顶部,在它之前只能有注释和空白;

如果这篇文章对你有用,可以点击下面的按钮告诉我

0

发表回复