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 个材质属性:

纹理贴图alphaMapaoMapbumpMapclearcoatMapclearcoatNormalMapdisplacementMapemissiveMapenvMapgradientMapiridescenceMapiridescenceThicknessMaplightMapmapmatcapmetalnessMapnormalMaproughnessMapsheenColorMapsheenRoughnessMapspecularColorMapspecularIntensityMapspecularMaptransmissionMapanisotropyMap

标量属性alphaTestanisotropyanisotropyRotationaoMapIntensityclearcoatclearcoatRoughnessdispersionemissiveIntensityenvMapIntensityioriridescenceiridescenceIORlightMapIntensitymetalnessopacityroughnesssheenshininessspecularIntensitythicknesstransmission

向量属性attenuationColorclearcoatNormalScalecoloremissivenormalScalesheenColorspecularspecularColor

_lightsCache WeakMap 按渲染 ID 缓存灯光数据,以便在灯光设置未更改时避免重新计算。

节点材质系统

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

核心节点架构

SVG
100%

图表:节点类层次结构

每个 Node 子类实现:

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

NodeBuilder 基类

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

核心构建器架构

SVG
100%

图表:NodeBuilder 内部结构

关键方法

  • build():主入口点。从材质输出节点(colorNodepositionNode 等)开始遍历节点图。返回着色器代码字符串。

  • buildCode( node ):通过调用 node.build( builder ) 处理单个节点。将结果存储在缓存中以防止重复处理。

  • getVarFromNode( node, type ):为节点分配唯一的变量名。顺序编号(例如 temp0temp1varying0)确保无冲突。

  • getDataFromNode( node ):检索节点的缓存数据,按渲染 ID 存储。支持每帧缓存。

  • format( code, fromType, toType ):生成类型转换代码(例如 floatvec3 变为 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 接收一个绑定槽位
  • NodeSamplerNodeSampledTexture 配对(采样器 + 纹理)
  • NodeStorageBuffer 使用 storage<access> 并带有 read/write/read_write 模式
  • 绑定组按更新频率组织(帧 vs 材质 vs 计算)

代码生成管线

WGSLNodeBuilder 编译过程:

SVG
100%

图表:WGSLNodeBuilder 编译阶段

设置阶段:构造函数初始化定义 WebGPU 能力的 supports 对象:

supports: {
  instance: true,
  swizzleAssign: false,
  storageBuffer: true
}

构建阶段:每个着色器阶段在入口节点上调用 buildCode()

  • 顶点:positionNodenormalNode
  • 片元:colorNodealphaNodeoutputNode
  • 计算: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 函数类别

数学运算MathNodeOperatorNode):

  • 算术: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()

纹理运算TextureNodeTextureSizeNode):

  • 采样:texture()textureLoad()cubeTexture()texture3D()
  • 属性:textureSize()textureBias()textureLevel()
  • 存储:textureStore()(仅计算着色器)

材质属性MaterialNodeMaterialReferenceNode):

  • PBR:materialRoughnessmaterialMetalnessmaterialClearcoatmaterialSheen
  • 光学:materialIORmaterialTransmissionmaterialThicknessmaterialAttenuationColor
  • 表面:materialColormaterialEmissivematerialOpacitymaterialAlphaTest
  • 纹理:materialNormalMapmaterialAOMapmaterialLightMap

灯光函数LightsNodeLightingModel):

  • BRDF:BRDF_GGX()BRDF_Lambert()D_GGX()F_Schlick()
  • 灯光类型:PointLightNodeDirectionalLightNodeSpotLightNodeAmbientLightNode
  • 工具:getDistanceAttenuation()getSpotAttenuation()punctualLightIntensityToIrradianceFactor()

访问器PositionNodeNormalNodeUVNode):

  • 几何体:positionLocalpositionWorldpositionViewpositionViewDirection
  • 法线:normalLocalnormalWorldnormalViewnormalGeometrytransformedNormalView
  • 坐标:uv()uv2()uvw()(3D 纹理)
  • 摄像机:cameraPositioncameraViewMatrixcameraProjectionMatrixcameraNormalMatrix

控制流IfNodeLoopNodeSwitchNode):

  • 条件:If( condition, trueNode, falseNode )select( condition, a, b )
  • 循环:Loop( callback )BreakContinue
  • 分支:Switch( node, caseMap )
  • 提前退出:Return( value )Discard

常量(直接导出):

  • 数学:PIPI2HALF_PITWO_PIEPSILONINFINITY
  • 预处理器:NodeAccessNodeShaderStageNodeTypeNodeUpdateType

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() 返回管理 GPUDeviceWebGPUBackend 实例

  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_audiowebgpu_compute_birdswebgpu_compute_cloth
  • webgpu_compute_particles_fluidwebgpu_compute_reduce
  • webgpu_compute_sort_bitonicwebgpu_compute_texture
  • webgpu_compute_texture_3dwebgpu_compute_texture_pingpong
  • webgpu_compute_waterwebgpu_struct_drawindirect

正在调查中

  • webgpu_backdrop_waterwebgpu_portalwebgpu_shadowmap
  • webgpu_postprocessing_aowebgpu_postprocessing_ssgi
  • webgpu_test_memorywebgpu_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 函数或自定义节点 || || 着色器材质 | ShaderMaterialRawShaderMaterial | 带有节点的 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(实验性)