WebGPU 与 Node 材质
目的与范围
本文档记录 WebGPU 渲染后端及其基于节点的材质系统。WebGPU 提供了现代 GPU API,其着色器生成与 WebGL 根本不同。有关传统 WebGL 渲染管线的信息,请参阅 WebGL 渲染管线。有关 WebGL 着色器模板和编译,请参阅着色器程序与 ShaderLib。
架构概述
WebGPU 后端用基于节点的材质系统替换了 WebGL 基于模板的着色器系统(ShaderLib),其中着色器由可重用的节点图组成。核心编译流程通过 WGSLNodeBuilder 类将 Node 实例转换为 WGSL 着色器代码。
图表:节点到着色器的编译管线
编译过程:
NodeMaterial将着色器定义为Node实例图WGSLNodeBuilder.build()遍历图,确定类型和依赖关系- 资源分配创建 uniform 缓冲区、存储缓冲区和绑定组布局
getCode()发出具有正确语法的最终 WGSL 着色器文本
实现位于 WebGPU 包中。节点材质系统从类型化的节点图组合着色器,而不是字符串连接。
构建产物和入口点
Three.js 通过独立的构建产物分发 WebGPU,这些产物具有不同的入口点:
|| 构建文件 | 包导入 | 目的 | 依赖项 | ||-------------------------|--------------------------------|--------------------------|-------------------| || three.module.js | import * from 'three' | WebGL 渲染器 | ShaderLib | || three.webgpu.js | import * from 'three/webgpu' | WebGPU 后端 | Node 系统 | || three.webgpu.nodes.js | 内部 | 完整节点实现 | 完整图 | || three.tsl.js | import * from 'three/tsl' | TSL 函数 | 引用 webgpu |
package.json 导出定义了这些入口点:
"exports": {
".": "./build/three.module.js",
"./webgpu": "./build/three.webgpu.js",
"./tsl": "./build/three.tsl.js"
}构建过程使用 Rollup,为每个目标设置单独的输入配置。
NodeMaterialObserver
NodeMaterialObserver 类确定渲染对象是否需要在渲染前刷新材质。它跟踪影响着色器生成的属性更改。
图表:NodeMaterialObserver 更改检测流程
观察器跟踪 refreshUniforms 数组中定义的 64 个材质属性:
纹理贴图:alphaMap、aoMap、bumpMap、clearcoatMap、clearcoatNormalMap、displacementMap、emissiveMap、envMap、gradientMap、iridescenceMap、iridescenceThicknessMap、lightMap、map、matcap、metalnessMap、normalMap、roughnessMap、sheenColorMap、sheenRoughnessMap、specularColorMap、specularIntensityMap、specularMap、transmissionMap、anisotropyMap
标量属性:alphaTest、anisotropy、anisotropyRotation、aoMapIntensity、clearcoat、clearcoatRoughness、dispersion、emissiveIntensity、envMapIntensity、ior、iridescence、iridescenceIOR、lightMapIntensity、metalness、opacity、roughness、sheen、shininess、specularIntensity、thickness、transmission
向量属性:attenuationColor、clearcoatNormalScale、color、emissive、normalScale、sheenColor、specular、specularColor
_lightsCache WeakMap 按渲染 ID 缓存灯光数据,以便在灯光设置未更改时避免重新计算。
节点材质系统
节点材质从连接的节点组合着色器,而不是使用固定模板。每个节点代表一个着色器操作。
核心节点架构
图表:节点类层次结构
每个 Node 子类实现:
build( builder ):分析依赖关系并分配资源generate( builder, output ):为此节点发出着色器代码getNodeType( builder ):返回 WGSL/GLSL 类型(例如'vec3'、'float')
NodeBuilder 基类
NodeBuilder 是着色器代码生成的抽象基类。它管理节点遍历、资源分配和代码发射。平台特定的构建器(WGSLNodeBuilder、GLSLNodeBuilder)扩展它以针对不同的着色器语言。
核心构建器架构
图表:NodeBuilder 内部结构
关键方法:
build():主入口点。从材质输出节点(colorNode、positionNode等)开始遍历节点图。返回着色器代码字符串。buildCode( node ):通过调用node.build( builder )处理单个节点。将结果存储在缓存中以防止重复处理。getVarFromNode( node, type ):为节点分配唯一的变量名。顺序编号(例如temp0、temp1、varying0)确保无冲突。getDataFromNode( node ):检索节点的缓存数据,按渲染 ID 存储。支持每帧缓存。format( code, fromType, toType ):生成类型转换代码(例如float到vec3变为vec3( value ))。
类型推断:构建器从节点输出推断 WGSL/GLSL 类型。节点通过 getNodeType() 声明其输出类型,实现自动类型检查。
WGSLNodeBuilder 实现
WGSLNodeBuilder 扩展 NodeBuilder 以生成具有 WebGPU 特定功能的 WGSL 着色器代码。
WGSL 类型映射
构建器维护类型转换表:
|| 节点类型 | WGSL 类型 | 示例 || ||-----------|---------------|----------------------------------|| || float | f32 | var x: f32 = 1.0; || || vec2 | vec2<f32> | var uv: vec2<f32> = vec2(0.5); || || vec3 | vec3<f32> | var pos: vec3<f32>; || || vec4 | vec4<f32> | var color: vec4<f32>; || || int | i32 | var index: i32 = 0; || || uint | u32 | var id: u32; || || mat3 | mat3x3<f32> | var rot: mat3x3<f32>; || || mat4 | mat4x4<f32> | var mvp: mat4x4<f32>; || || bool | bool | var flag: bool = true; ||
绑定组布局
WebGPU 将资源组织到绑定组中。构建器按顺序分配绑定:
图表:WebGPU 绑定组组织
绑定分配遵循以下规则:
- 每个
NodeUniformBuffer接收一个绑定槽位 NodeSampler和NodeSampledTexture配对(采样器 + 纹理)NodeStorageBuffer使用storage<access>并带有 read/write/read_write 模式- 绑定组按更新频率组织(帧 vs 材质 vs 计算)
代码生成管线
WGSLNodeBuilder 编译过程:
图表:WGSLNodeBuilder 编译阶段
设置阶段:构造函数初始化定义 WebGPU 能力的 supports 对象:
supports: {
instance: true,
swizzleAssign: false,
storageBuffer: true
}构建阶段:每个着色器阶段在入口节点上调用 buildCode():
- 顶点:
positionNode、normalNode - 片元:
colorNode、alphaNode、outputNode - 计算:
computeNode
分配阶段:解析器方法将抽象节点转换为具体资源:
_parseVars():临时变量(var temp0: vec3<f32>;)_parseUniforms():Uniform 缓冲区和绑定_parseAttributes():顶点缓冲区布局_parseVaryings():阶段间数据(@location(0) vUV: vec2<f32>)
发射阶段:使用正确语法和装饰器生成最终的 WGSL 文本。
节点到 WGSL 转换示例
示例 1:简单颜色节点
节点图:
const colorNode = vec3( 1.0, 0.5, 0.0 );
material.colorNode = colorNode;生成的 WGSL:
@fragment
fn main() -> @location(0) vec4<f32> {
var color: vec3<f32> = vec3<f32>( 1.0, 0.5, 0.0 );
return vec4<f32>( color, 1.0 );
}示例 2:纹理采样
节点图:
import { texture, uv } from 'three/tsl';
material.colorNode = texture( diffuseMap, uv() );生成的 WGSL:
@group(1) @binding(0) var sampler0: sampler;
@group(1) @binding(1) var texture0: texture_2d<f32>;
struct Varyings {
@location(0) vUV: vec2<f32>
}
@fragment
fn main( varyings: Varyings ) -> @location(0) vec4<f32> {
var color: vec4<f32> = textureSample( texture0, sampler0, varyings.vUV );
return color;
}示例 3:算术运算
节点图:
import { add, mul, positionLocal, normalLocal } from 'three/tsl';
material.positionNode = add( positionLocal, mul( normalLocal, 0.1 ) );生成的 WGSL:
@vertex
fn main(
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>
) -> @builtin(position) vec4<f32> {
var temp0: vec3<f32> = normal * 0.1;
var temp1: vec3<f32> = position + temp0;
return vec4<f32>( temp1, 1.0 );
}示例 4:自定义函数
节点图:
import { Fn, vec3, sin, float } from 'three/tsl';
const wave = Fn( ( [ pos, time ] ) => {
const offset = sin( add( pos.y, time ) );
return vec3( pos.x, add( pos.y, offset ), pos.z );
} );
material.positionNode = wave( positionLocal, time );生成的 WGSL:
fn tsl_wave( pos: vec3<f32>, time: f32 ) -> vec3<f32> {
var offset: f32 = sin( pos.y + time );
return vec3<f32>( pos.x, pos.y + offset, pos.z );
}
@vertex
fn main( @location(0) position: vec3<f32> ) -> @builtin(position) vec4<f32> {
var result: vec3<f32> = tsl_wave( position, uniforms.time );
return vec4<f32>( result, 1.0 );
}TSL(Three Shading Language)
TSL 提供了用于编写着色器的基于 JavaScript 的 API,这些着色器可编译为节点图。它提供了功能组合,无需直接编写 GLSL 或 WGSL。
TSL 核心抽象
图表:TSL 核心抽象和生成的代码
TSL 函数类别
数学运算(MathNode、OperatorNode):
- 算术:
add()、sub()、mul()、div()、mod()、pow() - 三角函数:
sin()、cos()、tan()、asin()、acos()、atan()、atan2() - 常用:
abs()、sign()、floor()、ceil()、fract()、sqrt()、exp()、log() - 插值:
mix()、smoothstep()、step()、clamp()、saturate() - 向量:
dot()、cross()、length()、normalize()、reflect()、refract()、faceforward()
纹理运算(TextureNode、TextureSizeNode):
- 采样:
texture()、textureLoad()、cubeTexture()、texture3D() - 属性:
textureSize()、textureBias()、textureLevel() - 存储:
textureStore()(仅计算着色器)
材质属性(MaterialNode、MaterialReferenceNode):
- PBR:
materialRoughness、materialMetalness、materialClearcoat、materialSheen - 光学:
materialIOR、materialTransmission、materialThickness、materialAttenuationColor - 表面:
materialColor、materialEmissive、materialOpacity、materialAlphaTest - 纹理:
materialNormalMap、materialAOMap、materialLightMap
灯光函数(LightsNode、LightingModel):
- BRDF:
BRDF_GGX()、BRDF_Lambert()、D_GGX()、F_Schlick() - 灯光类型:
PointLightNode、DirectionalLightNode、SpotLightNode、AmbientLightNode - 工具:
getDistanceAttenuation()、getSpotAttenuation()、punctualLightIntensityToIrradianceFactor()
访问器(PositionNode、NormalNode、UVNode):
- 几何体:
positionLocal、positionWorld、positionView、positionViewDirection - 法线:
normalLocal、normalWorld、normalView、normalGeometry、transformedNormalView - 坐标:
uv()、uv2()、uvw()(3D 纹理) - 摄像机:
cameraPosition、cameraViewMatrix、cameraProjectionMatrix、cameraNormalMatrix
控制流(IfNode、LoopNode、SwitchNode):
- 条件:
If( condition, trueNode, falseNode )、select( condition, a, b ) - 循环:
Loop( callback )、Break、Continue - 分支:
Switch( node, caseMap ) - 提前退出:
Return( value )、Discard
常量(直接导出):
- 数学:
PI、PI2、HALF_PI、TWO_PI、EPSILON、INFINITY - 预处理器:
NodeAccess、NodeShaderStage、NodeType、NodeUpdateType
TSL 模块提供着色器编写的声明式 API。它编译为构建器处理为 WGSL/GLSL 的 Node 实例。
TSL 到节点图示例
TSL 代码编译为构建器处理的 Node 实例:
TSL 输入:
import { Fn, vec3, float, mul, add, sin } from 'three/tsl';
const wobble = Fn( ( [ position, time ] ) => {
const offsetX = mul( sin( add( position.y, time ) ), 0.5 );
const offsetZ = mul( sin( add( position.x, time ) ), 0.5 );
return vec3( add( position.x, offsetX ), position.y, add( position.z, offsetZ ) );
} );等效节点图:
FunctionNode {
name: 'wobble',
inputs: [
ParameterNode { type: 'vec3', name: 'position' },
ParameterNode { type: 'float', name: 'time' }
],
output: JoinNode {
nodes: [
OperatorNode { op: '+', a: position.x, b: ... },
position.y,
OperatorNode { op: '+', a: position.z, b: ... }
]
}
}生成的 WGSL:
fn tsl_wobble( position: vec3<f32>, time: f32 ) -> vec3<f32> {
var offsetX: f32 = sin( position.y + time ) * 0.5;
var offsetZ: f32 = sin( position.x + time ) * 0.5;
return vec3<f32>( position.x + offsetX, position.y, position.z + offsetZ );
}WebGPU 特定功能
多渲染目标(MRT)
WebGPU 支持在单个渲染过程中同时渲染到多个纹理,通常用于延迟渲染。
图表:多渲染目标流程
MRT 实现高效的 G-Buffer 生成以用于延迟渲染。片元着色器在单次通道中输出到多个目标。
计算着色器
WebGPU 为通用 GPU 计算提供原生计算着色器支持:
|| 功能 | 描述 | 常见用例 || ||-------------------|--------------------------|--------------------------------------------|| || 存储缓冲区 | 读/写 GPU 内存 | 粒子系统、物理模拟 || || 工作组 | 并行执行单元 | 大规模并行(数千个线程) || || 共享内存 | 工作组本地内存 | 快速线程间通信 || || 原子运算 | 线程安全操作 | 同步、计数器 || || 间接调度 | GPU 驱动调度 | 动态工作负载大小 ||
代码库中的计算着色器示例:
webgpu_compute_birds- 具有空间分区的群体模拟webgpu_compute_cloth- 带有约束的布料物理webgpu_compute_particles- 粒子系统更新webgpu_compute_particles_fluid- SPH 流体模拟webgpu_compute_texture- 程序化纹理生成webgpu_compute_texture_3d- 3D 纹理计算webgpu_compute_water- 水面模拟webgpu_compute_sort_bitonic- GPU 排序算法
存储缓冲区和结构化数据
存储缓冲区支持具有读/写能力的结构化数据访问:
图表:存储缓冲区数据流
存储缓冲区支持:
- 大数据集(千兆字节)
- 随机读/写访问
- 结构化数组类型
- 原子运算
- 间接绘制参数
时间戳查询
WebGPU 通过时间戳查询提供精确的 GPU 计时以用于性能分析:
// 从 three.core 创建时间戳查询
const query = new TimestampQuery();
// 测量计算着色器
renderer.compute( particleComputeNode, query );
const gpuTime = query.getResult(); // 纳秒时间戳查询支持测量:
- 渲染过程持续时间
- 计算着色器执行时间
- 单个绘制调用开销
- 帧级 GPU 性能
- 管线阶段分析
坐标系差异
WebGPU 使用与 WebGL 不同的坐标约定:
|| 方面 | WebGL | WebGPU | 影响 || ||----------------|-------------------------|-------------------------|---------------|| || 裁剪空间 Y | -1(底部)到 +1(顶部) | -1(底部)到 +1(顶部) | 相同 || || 裁剪空间 Z | -1(近)到 +1(远) | 0(近)到 +1(远) | 不同 || || 纹理原点 | 左下角 | 左上角 | 不同 || || NDC 左手系 | 右手 | 左手 | 不同 || || 缠绕顺序 | CCW = 正面 | CCW = 正面 | 相同 ||
渲染器使用坐标系常量:
WebGLCoordinateSystem = 0WebGPUCoordinateSystem = 1
Three.js 通过投影矩阵和纹理采样中的内部转换自动处理这些差异。
材质编译和缓存
WebGPU 后端使用复杂的缓存来避免不必要的着色器重新编译:
图表:材质编译和缓存管线
缓存系统考虑:
- 材质属性:
refreshUniforms数组中的所有值 - 着色器定义:功能标志(#define 指令)
- 几何体布局:属性类型和顶点格式
- 灯光配置:活动灯光的数量和类型
- 渲染上下文:渲染目标格式、多重采样
跟踪属性的更改触发观察器检查。未跟踪的属性更新 uniform 而无需重新编译。
WebGPURenderer 集成
节点材质系统通过几个管理类与 WebGPURenderer 集成,这些类处理编译、缓存和 GPU 资源绑定。
图表:WebGPURenderer 类集成
渲染流程:
场景遍历:
WebGPURenderer.render()调用处理渲染列表(不透明、透明、计算)的renderScene()后端访问:
_getBackend()返回管理GPUDevice的WebGPUBackend实例材质处理:对于每个渲染对象,后端检索或编译管线:
NodeMaterialObserver.checkRefresh()检测属性更改WGSLNodeBuilder.build()生成 WGSL 着色器代码- 按材质哈希查找管线缓存
管线创建:在缓存未命中时,调用
device.createRenderPipeline()并带有:- 顶点/片元着色器模块
- 顶点缓冲区布局
- 绑定组布局
- 渲染目标格式
资源绑定:BindGroup 管理器组织资源:
- 组 0:帧 uniform(摄像机、时间)
- 组 1:材质 uniform(颜色、纹理)
- 组 2:对象 uniform(modelMatrix)
命令编码:
GPUCommandEncoder记录绘制命令,然后queue.submit()在 GPU 上执行
E2E 测试覆盖
WebGPU 后端在 E2E 测试套件中具有广泛的示例覆盖:
|| 类别 | 示例数量 | 测试状态 || ||-----------------|----------------|-----------------------------------|| || 核心功能 | ~180 个示例 | 在 CI 中测试 || || 计算着色器 | ~15 个示例 | 已排除(需要原生 WebGPU) || || 后处理 | ~25 个示例 | 测试,但有例外 || || TSL 示例 | ~10 个示例 | 已测试 || || 长时间运行 | ~10 个示例 | 已排除(超时 >1分钟) ||
排除列表(从自动化测试中排除):
长时间运行(> 1 分钟):
webgpu_parallax_uv(11 分钟)webgpu_cubemap_adjustments(9 分钟)webgpu_cubemap_mix(2 分钟)webgpu_water(1 分钟)
仅计算(需要原生 WebGPU):
webgpu_compute_audio、webgpu_compute_birds、webgpu_compute_clothwebgpu_compute_particles_fluid、webgpu_compute_reducewebgpu_compute_sort_bitonic、webgpu_compute_texturewebgpu_compute_texture_3d、webgpu_compute_texture_pingpongwebgpu_compute_water、webgpu_struct_drawindirect
正在调查中:
webgpu_backdrop_water、webgpu_portal、webgpu_shadowmapwebgpu_postprocessing_ao、webgpu_postprocessing_ssgiwebgpu_test_memory、webgpu_tsl_vfx_flames
测试套件使用带 SwiftShader 的 Puppeteer 进行一致的无头渲染,比较截图时使用 0.1 像素阈值。
从 WebGL 迁移
从 WebGL 迁移到 WebGPU 时的主要差异:
|| 方面 | WebGL 方法 | WebGPU 方法 || ||-------------------|---------------------------------------|--------------------------------------------|| || 导入 | import * from 'three' | import * from 'three/webgpu' || || 材质系统 | ShaderLib 模板 | 节点图 || || 着色器语言 | GLSL ES 3.0 | WGSL || || 自定义着色器 | material.onBeforeCompile() | TSL 函数或自定义节点 || || 着色器材质 | ShaderMaterial、RawShaderMaterial | 带有节点的 NodeMaterial || || 多个目标 | 有限(WEBGL_draw_buffers) | 原生 MRT 支持 || || 计算 | 变换反馈或纹理 | 原生计算着色器 || || 坐标系 | WebGL 约定 | WebGPU 约定(自动处理) ||
迁移步骤:
- 更改入口点:将
'three'导入替换为'three/webgpu' - 更新材质:将
ShaderMaterial转换为具有等效节点图的NodeMaterial - 转换着色器:将 GLSL 自定义转换为 TSL 函数
- 调整坐标:删除手动 Z 轴翻转(由渲染器处理)
- 测试兼容性:在 Chrome/Edge 113+、Firefox Nightly 中验证
浏览器支持:
- Chrome/Edge 113+(稳定版)
- Firefox Nightly(开发中)
- Safari Technology Preview(实验性)