WGSL 基础
WGSL(WebGPU Shading Language)是 WebGPU 的着色语言。顶点着色器、片元着色器和计算着色器都用它编写,再交给 GPUDevice.createShaderModule() 编译。
WGSL 看起来接近 Rust 和 C++,但规则更窄:没有递归,入口函数必须用属性标出来,送进管线的数据也必须声明地址空间。
一个着色器模块可以包含多个函数。管线只调用你指定的入口:
| 属性 | 阶段 | 作用 |
|---|---|---|
@vertex | 顶点着色器 | 每个顶点调用一次,写出裁剪空间位置 |
@fragment | 片元着色器 | 每个被光栅化的像素调用一次,写出颜色 |
@compute | 计算着色器 | 按工作组并行执行,不经过光栅化 |
入口名字是自定义的。本站的图形示例沿用 vs_main 和 fs_main,计算着色器沿用 main。规范允许模块里某个阶段只有一个入口时省略 entryPoint,示例里仍然写上,方便对照。
标量是 bool、i32、u32 和 f32。向量、矩阵用元素个数和元素类型组成:
vec2f // vec2<f32> 的别名vec3fvec4fvec2u // vec2<u32>vec2i // vec2<i32>mat4x4farray<vec2f, 3> 是 3 个 vec2f 的定长数组。存储缓冲里常见的 array<f32> 不写长度,表示运行时长度。
构造和重组:
let position = vec2f(0.0, 0.5);let clip = vec4f(position, 0.0, 1.0); // xy 来自 position,z = 0,w = 1let xy = clip.xy;let 绑定后不能改,var 可以改,const 必须能在编译期算出来。
入口函数怎么拿到数据
Section titled “入口函数怎么拿到数据”顶点着色器最简单的输入是内置变量 @builtin(vertex_index),类型是 u32,表示这次调用是第几个顶点。三角形示例就靠它从数组里取出坐标。
位置必须写到 @builtin(position),类型是 vec4f。xyz 是裁剪坐标,w 一般先写 1.0。之后 GPU 会做透视除法,得到 NDC(归一化设备坐标)。WebGPU 的 NDC 里,x 从左到右是 -1 到 1,y 从下到上是 -1 到 1,z 从近到远是 0 到 1。
片元着色器的返回值用 @location(0) 标出,对应渲染目标里的第 0 个颜色附件。返回 vec4f(r, g, b, a),分量通常在 0 到 1。
把顶点数据送到片元
Section titled “把顶点数据送到片元”只返回颜色的片元着色器画不出渐变。顶点着色器可以在自定义 @location 上多写一份数据,光栅器会按三角形插值,片元着色器再用同一个 location 读出来。
struct VertexOut { @builtin(position) position: vec4f, @location(0) color: vec3f,}
@vertexfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOut { var positions = array<vec2f, 3>( vec2f(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5), ); var colors = array<vec3f, 3>( vec3f(1.0, 0.25, 0.2), vec3f(0.2, 0.85, 0.35), vec3f(0.25, 0.45, 1.0), );
var out: VertexOut; out.position = vec4f(positions[vertexIndex], 0.0, 1.0); out.color = colors[vertexIndex]; return out;}
@fragmentfn fs_main(@location(0) color: vec3f) -> @location(0) vec4f { return vec4f(color, 1.0);}@location 的编号要和管线两边对上。这里片元入口的 @location(0) 就是顶点输出的颜色,和帧缓冲附件的 @location(0) 不是同一个命名空间,只是编号碰巧都从 0 开始。
着色器外面的资源必须声明放在哪里:
| 写法 | 用途 |
|---|---|
var<uniform> | 只读的小块参数,例如颜色、矩阵 |
var<storage, read> | 只读的大块数据 |
var<storage, read_write> | 计算着色器可写的存储缓冲 |
var<workgroup> | 同一个工作组内共享的计算数据 |
texture_2d<f32> / sampler | 采样纹理 |
texture_storage_2d<rgba8unorm, write> | 计算着色器写入的存储纹理 |
这些变量还要写上 @group 和 @binding,告诉 WebGPU 它们对应绑定组里的哪一项。下一章先把函数放进渲染管线,绑定组放到缓冲区之后。
下面这块画布使用上面的着色器。三个顶点分别是红、绿、蓝,三角形内部是插值出来的颜色。
顶点颜色插值
着色器要放进 GPURenderPipeline 才会被执行。WGSL 的完整语法见 WGSL 规范。