跳转到内容

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> 的别名
vec3f
vec4f
vec2u // vec2<u32>
vec2i // vec2<i32>
mat4x4f

array<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 = 1
let xy = clip.xy;

let 绑定后不能改,var 可以改,const 必须能在编译期算出来。

顶点着色器最简单的输入是内置变量 @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。

只返回颜色的片元着色器画不出渐变。顶点着色器可以在自定义 @location 上多写一份数据,光栅器会按三角形插值,片元着色器再用同一个 location 读出来。

struct VertexOut {
@builtin(position) position: vec4f,
@location(0) color: vec3f,
}
@vertex
fn 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;
}
@fragment
fn 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 规范。