Skip to content

GLSL-es 基本语法

GLSL ES是在GLSL(OpenGL着色器语言)的基础上,删除和简化了一部分功能后形成的,ES版本主要降低了硬件功耗,减少了性能开销。实际上 GLSL 并不支持GLSL ES的所有特性

LSL ES是强类型语言,"强类型语言"指的是编程语言中对变量和表达式的类型进行严格检查的语言,相对于js弱类型语言,这就得在声明变量的时候指出其数据类型了,且该类型不能被隐式转换为其他类型。

可以安装glsl-canvas插件,该扩展通过提供Show glslCanvas命令()在VSCode中打开GLSL着色器的实时WebGL预览。

使用快捷键 ctrl + shift + P ,打开命令行窗囗后输入 Show glslcanvas预览。

GLSL
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main() {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
    st.x *= u_resolution.x/u_resolution.y;

    vec3 color = vec3(0.);
    color = vec3(st.x,st.y,abs(sin(u_time)));

    gl_FragColor = vec4(color,1.0);
}
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

void main() {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
    st.x *= u_resolution.x/u_resolution.y;

    vec3 color = vec3(0.);
    color = vec3(st.x,st.y,abs(sin(u_time)));

    gl_FragColor = vec4(color,1.0);
}

变量的基本规范

  1. 大小写敏感。
  2. 变量只能包括a-zA-Z0-9_
  3. 变量名的首字符不能是数字。
  4. 变量不能是关键字,保留字。
  5. 语句末尾必须要有分号。
  6. 注释语法和javascript一样 单行: //多行: /**/
  7. GLSL ES是强类型语言,声明变量时必须指定类型,允许先声明后赋值。
关键字
attributeboolbreakbvec2bvec3bvec4
constcontinuediscarddoelsefalse
floatforhightpifininout
intinvariantivec2ivec3ivec4lowp
mat2mat3mat4mediumoutprecision
returnsampler2Dsampler3Dstructtrueuniform
varyingvec2vec3vec4voidwhile
保留字
asmcastclassdefault
doubledvec2dvec3devc4
enumexternexternalfixed
flatfvec2fvec3fvec4
gotohalfhvec2hvec3
hvec4inlineinputinterface
longnamespacenoinlineoutput
packedpublicsampler1Dsampler1DShadow
sampler2DRectsampler2DRectShadowsampler2DShadowsampler3D
sampler3DRectshortsizeofstatic
superpswitchtemplatethis
typedefunionunsignedusing
volatile

基本数据类型

  • 浮点型: float,如1.0
  • 整型: int,如1
  • 布尔型: bool,如true / false

变量声明

GLSL ES是强类型语言,在声明变量的时候应该指明变量类型。

GLSL
float f = 1.0;
int i = 1;
bool b = true;
float f = 1.0;
int i = 1;
bool b = true;

变量的赋值

使用等号=赋值,=两侧的数据类型需要一致

GLSL
int i = 8.0; // ✖
int i = 8; // ✔
float f = 8; // ✖
float f = 8.0; // ✔
int i = 8.0; // ✖
int i = 8; // ✔
float f = 8; // ✖
float f = 8.0; // ✔

变量的类型转换

  • 浮点转整数:int(float)
  • 布尔转整数:int(bool)
  • 整数转浮点:float(int)
  • 布尔转浮点:float(bool)
  • 整数转布尔:bool(int)
  • 浮点转布尔:bool(float)
GLSL
float f =float(8)
float f =float(8)

向量

类型含义
vec(n)包含n个float分量的默认向量。
bvec(n)包含n个bool分量的向量。
ivec(n)包含n个int分量的向量。
uvec(n)包含n个无符号(只表示数值)int分量的向量。
dvec(n)包含n个double分量的向量。

向量声明

GLSL
vec3 v3 = (1.0, 0.0, 0.5);  // (1.0, 0.0, 0.5)
vec2 v2 = (v3);             // (1.0, 0.0)
vec4 v4 = (1.0)             // (1.0, 1.0, 1.0, 1.0)
vec3 v3 = (1.0, 0.0, 0.5);  // (1.0, 0.0, 0.5)
vec2 v2 = (v3);             // (1.0, 0.0)
vec4 v4 = (1.0)             // (1.0, 1.0, 1.0, 1.0)

将多个向量合并

GlSL
vec4 v4b=vec4(v2,v4);       // (1.0, 0.0, 1.0, 1.0)
vec4 v4b=vec4(v2,v4);       // (1.0, 0.0, 1.0, 1.0)

注:= 两侧的数据类型必须一致

GlSL
vec4 v4 = vec2(1.0);        // 错的
vec4 v4 = vec2(1.0);        // 错的

矩阵

GLSL ES 支持2、3、4维矩阵:mat2、mat3、mat4。矩阵中的元素都是浮点型

  • 向矩阵传入值为浮点数时顺序必须是列主序(真实矩阵的转置格式)
GLSL
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/

上面4 x 4矩阵对应的其实是这样的列主序

(1.05.09.013.02.06.010.014.03.07.011.015.04.08.012.016.0)
  • 向矩阵传入值为向量
GLSL
vec4 v4_1 = vec4(1.0, 2.0, 3.0, 4.0);
vec4 v4_2 = vec4(5.0, 6.0, 7.0, 8.0);
vec4 v4_3 = vec4(9.0, 10.0, 11.0, 12.0);
vec4 v4_4 = vec4(13.0, 14.0, 15.0, 16.0)
mat4 m4 = mat4(v4_1, v4_2, v4_3, v4_4);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/
vec4 v4_1 = vec4(1.0, 2.0, 3.0, 4.0);
vec4 v4_2 = vec4(5.0, 6.0, 7.0, 8.0);
vec4 v4_3 = vec4(9.0, 10.0, 11.0, 12.0);
vec4 v4_4 = vec4(13.0, 14.0, 15.0, 16.0)
mat4 m4 = mat4(v4_1, v4_2, v4_3, v4_4);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/
  • 向矩阵传入值为向量浮点数
GLSL
vec4 v4_1 = vec4(1.0, 2.0, 3.0, 4.0);
vec4 v4_2 = vec4(5.0, 6.0, 7.0, 8.0);
mat4 m=mat4(
  v4_1,
  v4_2,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/
vec4 v4_1 = vec4(1.0, 2.0, 3.0, 4.0);
vec4 v4_2 = vec4(5.0, 6.0, 7.0, 8.0);
mat4 m=mat4(
  v4_1,
  v4_2,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
/*
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
*/
  • 单个浮点数(单位矩阵)
GLSL
mat4 m4 = mat4(1.0);
/*
  1.0, 0.0, 0.0, 0.0,
  0.0, 1.0, 0.0, 0.0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0,
*/
mat4 m4 = mat4(1.0);
/*
  1.0, 0.0, 0.0, 0.0,
  0.0, 1.0, 0.0, 0.0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0,
*/

访问元素

访问矢量或矩阵中的元素,可以使用 · 或者 [] 运算符。

向量分量的访问

  • 通过分量属性访问:
GlSL
v4.x, v4.y, v4.z, v4.w  // 齐次坐标
v4.r, v4.g, v4.b, v4.a  // 色值
v4.s, v4.t, v4.p, v4.q  // 纹理坐标
v4.x, v4.y, v4.z, v4.w  // 齐次坐标
v4.r, v4.g, v4.b, v4.a  // 色值
v4.s, v4.t, v4.p, v4.q  // 纹理坐标
  • 将分量的多个属性连在一起,可以获取多个向量
GlSL
vec4 v4 = vec4(1.0, 2.0, 3.0, 4.0); 
v4.xy //(1.0, 2.0)
v4.yx //(2.0, 1.0)
v4.xw //(1.0, 4.0)
vec4 v4 = vec4(1.0, 2.0, 3.0, 4.0); 
v4.xy //(1.0, 2.0)
v4.yx //(2.0, 1.0)
v4.xw //(1.0, 4.0)
  • 通过分量索引访问
GLSL
v4[0], v4[1], v4[2], v4[3]
v4[0], v4[1], v4[2], v4[3]
  • 访问到向量后,也可以用=号为向量的分量赋值。
GLSL
v4.x = 1.0
v4[0] = 1.0
v4.xy = vec2(1.0, 2.0)
v4.x = 1.0
v4[0] = 1.0
v4.xy = vec2(1.0, 2.0)

矩阵的访问

  • 矩阵中的元素仍然是按照列主序读取。
GLSL
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
mat4[0]; // 1.0, 2.0, 3.0, 4.0,
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
mat4[0]; // 1.0, 2.0, 3.0, 4.0,

-4m[x][y] x行 y列

GLSL
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
mat4[0][0]; // 1.0
mat4 m4 = mat4(
  1.0, 2.0, 3.0, 4.0,
  5.0, 6.0, 7.0, 8.0,
  9.0, 10.0, 11.0, 12.0,
  13.0, 14.0, 15.0, 16.0
);
mat4[0][0]; // 1.0
  • [] 中的索引值接受只读索引
GLSL
const int y = 0; // ✔
int y = 0;       // ✖
4m[y];
const int y = 0; // ✔
int y = 0;       // ✖
4m[y];
  • [] 中的索引值可以是表达式
GLSL
const int y = 0;
4m[y+1];
const int y = 0;
4m[y+1];

结构体

GLSL ES 支持用于自定义的类型,也就是结构体。使用关键字 struct,将已存在的类型聚合在一起,就可以定义为结构体:

GLSL
// 定义了结构体类型 light
struct light {
  vec4 color;     // 颜色
  vec3 position;  // 位置
}
light l1, l2;     // 声明了 light 类型的变量 l1 和 l2

// 也可以是下面这种写法
struct light {
  vec4 color;     // 光的颜色
  vec3 position;  // 光源位置
} l1;          // 声明了 light 类型的变量 l1
// 定义了结构体类型 light
struct light {
  vec4 color;     // 颜色
  vec3 position;  // 位置
}
light l1, l2;     // 声明了 light 类型的变量 l1 和 l2

// 也可以是下面这种写法
struct light {
  vec4 color;     // 光的颜色
  vec3 position;  // 光源位置
} l1;          // 声明了 light 类型的变量 l1

使用赋值和构造创建:

GLSL
l1 = light(vec4(0.0, 1.0, 0.0, 1.0), vec3(8.0, 3.0, 0.0))
l1 = light(vec4(0.0, 1.0, 0.0, 1.0), vec3(8.0, 3.0, 0.0))

结构体访问

跟 JavaScript 对象访问属性是一样的。如下:

GLSL
vec4 position = l1.color;
vec3 position = l1.position;
vec4 position = l1.color;
vec3 position = l1.position;

存储限定符

  • 存储限定字(Storage Qualifier)是用于指定变量存储方式的关键字。存储限定字可以用于在内存中指定变量的存储位置、生命周期和可访问性等信息。

const:表示该变量的值不能被改变。

GLSL
const float pi = 3.14159;
const float pi = 3.14159;

attribute:attribute变量是全局且只读的,它只能在vertex shader中使用,只能与浮点数,向量或矩阵变量组合, 一般attribute变量用来放置程序传递来的模型顶点,法线,颜色,纹理等数据它可以访问数据缓冲区。 const:表示该变量的值不能被改变。

GLSL
attribute vec4 position;
attribute vec4 position;

uniform:变量可以用在顶点着色器和片元着色器中,并且是全局变量。uniform变量是只读的,如果顶点着色器和片元着色器中声明了同名的uniform变量,那么它就会被两种着色器共享。

varying:变量必须是全局变量,它的作用是从顶点着色器向片元着色器中传输数据。

精度限定符

精度限定字描述Floatint
highp高精度(-2^62, 2^62 )(-2^16, 2^16 )
mediump中精度(-2^14, 2^14 )(-2^10, 2^10 )
lowp低精度(-2, 2)(-2^8, 2^8 )

参数限定符

in:默认使用的缺省限定符,指明参数传递的是值,并且函数不会修改传入的值,用于值传递。

inout:指明参数传入的是引用,如果在函数中对参数的值进行了修改,当函数结束后参数的值也会修改,用于引用传递。

out:参数的值不会传入函数,但是在函数内部修改其值,函数结束后其值会被修改。

数组

GLSL ES 支持数组类型:

  • 并且只支持一维数组。
  • 没有与 JavaScript 一样有数组的一些操作方法(如pus、pop)。
  • 数组的长度必须是大于 0 的整型常量表达式。
  • 数组元素可以通过索引值来访问,索引值是从 0 开始的。只有整型常量表达式和 uniform 变量,可以被用作数组的索引值。
  • 数组不能在声明时被一次性地初始化,而必须显示地对每个元素进行初始化。
GLSL
// int size = 4 错误,如果第一行为 const int size = 4,则不会报错
const int size = 4;
vec4 vec4Array[size];
vec4Array[0] = vec4(4.0, 3.0, 6.0, 1.0)
vec4Array[1] = vec4(4.0, 3.0, 6.0, 1.0)

// 数组只支持 [] 运算,但数组的元素可以参与其自身类型支持的任意运算:
float f = floatArray[1] * 3.14; // 将 floatArry 的第 2 个元素乘以 3.14
// int size = 4 错误,如果第一行为 const int size = 4,则不会报错
const int size = 4;
vec4 vec4Array[size];
vec4Array[0] = vec4(4.0, 3.0, 6.0, 1.0)
vec4Array[1] = vec4(4.0, 3.0, 6.0, 1.0)

// 数组只支持 [] 运算,但数组的元素可以参与其自身类型支持的任意运算:
float f = floatArray[1] * 3.14; // 将 floatArry 的第 2 个元素乘以 3.14

取样器(纹理)

GLSL ES 支持一种内置的类型:取样器,我们必须通过该类型变量访问纹理,取样器变量只能是 uniform 变量,或者需要访问纹理的函数。有两种基本的取样器类型:

  • texture(sampler, coord): 从纹理中获取颜色值。
  • texture2D(sampler, coord): 适用于二维纹理的采样。
  • textureCube(sampler, coord): 从立方体纹理中获取颜色值。

texture2D():

GLSL
uniform sampler2D u_Sampler_1;
uniform samplerCube u_Sampler_2;

// vec4 texture2D(sampler2D sampler,vec2 coord);
// vec4 textureCube(samplerCube sampler,vec3 coord);
uniform sampler2D u_Sampler_1;
uniform samplerCube u_Sampler_2;

// vec4 texture2D(sampler2D sampler,vec2 coord);
// vec4 textureCube(samplerCube sampler,vec3 coord);

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

使用 gl.uniformli(u_Sample, 0) 将纹理单元编号 0 传给着色器。

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

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

着色器表示最大数量的内置常量最小数量
顶点着色去const mediump int gl_MaxVertexTextureImageUnits0
片元着色器const mediump int gl_MaxTextureImageUnits8

程序流程控制

IF条件判断

GLSL 中支持像 C 中的 if 写法一样,可以使用 ifelse ifelse 进行判断。

GLSL
float dist = distance(gl_PointCoord,vec2(0.5,0.5));

if(dist >= 0.5){
    gl_FragColor= vec4(1.0, 0.0, 0.0, 1.0);
} else {
    discard; // 取消片元的绘制
}
float dist = distance(gl_PointCoord,vec2(0.5,0.5));

if(dist >= 0.5){
    gl_FragColor= vec4(1.0, 0.0, 0.0, 1.0);
} else {
    discard; // 取消片元的绘制
}

注意

如果 if 语句太多会降低设色器执行速度。

For 循环

For 循环中的变量只能有一个,并且只能是 int 或 float 类型;另外,在循环体中也可以使用 break 或 continue 语句。

GLSL
for (int i = 0; i < 3; i++) {
  sum += i
}
for (int i = 0; i < 3; i++) {
  sum += i
}

需要注意的是,循环变量,也就是上面的 i 只能在舒适化表达式中定义,也就是 for 的第一个参数。for 语句有下面一些限制:

  • 只允许有一个循环变量,循环变量只能是 int 或 float 类型。
  • 循环表达式必须是以下的形式:i++, i--, i+= 常量表达式或 i-=常量表达式。
  • 条件表达式必须是循环变量与整型变量的比较。
  • 在循环体内,循环变量 i 不可被赋值。

continue、break 和 discard 语句

  • continue 终止包含该语句的最内层循环和执行循环表达式(递增 / 递减循环变量),然后执行下一次循环。
  • break 终止包含该语句的最内存循环,并不再继续执行循环。
  • discard 他只能在片元着色器中使用,表示放弃当前片元直接处理下一个片元

函数

  • 如果函数没有返回值,函数不需要有 return 语句,并且返回类型必须是 void。
  • 可以将自己定义的结构体类型指定为返回类型,但是结构体的成员中不能有数组。
  • 如果函数定义在其调用之后,需要先声明函数的规范。
  • 不能递归调用函数。

参数限定词: 在 GLSL ES 中,可以为函数参数指定限定词,以控制参数的行为。可以将函数参数定义为:

(1) 传递给函数的。

(2) 将要在函数中被赋值的。

(3) 既是传递给函数的,也是将要在函数中被赋值的。

  • in 参数深拷贝,可读写,不影响原始数据,默认限定词
  • out 参数浅拷贝,可读写,影响原始数据。
  • const in 常量限定词,只读。
  • inout 功能类似于 out,用得不多,知道即可。

自定义函数

GLSL
vec4 getPosition(){ 
    vec4 v4 = vec4(0.0, 0.0, 0.0, 1.0);
    return v4;
}

void doubleSize(inout float size){
    size = size * 2.0;
}

void main() {
    float psize = 10.0;
    doubleSize(psize);
    gl_Position = getPosition();
    gl_PointSize = psize;
}
vec4 getPosition(){ 
    vec4 v4 = vec4(0.0, 0.0, 0.0, 1.0);
    return v4;
}

void doubleSize(inout float size){
    size = size * 2.0;
}

void main() {
    float psize = 10.0;
    doubleSize(psize);
    gl_Position = getPosition();
    gl_PointSize = psize;
}

函数规范声明

GLSL
float luma(vec4) // 规范声明 
attribute vec4 a_Color;
void main() {
  float brightness = luma(a_Color) // 在定义之前就被调用了
}

float luma(vec4 color) {
  float r = color.r;
  float g = color.g;
  float b = color.b;
  return 0.2126 * r + 0.7126 * g + 0.0722 * b;
}
float luma(vec4) // 规范声明 
attribute vec4 a_Color;
void main() {
  float brightness = luma(a_Color) // 在定义之前就被调用了
}

float luma(vec4 color) {
  float r = color.r;
  float g = color.g;
  float b = color.b;
  return 0.2126 * r + 0.7126 * g + 0.0722 * b;
}

预处理指令

GLSL ES支持预处理指令。预处理指令用来在真正编译之前对代码进行预处理,以#号开始

GLSL
#ifdef GL_ES
precision mediump float;
#endif
#ifdef GL_ES
precision mediump float;
#endif

GLSL ES中可能用到的三种预处理指令。

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

#ifdef
如果定义了宏执行这里
#ednif

#ifndef
如果没有定义宏执行这里
#ednif
#if 条件表达式
If 如果条件表达式为真,执行这里
#ednif

#ifdef
如果定义了宏执行这里
#ednif

#ifndef
如果没有定义宏执行这里
#ednif

在 GLSL ES 中,宏的定义主要通过 #define 指令来实现。

GLSL
#define 宏名 替换文本
#define 宏名 替换文本
GLSL
#define PI 3.14159265359
#define MAX(a, b) ((a) > (b) ? (a) : (b))

void main() {
    float angle = 45.0 * PI / 180.0; // 使用宏 PI
    float x = 5.0;
    float y = 10.0;
    float maxValue = MAX(x, y); // 使用宏 MAX
}
#define PI 3.14159265359
#define MAX(a, b) ((a) > (b) ? (a) : (b))

void main() {
    float angle = 45.0 * PI / 180.0; // 使用宏 PI
    float x = 5.0;
    float y = 10.0;
    float maxValue = MAX(x, y); // 使用宏 MAX
}

注意:

宏展开: 在编译时,所有的宏都会被展开。这意味着,在代码中使用宏时,编译器会将其替换为定义时指定的文本。 参数宏: 在宏定义中可以使用参数(如 MAX(a, b)),这样可以创建更灵活的宏。 避免副作用: 使用参数宏时要小心,因为传递的参数会在宏展开时被多次计算,可能导致意想不到的副作用。

可以使用 #undef 来取消定义一个宏。例如:

GLSL
#undef PI
#undef PI

内置的宏:

LINE : 当前源码中的行号。

VERSION : 一个整数,指示当前的glsl版本 比如 100 ps: 100 = v1.00

GL_ES : 如果当前是在 OPGL ES 环境中运行则 GL_ES 被设置成1,一般用来检查当前环境是不是 OPENGL ES。

GL_FRAGMENT_PRECISION_HIGH : 如果当前系统glsl的片元着色器支持高浮点精度,则设置为1.一般用于检查着色器精度。