WebGL学习(五)着色器语言

134 阅读9分钟

1.简介

OpenGL ES着色器语言(GLSL ES),是OpenGL着色器语言的简化版。着色器语言就是编写着色器代码的语言。这里我会略过很多介绍,假设你有编程基础。注意:这里讲的是webgl1.0,所以不是es3.0的语法

2.基础

2.1 执行次序

c语言类似,从文件的main函数开始执行

2.2 注释

// 单行
/**
* 多行
*/

2.3 数据值类型

数据值类型只有两种:

  • 数值:包括浮点数和整数,只要有小数点都被认为是浮点数
  • 布尔值:truefalse
  • 注意:没有字符串类型

2.4 变量

变量命名规则和大多数语言一样,特别注意不能gl_webgl__webgl_开头。

3. 类型

GLSL ES是强类型语言,声明变量的时候定义类型<类型> <变量名>

3.1 基本类型

类型描述
float单精度浮点
int整型
bool布尔值

支持使用类型包装函数转换数据,如

int a = 1;
float b = float(a) // 1.0

3.2 运算符

一般运算符都支持

3.3 矢量与矩阵

矢量(a,b,c)  矩阵[aa1a2bb1b2cc1c2]矢量\\ (a, b, c)\\\;\\ 矩阵\\ \begin{bmatrix} a&a1&a2\\b&b1&b2\\c&c1&c2\\ \end{bmatrix}
矢量类型描述
vec2、vec3、vec4具有2、3、4个浮点数元素的矢量
ivec2、ivec3、ivec4具有2、3、4个整型数元素的矢量
bvec2、bvec3、bvec4具有2、3、4个布尔值元素的矢量
矩阵类型描述
mat2、mat3、mat4具有2、3、4个浮点数元素的矩阵

3.3.1 矢量使用:

// 使用对应类型的构造函数生成类型变量
vec3 v3 = vec3(1, 2, 3)  // => (1, 2, 3)
// 多余的会被舍弃
vec2 v2 = vec2(v3)  // => (1, 2) 
// 缺少的会补1
vec4 v4 = vec4(1) // => (1, 1, 1, 1)
// 合成矢量,v2填不满用v4的元素填充
vec4 v5 = vec4(v2, v4) // => (1, 2, 1, 1)

3.3.2 矩阵使用:

// 注意是列主序
/**
    原始矩阵是 1, 2, 3              1, 4, 7,
            [ 4, 5, 6 ]   => mat4( 2, 5, 8, )
              7, 8, 9              3, 6, 9
*/
// 使用矢量合成矩阵
vec2 v1 = vec2(1, 2);
vec2 v2 = vec2(3, 4);
mat2 m1 = mat2(v1, v2); // => 1, 3
                              2, 4
vec4 v3 = vec4(1, 2, 3, 4);
mat2 m2 = mat2(v3);// => 1, 3
                         2, 4
                     
// 传入单个数值, 对角线都是1
mat2 m3 = mat2(1) // => 1, 0
                        0, 1

3.3.3 访问元素

js对象一样使用.或者[],不过.是用来访问矢量的分量,[]是用来访问矢量、数组或者矩阵元素的。

// '.'操作
vec3 v1 = vec3(1, 2, 3)
v1.x // => 1
v1.r // => 1
v1.z // => 3

上面使用了x、y、z...字母来获取分量,实际上这是规定好的。

x代表第一个分量y代表第二个z代表第三个w代表第四个。

但是为了更加语义化,比如你试图获取一个代表颜色的矢量的分量,你可以使用r、g、b、a这几个字母,他们仍然是依次获取分量。

系列含义
x y z w获取坐标分量
r g b a获取颜色分量
s t p q获取纹理分量,因为r已经用过了,所以用p代替

.操作还有个特殊的地方,混合(swizzling):

v => (1, 2, 3, 4)
v.xy => (1, 2)
v.xz => (1, 3)
// 可以逆序
v.wx => (4, 1)
// 可以重复
v.xx => (1, 1)
// 可以赋值
v.xz = vec2(9, 9)
v => (9, 2, 9, 4)

注意:使用的分量字符,必须是同一系列的。不能如v.rx这样。

// '[]'操作
/*
   1,2,3
 [ 4,5,6 ]
   7,8,9
*/
// 注意列主顺序
mat3 m = mat3( 1, 4, 7
               2, 5, 8,
               3, 6, 9) 
// 表示m的第一行,但是代表实际矩阵的第一列
m[0] // => vec3(1, 4, 7)
m[0][0] //=> 1
// 因为m[1]的结果是一个矢量,所以可以使用.符号
m[1].y //=> 5

[]操作同样有需要注意的地方:

  1. 由于没有字符串类型,所以不能m[1]['y']
  2. 索引值要遵守以下规则
  • 整型
  • const修饰的变量
  • 循环的索引
  • 上面三个组成的表达式
int index = 1
m[index] // × 不能使用变量
const int index = 1 // √ 使用const修饰
m[index + 1] // √ 可以使用表达式 

3.3.4 运算符

矩阵或者向量也支持一般的运算符,个别运算和字面量运算意义可能不一样。

1. + - * / +=等两个矢量或者矩阵的基本运算,实际上是对他们的分量分别做计算:

  mat4 m1 = mat4(
   1, 1, 1, 1,
   0, 0, 0, 0,
   0, 0, 0, 0,
   0, 0, 0, 0
 );
 mat4 m2 = mat4(
   1, 1, 1, 1,
   0, 0, 0, 0,
   0, 0, 0, 0,
   0, 0, 0, 0
 );
 m1 + m2 => (
               2, 2, 2, 2,
               0, 0, 0, 0,
               0, 0, 0, 0,
               0, 0, 0, 0 
            )

2. == !=,比较运算比较的是每一个分量是否相等。webgl有内置函数equal,它接收两个矢量,返回一个矢量,

    // 用上面的m1、m2
    bvec4 isEqual = equal(m1[0], m2[0]) // => (true, true, true, true)

3. 矩阵右乘矢量

举个例子  [111000000](2,2,2)(12+12+12,02+02+02,02+02+02)=(3,0,0)举个例子\\\;\\ \begin{bmatrix} 1&1&1\\0&0&0\\0&0&0\\ \end{bmatrix} * (2,2,2) \rightarrow (1*2+1*2+1*2, 0*2+0*2+0*2, 0*2+0*2+0*2) \\= (3, 0, 0)
// 注意是列主序存储
mat3 m = mat3(
    1, 0, 0,  
    1, 0, 0  
    1, 0, 0
)
vec3 v = vec3(2,2,2)
vec3 res = m * v
// 所以res.x应该是m的第一行与v.x的乘和
// 由于列主序,所以第一行其实是m的第一列
// res.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z
// res.y  res.z以此类推

4. 矩阵左乘矢量

举个例子  (2,2,2)[111000000](21+20+20,21+20+20,21+20+20)=(2,2,2)举个例子\\\;\\ (2,2,2) * \begin{bmatrix} 1&1&1\\0&0&0\\0&0&0\\ \end{bmatrix} \rightarrow (2*1+2*0+2*0, 2*1+2*0+2*0, 2*1+2*0+2*0) \\= (2, 2, 2)
mat3 m = mat3(
    1, 0, 0,  
    1, 0, 0  
    1, 0, 0
)
vec3 v = vec3(2,2,2)
vec3 res = v * m
// res.x = m[0].x * v.x + m[0].y * v.y + m[0].z * v.z

5. 矩阵乘以矩阵

与上面类似

m3 = m1 * m2 
    => m3[0].x 
       = m1第一行 与 m2第一列 的乘和
       = m1[0].x * m2[0].x + m1[1].x * m2[0].y + m1[2].x * m2[0].z

3.4 结构体

和C语言类似,使用struct关键字

struct light {
    vec4 color
    bool open
}
light l // 声明了一个light类型的变量
        // 这里和c语言不同,不需要typedef来定义类型,会自动生成同名类型

3.4.1 构造赋值

    // 自动生成的构造函数light,参数就是结构体成员
    l = light(vec4(1, 0, 0, 1), true);

3.4.2 访问成员

c语言一样,使用`.`操作符

3.4.3 运算符

只有`==``!=`以及赋值

3.5 数组

js不同,这里的数组只能是一维的,而且没有pop之类的方法。

vec4 v[3] // 只需要在变量后面加上[长度],就是一个数组
          // 这里定义了一个元素类型为vec4,长度为3的数组
// 数组的长度和之前的索引一样,不能使用变量
// 只能const修饰的变量,索引变量,或者uniform变量

数组不能一次性全部初始化比如list = [1, 2, 3],只能一个元素一个元素初始化list[0] = ... list[1] =...

3.7 采样器sampler

通过这种类型的变量访问纹理,采样器只能是uniform变量,它有两种sampler2DsamplerCube

4. 流程控制

就跟js一样,if-else for continue,这里讲点区别。

  • 没有switch语句,大量的判断会降低效率
  • for循环变量只能是int或者float,循环条件必须是循环变量与整型常量比较,循环体内不能赋值循环变量。
  • discard,只能在片元着色器中使用,表示放弃当前片元,执行下一个。

5. 函数

c语言类似:

返回值类型 (参数类型 参数1) {
    函数体
    return 返回值
}
// 先声明
bool isEqual(vec4 v1, vec4 v2) {
    return all(equal(v1, v2))
}
bool eq = isEqual(vec4(), vec4())
    
// 也可以后声明,但是要提前规范声明
bool isEqual(vec4 v1, vec4 v2) 
bool eq = isEqual(vec4(), vec4())
bool isEqual(vec4 v1, vec4 v2) {
    return all(equal(v1, v2))
}

返回值可以为结构体,但是结构不能有数组成员。函数调用不能有递归。

C语言中参数可以传递指针,在webgl中有参数限定词。

类别作用
in和没有限定一样,表示传入一个参数,内部可以使用修改,但是不会影响变量本身
const in内部不能修改
out类似传递引用,内部可以修改,并且会影响外部变量
inout类似传递引用,内部可以修改,并且会影响外部变量

那么outinout有什么区别呢。out如他名字一样,表示他是一个输出的参数,它只在乎输出。而inout如同名字输入输出都在乎,所以输入会影响参数的初始值。

// inout必须有初始值,因为函数会用到
void swap(inout float a, inout float b) {
  float temp = a;
  a = b;
  b = temp;
}
float a = 1.0;
float b = 2.0; 
swap(a, b);
    
void lengthAndDirection(in vec3 v, out float len, out vec3 dir) {
  len = length(v);
  dir = normalize(v);
}
vec3 v = vec3(1.0, 2.0, 3.0);
float len; 
vec3 dir; 
// dir和len不能有初始值,函数也不会用到初始值
lengthAndDirection(v, len, dir);

6.内置函数

参考

7.存储限定词

指的是在类型前面的修饰词,const int a uniform vec4 a

7.1 const

必须在声明的时候初始化,设定初始值之后就不能修改了。

7.2 attribute

只能声明在顶点着色器中,只能为全局变量。它用来表示逐顶点信息。 什么是逐顶点?就是关键的顶点,比如你只需要给出两个点就能画出一条直线。

attribute变量有数量限制,至少有8个,最多有gl_MaxVertexAttribs个 相似的uniform varying也有数量限制

attribute只能修饰固定的类型float vec mat

7.3 uniform

在着色器中表示不可变的变量,只读的。如果顶点着色器和片元着色器都声明了同一个uniform变量,那么他们是共享的。

7.4 varying

用于顶点着色器向片元着色器传递数据,而且在传递过程中(光栅化中),进行插值(内插)。

必须声明为全局变量,和attribute一样只能修饰float vec mat 。 顶点着色器和片元着色器必须声明一样名称的varying变量。

8 精度限定

因为webgl设计出来是为了在性能不足的设备上运行,所以配置浮点精度是很有必要的,可以减少计算量。当然过低也会导致计算误差大。

// 指定所有的浮点数精度都是中等
precision mediump float;
// 指定当前变量精度为高精度
highp vec4 position
// 低精度
lowp vec4 color

因为float没有默认精度,所以之前的程序中都设置了它的精度。

9 预处理

// 定义宏
#define 宏名 宏内容
// 解除宏
#undef 宏名
// 预处理
#if 条件
执行内容
#else
执行内容
#endif

    
#ifdef 宏名
如果定义某宏,执行
#endif
#ifndef 宏名
如果没有定义某宏,执行
#endif