WebGL从零开始(五)—— 复习下GLSL ES 语言的语法
GLSL ES(OpenGL ES Shading Language)是 WebGL 的核心,它的语法类似 C/C++,但针对 GPU 并行计算做了很多特殊限制。
# 1.打印向量的方法
GLSL 运行在 GPU 上,没有 console.log 或 print 这样的控制台输出函数。
# 常用调试方法:
- 颜色可视化(最常用):将向量(如位置、法线)直接赋值给 gl_FragColor。例如 gl_FragColor = vec4(normal, 1.0);,通过观察屏幕上的颜色来验证数据是否正确。
- 编码到 Alpha 通道:如果数据超出 0~1 范围,可以将其映射到颜色空间,或者利用 Alpha 通道传递数据,在 JS 端通过 readPixels 读取。
- 条件高亮:使用 if 判断特定条件,满足时输出高亮颜色(如红色),不满足输出黑色。
颜色法:通过将向量赋值给片元着色器,然后通过抓取像素的方法获取上面的 rgba 值,即可获取到,示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLSL语法测试</title>
<style>
html,
body {
padding: 0;
margin: 0;
box-sizing: border-box;
overflow: hidden;
}
#canvas {
font-size: small;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vertexSharderSource" type="notjs" >
void main() {
gl_Position = vec4(0,0,0,1);
gl_PointSize = 512.0;
}
</script>
<script id="fragmentShaderSource" type="notjs" >
precision mediump float;
vec4 v = vec4(1,2,3,4) + vec4(5,6,7,8);
void main() {
gl_FragColor = v/255.0; // 因为 gl_FragColor 中1个单位的分量就相当于 画布中255的分量值,所以要除以 255
}
</script>
<script type="module">
import { initProgram } from '../utils/utils.js'
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext('webgl');
// 获取着色器代码
const vsShaderSource = document.getElementById('vertexSharderSource').innerText;
const fsShaderSource = document.getElementById('fragmentShaderSource').innerText;
// 初始化着色器和着色程序
initProgram(gl, vsShaderSource, fsShaderSource);
// 声明颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
// 像素容器
const pixel = new Uint8Array(4)
// 抓取像素 readPixels(x,y,width, height, format, type, pixels, dstOffset, Optional)
gl.readPixels(canvas.width/2, canvas.height/2, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
console.log(pixel);
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
由于目标点的坐标是 gl_Position = vec4(0,0,0,1); 在画布中心,readPixels 方法的 x,y就是canvas.width/2, canvas.height/2;
然后取 1 个单位的像素,readPixels 方法的 width, height就是 1;
这里获取 RGBA 属性,所以 format 用的 gl.RGBA;
由于用的 Uint8Array 存取像素的颜色值,则对应的 type 值为 gl.UNSIGNED_BYTE
打印多个向量的值:通过不同层级的渲染不同颜色,然后抓取对应距离的像素。
# 2.变量
# 基础
GLSL 是强类型语言,不支持隐式类型转换(例如 float a = 1; 会报错,必须写 1.0)
- 基本类型:
void,bool,int,float。 - 向量类型:
vec2,vec3,vec4(浮点),ivec(整型),bvec(布尔)。 - 矩阵类型:
mat2,mat3,mat4。 - 变量限定符:
attribute/in:顶点着色器专属,接收来自 CPU 的顶点数据(每个顶点不同)。uniform:全局变量,接收 CPU 传来的批次数据(如 MVP 矩阵、时间,一次绘制中所有顶点相同)。varying/out&in:用于顶点着色器向片元着色器传递数据,GPU 会自动进行插值。
# 类型转换
- 浮点转整数:
int(float),如int a = int(1.0); - 布尔转整数:
int(bool),如int a = int(true); - 整数转浮点:
float(int),如float b = float(1); - 布尔转浮点:
float(bool),如float b = float(true); - 整数转布尔:
bool(int),如bool c = bool(1); - 浮点转布尔:
bool(float),如bool c = bool(0.0);
# 3.运算符与 Swizzling(分量重组)
# 基础运算
+, -, *, / 在向量之间是逐分量(Component-wise)进行的。
例如 vec3(1,2,3) * vec3(4,5,6) 结果是 vec3(4,10,18)。
# Swizzling(极其重要)
通过字母快速提取或重组向量分量。
- 坐标集:x, y, z, w
- 颜色集:r, g, b, a
- 纹理集:s, t, p, q 示例:
vec4 color = vec4(1.0, 0.5, 0.2, 1.0);
vec3 rgb = color.rgb;
vec4 swapped = color.bgra;
2
3
# 4.for循环
由于 GPU 需要并行执行,GLSL ES 对 for 循环有严格限制(为了支持编译器将其展开)
- 循环变量必须在初始化表达式中声明,且只能是
int或float。 - 条件表达式必须是循环变量与常量的比较(如
i < 10)。 - 循环表达式只能是
i++,i--,i += 常量,i -= 常量。 - 循环体内不能修改循环变量。
注:WebGL 1.0 (GLSL ES 1.0) 不支持 while 和 do-while。
# 5.函数
定义方式与 C 语言类似:返回类型 函数名(参数) { ... }。 参数修饰符:in(默认,传入), out(传出), inout(传入且可修改)。 支持重载(同名函数,参数类型/数量不同)。
限制:不能递归;返回的结构体中不能包含数组。
# 6.作用域与生命周期
- 局部作用域:在
{}内声明的变量,离开块即销毁。 - 全局作用域:在函数外声明,整个 Shader 文件可见。
- GPU 生命周期:
uniform在一次drawCall中保持不变;attribute随顶点变化;varying在光栅化阶段由顶点值插值生成。