1.起步

简介

Shader 代码运行在 GPU 中,主要负责一些图形图像的效果处理。当下浏览器兼容性较好的 WebGL 是基于 OpenGL ES 的,因此建议学习 GLSL 编写 Shader。主流的 Shader 编程语言如下:

  • GLSL:OpenGL Shading Language,OpenGL 平台 API。

  • WGSL:WebGPU Shading Language,WebGPU 平台 API。

  • HLSL:High-Level Shading Language,DirectX 平台 API。

  • Cg:C for Graphics,由英伟达开发,可编译为 GLSL、HLSL。

兼容性

image-20240117211725469

image-20240117211747563

image-20240117211817351

2.定义

变量命名规则

  • 大小写敏感:例如 apositionaPosition 是不同变量。
  • 只能使用数字、字母、下划线。
  • 不能以数字开头。
  • 不能以关键字命名:例如 uniformattributevarying
  • 不能以保留前缀开头:例如 gl_webgl__webgl_ 开头的变量被用于定义 GLSL 的内部变量,这是 GLSL 保留的命名前缀。
  • 语句强制分号。

变量与常量

<变量限定符> <精度限定符> 数据类型 变量名 = 数据值;
  • 变量

    // 1.定义变量
    int count = 10; // 整型
    float num1 = 10.0; // 浮点型
    mediump float num2 =20.0; // 指定精度的浮点型
    
    // 2.修改变量
    count = 20;
    num = 20.0;
    
  • 常量

    // 1.定义常量
    const int count = 10; // 整型常量
    const mediump float PI =3.1415926; // 指定精度的浮点型常量
    
    // 2.修改常量直接报错
    count = 20;
    

注释

// 单行注释

/**
 * 多行注释
 */

3.数据类型

注意

GLSL 中类型严格,任何与数据类型不相符的赋值都会报错。

基本类型

// 1.布尔型 bool
bool hasLight = false;

// 2.整型 int
int count = 10;

// 3.单精度浮点型 float(32 位,浮点型至少需要 1 位小数,否则系统运算时可能会识别为 int)
float num = 10.0;

// 4.空 void(常用于函数返回值)
void func() {}
  • 强制类型转换

    // float、bool 转 int(直接去掉浮点数部分,false 为 0、true 为 1)
    int a = int(10.0); // 10
    int b = float(true); // 1
    
    // int、bool 转 float(整型加 1 位小数,false 为 0.0、true 为 1.0)
    float c = float(10); // 10.0
    float d = float(false); // 0.0
    
    // int、float 转 bool(0 和 0.0 为 false、其他为 true)
    bool c = bool(10);
    

向量类型

注意

齐次坐标:用 n+1 维向量 来表达 n 维点坐标和向量,w 为 0 时代表 向量,w 不为 0 时代表 点坐标,常用于透视除法。

// 1.单精度浮点型向量 vec(32位)
vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(1.0, 2.0, 3.0);
vec4 c = vec4(1.0, 2.0, 3.0, 4.0);

// 2.整型向量 ivec
ivec2 d = ivec2(1, 2);
ivec3 e = ivec3(1, 2, 3);
ivec4 f = ivec4(1, 2, 3, 4);

// 3.布尔型向量 bvec
bvec2 g = bvec2(true, false);
bvec3 h = bvec3(true, false, true);
bvec4 i = bvec4(true, false, true, false);
  • 分量

    空间坐标向量颜色向量纹理向量下标
    xrs[0]
    ygt[1]
    zbp[2]
    w(齐次坐标分量)aq[3]
  • 赋值

    // 1.单一值创建向量
    vec3 b = vec3(1.0); // -> vec3(1.0, 1.0, 1.0)
    
    // 2.高维向量赋值低维向量
    vec4 v4 = vec4(1, 2, 3, 4);
    vec2 v2 = vec2(v.xx); // -> vec2(1.0, 1.0)
    
    // 3.向量类型会自动对元素做类型转换
    vec2 coords = vec2(1, 2); // -> vec2(1.0, 2.0) 向量值自动转换为 float
    
  • 取值

    vec4 a = vec4(1.0, 2.0, 3.0, 4.0);
    
    // 相同类型的分量可以任意组合取值
    vec2 b = a.xw; // -> vec2(1.0, 4.0)
    vec3 c = a.xyz; // -> vec3(1.0, 2.0, 3.0)
    vec4 d = a.xyxx; // -> vec4(1.0, 2.0, 1.0, 1.0)
    vec4 e = a.rgrr; // -> vec4(1.0, 2.0, 1.0, 1.0)
    vec4 f = a.stss; // -> vec4(1.0, 2.0, 1.0, 1.0)
    
  • 运算

    vec4 a = vec4(1.0, 2.0, 3.0, 4.0);
    
    // 1.向量 和 数字:每个元素都与该数字运算
    // 加
    vec4 b = a + 1.0; // -> vec4(2.0, 3.0, 4.0, 5.0)
    // 减
    vec4 c = a - 1.0; // -> vec4(0.0, 1.0, 2.0, 3.0)
    // 乘
    vec4 d = a * 2.0; // -> vec4(2.0, 4.0, 6.0, 8.0)
    // 除
    vec4 e = a * 2.0; // -> vec4(0.5, 1.0, 1.5, 2.0)
    
    // 2.向量 和 向量:对应分量运算
    // 加
    vec4 f = a + vec4(-1.0, 2.0, -3.0, 4.0); // -> vec4(0.0, 4.0, 0.0, 8.0)
    // 减
    vec4 g = a - vec4(-1.0, 2.0, -3.0, 4.0); // -> vec4(2.0, 0.0, 6.0, 0.0)
    // 乘
    vec4 h = a * vec4(-1.0, 2.0, -3.0, 4.0); // -> vec4(-1.0, 4.0, -9.0, 16.0)
    // 除
    vec4 i = a * vec4(-1.0, 2.0, -3.0, 4.0); // -> vec4(-1.0, 1.0, -1.0, 1.0)
    

矩阵类型

注意

矩阵参数是列主序的。

/**
 * 1.单精度浮点型 2x2 矩阵
 *
 * 1.0  3.0
 * 2.0  4.0
 */
mat2 a = mat2(
  1.0, 2.0,
  3.0, 4.0
);

/**
 * 2.单精度浮点型 3x3 矩阵
 *
 * 1.0  4.0  7.0
 * 2.0  5.0  8.0
 * 3.0  6.0  9.0
 */
mat3 b = mat3(
  1.0, 2.0, 3.0,
  4.0, 5.0, 6.0,
  7.0, 8.0, 9.0
);

/**
 * 3.单精度浮点型 4x4 矩阵
 *
 * 1.0  5.0  9.0   13.0
 * 2.0  6.0  10.0  14.0
 * 3.0  7.0  11.0  15.0
 * 4.0  8.0  12.0  16.0
 */
mat4 c = 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.创建对角阵
     *
     * 1  0  0  0
     * 0  1  0  0
     * 0  0  1  0
     * 0  0  0  1
     */
    mat4 a = mat4(1.0);
    
    /**
     * 2.列向量创建矩阵
     *
     * 1  4  7
     * 2  5  8
     * 3  6  9
     */
    vec3 v1 = vec3(1, 2, 3);
    vec3 v2 = vec3(4, 5, 6);
    vec3 v3 = vec3(7, 8, 9);
    mat3 b = mat3(v1, v2, v3);
    
  • 取值

    /**
     * 1.0  5.0  9.0   13.0
     * 2.0  6.0  10.0  14.0
     * 3.0  7.0  11.0  15.0
     * 4.0  8.0  12.0  16.0
     */
    mat4 a = 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.取第 2 列第 3 行数据
    float b = a[1][2]; // -> 7.0
    
    // 2.取第 2 列数据
    vec4 c = a[1]; // -> vec4(5.0, 6.0, 7.0, 8.0)
    
  • 运算

     /**
     * 1.0  3.0
     * 2.0  4.0
     */
    mat2 a = mat2(
      1.0, 2.0,
      3.0, 4.0
    );
    
    // 1.矩阵 和 数字:每个元素都与该数字运算
    mat2 b = a * 10; // -> mat2(10.0, 20.0, 30.0, 40.0)
    
    // 2.矩阵 和 向量:线性代数规则
    vec2 c = a * vec2(1.0, 2.0); // -> vec2(5.0, 11.0)
    
    // 3.矩阵 和 矩阵:线性代数规则
    mat2 d = a * mat2(1.0, 2.0, 1.0, 2.0); // -> mat2(3.0, 6.0, 7.0, 14.0)
    

结构体类型

// 1.定义结构体数据类型
struct DirectionalLight {
  vec2 direction;
  vec3 color;
  float level;
};

// 2.声明结构体变量
uniform DirectionalLight light1;
DirectionalLight light2 = DirectionalLight(
  vec2(1.0, 2.0),
  vec3(4.0, 5.0, 6.0),
  7.0
);
  • 赋值

    // 1.访问结构体成员赋值
    light2.level = 8.0;
    
  • 取值

    // 1.访问结构体成员取值
    float level = light2.level; // -> 8.0
    

取样器类型

注意

取样器的存储限定符只能使用 uniform

// 1.二维纹理(纹理图片的像素数据)
uniform sampler2D uDiffuse;

// 2.立方体纹理
uniform samplerCube uCubeDiffuse;
  • 纹理映射

    // 一.顶点着色器
    attribute vec4 aPosition;
    attribute vec2 aUv; // 纹理坐标
    varying vec2 vUv; // 传递片元着色器的纹理坐标
    
    void main() {
      gl_Position = aPosition;
      // 纹理坐标传递
      vUv = aUv;
    }
    
    // 二.片元着色器
    precision mediump float;
    uniform sampler2D uDiffuse; // 纹理图片像素数据
    varying vec2 vUv; // 传递片元着色器的纹理坐标
    
    void main() {
      // 根据 uv 采集纹素后逐片元赋值
      gl_FragColor = texture2D(uDiffuse, vUv);
    }
    

4.变量限定符

注意

用于描述变量如何被 GLSL 存储,不同的变量限定符无法同时使用

const 常量

定义后数据值不可修改。

const float PI = 3.1415926;
  • 场景:声明一些不需要修改的参数,例如弧度。

uniform 全局变量

用于向着色器传递指定参数,必须声明在所有着色器代码之前,在所有着色器中值保持一致(所有顶点数据相同)。

uniform vec3 uColor;
  • 场景:声明非顶点相关的数据,例如复变矩阵、二维纹理。
  • 数据流:JavaScript -> 顶点着色器、片元着色器

attribute 顶点变量

一般与 buffer 结合向着色器传递逐顶点信息(每个顶点数据不同)。

attribute vec2 aPosition;
  • 场景:声明顶点相关的数据,例如位置、颜色、法向量。
  • 数据流:JavaScript -> 顶点着色器

varying 传递变量

用于顶点着色器和片元着色器之间的数据传递及插值计算。

varying vec2 vUv;
  • 场景:声明需要插值计算的顶点数据。
  • 数据流:顶点着色器 -> 片元着色器

5.精度限定符

注意

定义指定数据类型的计算精度,较高精度会占用更多系统资源

精度级别

  • lowp:低精度。
  • mediump:中精度。
  • highp:高精度。

声明方式

// 1.全局声明:影响着色器内同数据类型精度
precision mediump float; // 中精度浮点型

// 2.单个声明:影响单一变量(不推荐,不利于后期维护)
lowp vec2 position; // 低精度浮点型二维向量

默认精度

数据类型顶点着色器片元着色器
inthighpmediump
floathighp无,必须手动设置
sampler2Dlowplowp
samplerCubelowplowp

6.运算符

算数运算符

// 1.数学计算
+  -  *  /

// 2.赋值计算
+=  -=  *=  /=

// 3.自增、自减
++  --

比较运算符

// 1.大小比较
>  <  >=  <=

// 2.相等比较
==  !=

逻辑运算符

// 1.或、且、非
||  &&  !

// 2.异或
^^

三元运算符

// 1.三元
expression ? a : b

7.控制语句

if-else 判断

if(x > 100){
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
} else if (x > 50) {
  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
} else {
  gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}

for 循环

for(int i = 0; i < 10; i++) {
  if (i == 5) {
    continue; // 中断当前循环,进入下一次循环
  } else if (i == 8) {
    break; // 结束当前层级循环
  }
}

while 循环

int i = 0;
while (i < 10) {
  vec4 test = vec4(1.0, 0.0, 0.0, 1.0);
  i++;
}

do-while 循环

int i = 0;
do {
 vec4 test = vec4(1.0, 0.0, 0.0, 1.0);
 i++;
} while(i < 10)

discard 舍弃

常用于其他控制语句中,可将不符合条件的片元舍弃,仅用于片元着色器

void main() {
  // 1.计算片元坐标距离渲染点中心的距离
	float r = distance(gl_PointCoord, vec2(0.5, 0.5));
  // 2.距离几何中心半径 < 0.5 的片元舍弃
  if(r < 0.5){
    discard;
  }
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

8.数组

语法

数组元素的数据类型 数组变量名[数组元素个数];

定义

// 1.浮点型数组 + 长度 100
float arr1[100];

// 2.浮点型向量数组 + 长度 20
vec3 arr2[20];

// 3.全局变量 + 浮点型数组 + 长度 3
uniform float num[3];

// 4.全局变量 + 结构体数组 + 长度 2
struct DirectionalLight {
  vec3 direction;
  vec4 color;
};
uniform DirectionalLight light[2];
  • 赋值

    // 1.普通数组 + 初始化赋值
    float num[3] = float[3](1.0, 2.0, 3.0);
    
    // 2.普通数组 + 索引赋值
    num[0]=3.0;
    
    // 3.结构体数组 + 索引赋值
    DirectionalLight light[2];
    light[0].direction = vec3(0.0);
    light[0].color = vec4(1.0);
    
  • 取值

    // 1.普通数组取值
    num[2]; // -> 3.0
    
    // 2.结构体数组取值
    light[0].color; // -> vec4(1.0, 1.0, 1.0, 1.0)
    

9.函数

注意

GLSL 的函数不支持递归。

语法

返回值类型 函数名(<参数传递方式> 参数类型 参数名) {

}
参数传递方式处理方式是否可修改内部形参是否会影响外部实参拷贝次数
in复制实参数据到形参,默认的参数传递方式1 次
const in复制实参数据到形参1 次
out函数执行后将形参值复制到实参1 次
inout复制实参数据到形参,函数执行后将内部形参值复制到实参2 次

定义

// 1.定义设置分量 x 的函数
float func(out vec3 point) {
  point.x = 2.0;
  return point.x * point.x;
}

void main() {
  vec3 size = vec3(1.0, 0.0, 0.0);
  // 2.执行该函数后,size.x 被改变
  func(size);
  gl_PointSize= size.x;
  gl_Position = vec4(0.0,0.0,0.0,1.0);
}

重载

GLSL 支持函数重载,可以为同名函数定义不同的参数及类型,在调用时编译器会根据参数列表自动判断具体调用函数。

// 1.定义 test 函数重载
vec2 test(vec2 a) {
  return a;
}
vec3 test(vec3 a) {
  return a;
}
vec4 test(vec4 a) {
  return a;
}

// 2.编译器自动判断调用:vec3 test(vec3 a)
vec3 b = vec3(1.0);
b = test(b);

10.编译预处理

注意

一种文本替换机制,没有类型检查和作用域限制,在编译预处理阶段会自动将宏转换成对应代码,因此不需要分号。

#define 宏定义

// 1.定义宏变量
#define PI 3.14159265359

// 2.定义宏函数
#define add(a,b)a+b

#if、#ifdef、#endif 宏条件

// 1.判断宏定义是否存在:如果存在宏定义 USE_COLOR,则保留 #ifdef 到 #endif 之间的代码
#ifdef USE_COLOR
	color.y = USE_COLOR;
#endif

// 2.判断条件是否成立:如果 10 > 0,则保留 #if 到 #endif 之间的代码
#if 10 > 0
	vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
#endif

#include 宏引入

// 1.引入外部 glsl 文件
#include <common>
#include <color>

// 2.引入 npm 包中的 glsl 文件
#include "/node_modules/lygia/generative/cnoise.glsl"

// 编译预处理后等于在当前文件中注入下面代码
float alpha = 0.5; // common.glsl 中定义的变量
uniform vec3 color; // color.glsl 中定义的变量
float cnoise() { } // lygia 中定义的柏林噪声函数
此文档贡献者: 嗷大张