4、着色器基础WGSL:WGSL语法入门、数据类型、函数定义、与GLSL的对比
好,咱们今天来聊聊WGSL。说实话,我第一次接触WGSL的时候,心里是有点抵触的——毕竟GLSL用了那么多年,凭什么要换?但真正上手写了几个Shader之后,我发现WGSL的设计其实挺讲究的。它不像GLSL那样“野路子”,而是更像一门正经的编程语言。
嗯,咱们今天的目标很明确:让你能看懂WGSL代码,能自己写简单的Shader,并且知道它和GLSL到底差在哪。
4.1 WGSL语法入门:先跑起来再说
WGSL的全称是WebGPU Shading Language。它是WebGPU的专属着色器语言。你想想看,WebGPU要跨平台、要安全、要高效,那它的着色器语言肯定不能太随意。
先看一个最简单的例子——一个啥也不干的片段着色器:
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
这段代码干了三件事:
@fragment是一个装饰器,告诉编译器这是个片段着色器入口fn main()定义了一个函数,名字叫main-> @location(0) vec4<f32>表示返回值是4维浮点向量,绑定到location 0
我个人习惯把这种结构叫做“装饰器+函数签名+函数体”三段式。你记住这个模式,后面所有Shader都长这样。
-> void。但片段着色器必须返回颜色值,所以这里不能省略。
4.2 数据类型:比GLSL严谨多了
WGSL的数据类型,说白了就是“把GLSL的隐式转换全砍了”。我在项目中遇到过好几次因为类型不匹配导致的编译错误,后来学乖了——所有数字都老老实实写类型。
常用的标量类型:
| 类型 | 说明 | GLSL对应 |
|---|---|---|
i32 |
32位有符号整数 | int |
u32 |
32位无符号整数 | uint |
f32 |
32位浮点数 | float |
bool |
布尔值 | bool |
向量类型:
vec2<f32>、vec3<f32>、vec4<f32>- 也支持
vec2<i32>、vec4<u32>等
矩阵类型:
mat2x2<f32>、mat3x3<f32>、mat4x4<f32>- 注意:WGSL里矩阵是列主序的,和GLSL一样
double 类型!所有浮点数都是f32。我曾经试图传一个double进去,结果编译直接报错——嗯,从那以后我再也不在WGSL里用双精度了。
4.3 函数定义:有模有样
WGSL的函数定义比GLSL规范得多。它借鉴了Rust的语法风格,每个参数都要写类型,返回值也要写类型。
基本语法:
fn 函数名(参数名: 参数类型) -> 返回值类型 {
// 函数体
return 值;
}
举个例子,一个计算光照强度的函数:
fn calculateLight(normal: vec3<f32>, lightDir: vec3<f32>) -> f32 {
let dotProduct = dot(normal, lightDir);
return max(dotProduct, 0.0);
}
你想想看,GLSL里写 float calculateLight(vec3 normal, vec3 lightDir) 虽然也行,但类型检查没那么严格。WGSL是强类型,参数类型写错了直接编译不过。
函数可以嵌套调用,也可以递归——但我不建议你在Shader里写递归,性能太差了。
4.4 与GLSL的对比:到底差在哪?
好,咱们来做个对比。我整理了一张表,把最核心的区别列出来:
| 特性 | WGSL | GLSL |
|---|---|---|
| 类型声明 | var x: f32 = 1.0; |
float x = 1.0; |
| 常量声明 | const PI: f32 = 3.14; |
const float PI = 3.14; |
| 向量构造 | vec3<f32>(1.0, 0.0, 0.0) |
vec3(1.0, 0.0, 0.0) |
| 内置变量 | @builtin(position) |
gl_Position |
| 输入输出 | @location(0) 装饰器 |
layout(location=0) |
| 类型转换 | 显式转换,如 f32(1) |
隐式转换较多 |
说白了,WGSL就是GLSL的“严格版”。它砍掉了所有模糊不清的地方,让你写代码的时候必须想清楚每个变量的类型和用途。
举个例子,GLSL里你可以写 vec3 color = 1.0;,编译器会自动帮你展开成 vec3(1.0, 1.0, 1.0)。但在WGSL里,你必须写 let color: vec3<f32> = vec3<f32>(1.0, 1.0, 1.0);。刚开始觉得麻烦,但习惯了之后发现——嗯,确实少了很多莫名其妙的bug。
4.5 避坑指南:我踩过的那些坑
最后分享几个我实际项目中遇到的坑:
- 类型转换要显式写:
let a: f32 = 1;会报错,必须写成let a: f32 = f32(1); - 数组索引从0开始:这个和GLSL一样,但WGSL的数组越界会直接导致Shader崩溃
- 不要用
++和--:WGSL不支持自增自减运算符,要用i = i + 1; - 注意
let和var的区别:let声明的是不可变变量,var才是可变的
let 声明了一个变量,然后试图修改它,结果编译报错。找了半天才发现是 let 的问题。从那以后,我所有可变变量都用 var,不可变的才用 let。
好了,这一章的内容就到这里。WGSL的语法其实不难,关键是适应它的“严谨风格”。下一章咱们会深入讲讲WGSL的缓冲区绑定和资源管理,那才是真正体现WebGPU威力的地方。