1.简介
OpenGL ES
着色器语言(GLSL ES
),是OpenGL
着色器语言的简化版。着色器语言就是编写着色器代码的语言。这里我会略过很多介绍,假设你有编程基础。注意:这里讲的是webgl1.0,所以不是es3.0的语法
2.基础
2.1 执行次序
和c
语言类似,从文件的main
函数开始执行
2.2 注释
// 单行
/**
* 多行
*/
2.3 数据值类型
数据值类型只有两种:
- 数值:包括浮点数和整数,只要有小数点都被认为是浮点数
- 布尔值:
true
或false
- 注意:没有字符串类型
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 矢量与矩阵
矢量类型 | 描述 |
---|---|
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
[]
操作同样有需要注意的地方:
- 由于没有字符串类型,所以不能
m[1]['y']
- 索引值要遵守以下规则
- 整型
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. 矩阵右乘矢量
// 注意是列主序存储
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. 矩阵左乘矢量
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
变量,它有两种sampler2D
和samplerCube
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 | 类似传递引用,内部可以修改,并且会影响外部变量 |
那么out
和inout
有什么区别呢。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