Skip to content

WebGPU 与 Node 材质 ​

目的与范围 ​

本文档记录 WebGPU 渲染后端及其基于节点的材质系统。WebGPU 提供了现代 GPU API,其着色器生成与 WebGL 根本不同。有关传统 WebGL 渲染管线的信息,请参阅 WebGL 渲染管线。有关 WebGL 着色器模板和编译,请参阅着色器程序与 ShaderLib。

架构概述 ​

WebGPU 后端用基于节点的材质系统替换了 WebGL 基于模板的着色器系统(ShaderLib),其中着色器由可重用的节点图组成。核心编译流程通过 WGSLNodeBuilder 类将 Node 实例转换为 WGSL 着色器代码。

SVG
100%

图表:节点到着色器的编译管线

编译过程:

  1. NodeMaterial 将着色器定义为 Node 实例图
  2. WGSLNodeBuilder.build() 遍历图,确定类型和依赖关系
  3. 资源分配创建 uniform 缓冲区、存储缓冲区和绑定组布局
  4. 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 类确定渲染对象是否需要在渲染前刷新材质。它跟踪影响着色器生成的属性更改。

SVG
100%

图表: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 缓存灯光数据,以便在灯光设置未更改时避免重新计算。

节点材质系统 ​

节点材质从连接的节点组合着色器,而不是使用固定模板。每个节点代表一个着色器操作。

核心节点架构 ​

SVG
100%

图表:节点类层次结构

每个 Node 子类实现:

  • build( builder ):分析依赖关系并分配资源
  • generate( builder, output ):为此节点发出着色器代码
  • getNodeType( builder ):返回 WGSL/GLSL 类型(例如 'vec3'、'float')

NodeBuilder 基类 ​

NodeBuilder 是着色器代码生成的抽象基类。它管理节点遍历、资源分配和代码发射。平台特定的构建器(WGSLNodeBuilder、GLSLNodeBuilder)扩展它以针对不同的着色器语言。

核心构建器架构 ​

SVG
100%

图表: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 将资源组织到绑定组中。构建器按顺序分配绑定:

SVG
100%

图表:WebGPU 绑定组组织

绑定分配遵循以下规则:

  • 每个 NodeUniformBuffer 接收一个绑定槽位
  • NodeSampler 和 NodeSampledTexture 配对(采样器 + 纹理)
  • NodeStorageBuffer 使用 storage<access> 并带有 read/write/read_write 模式
  • 绑定组按更新频率组织(帧 vs 材质 vs 计算)

代码生成管线 ​

WGSLNodeBuilder 编译过程:

SVG
100%

图表: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 核心抽象 ​

SVG
100%

图表: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 支持在单个渲染过程中同时渲染到多个纹理,通常用于延迟渲染。

SVG
100%

图表:多渲染目标流程

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 排序算法

存储缓冲区和结构化数据 ​

存储缓冲区支持具有读/写能力的结构化数据访问:

SVG
100%

图表:存储缓冲区数据流

存储缓冲区支持:

  • 大数据集(千兆字节)
  • 随机读/写访问
  • 结构化数组类型
  • 原子运算
  • 间接绘制参数

时间戳查询 ​

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 = 0
  • WebGPUCoordinateSystem = 1

Three.js 通过投影矩阵和纹理采样中的内部转换自动处理这些差异。

材质编译和缓存 ​

WebGPU 后端使用复杂的缓存来避免不必要的着色器重新编译:

SVG
100%

图表:材质编译和缓存管线

缓存系统考虑:

  1. 材质属性:refreshUniforms 数组中的所有值
  2. 着色器定义:功能标志(#define 指令)
  3. 几何体布局:属性类型和顶点格式
  4. 灯光配置:活动灯光的数量和类型
  5. 渲染上下文:渲染目标格式、多重采样

跟踪属性的更改触发观察器检查。未跟踪的属性更新 uniform 而无需重新编译。

WebGPURenderer 集成 ​

节点材质系统通过几个管理类与 WebGPURenderer 集成,这些类处理编译、缓存和 GPU 资源绑定。

SVG
100%

图表:WebGPURenderer 类集成

渲染流程:

  1. 场景遍历:WebGPURenderer.render() 调用处理渲染列表(不透明、透明、计算)的 renderScene()

  2. 后端访问:_getBackend() 返回管理 GPUDevice 的 WebGPUBackend 实例

  3. 材质处理:对于每个渲染对象,后端检索或编译管线:

    • NodeMaterialObserver.checkRefresh() 检测属性更改
    • WGSLNodeBuilder.build() 生成 WGSL 着色器代码
    • 按材质哈希查找管线缓存
  4. 管线创建:在缓存未命中时,调用 device.createRenderPipeline() 并带有:

    • 顶点/片元着色器模块
    • 顶点缓冲区布局
    • 绑定组布局
    • 渲染目标格式
  5. 资源绑定:BindGroup 管理器组织资源:

    • 组 0:帧 uniform(摄像机、时间)
    • 组 1:材质 uniform(颜色、纹理)
    • 组 2:对象 uniform(modelMatrix)
  6. 命令编码: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_cloth
  • webgpu_compute_particles_fluid、webgpu_compute_reduce
  • webgpu_compute_sort_bitonic、webgpu_compute_texture
  • webgpu_compute_texture_3d、webgpu_compute_texture_pingpong
  • webgpu_compute_water、webgpu_struct_drawindirect

正在调查中:

  • webgpu_backdrop_water、webgpu_portal、webgpu_shadowmap
  • webgpu_postprocessing_ao、webgpu_postprocessing_ssgi
  • webgpu_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 约定(自动处理) ||

迁移步骤:

  1. 更改入口点:将 'three' 导入替换为 'three/webgpu'
  2. 更新材质:将 ShaderMaterial 转换为具有等效节点图的 NodeMaterial
  3. 转换着色器:将 GLSL 自定义转换为 TSL 函数
  4. 调整坐标:删除手动 Z 轴翻转(由渲染器处理)
  5. 测试兼容性:在 Chrome/Edge 113+、Firefox Nightly 中验证

浏览器支持:

  • Chrome/Edge 113+(稳定版)
  • Firefox Nightly(开发中)
  • Safari Technology Preview(实验性)