Skip to content

WebGPU & Node Materials

Purpose and Scope

This page documents the WebGPU rendering backend and its node-based material system. WebGPU provides a modern GPU API with fundamentally different shader generation compared to WebGL. For information about the traditional WebGL rendering pipeline, see WebGL Rendering Pipeline. For WebGL shader templates and compilation, see Shader Programs & ShaderLib.

Architecture Overview

The WebGPU backend replaces WebGL's template-based shader system (ShaderLib) with a node-based material system where shaders are composed from reusable node graphs. The core compilation flow transforms Node instances into WGSL shader code through the WGSLNodeBuilder class.

SVG
100%

Diagram: Node-to-Shader Compilation Pipeline

The compilation process:

  1. NodeMaterial defines a shader as a graph of Node instances
  2. WGSLNodeBuilder.build() traverses the graph, determining types and dependencies
  3. Resource allocation creates uniform buffers, storage buffers, and bind group layouts
  4. getCode() emits final WGSL shader text with proper syntax

The implementation resides in the WebGPU bundle. The node material system composes shaders from graphs of typed nodes rather than string concatenation.

Build Artifacts and Entry Points

Three.js distributes WebGPU through separate build artifacts with distinct entry points:

Build FilePackage ImportPurposeDependencies
three.module.jsimport * from 'three'WebGL rendererShaderLib
three.webgpu.jsimport * from 'three/webgpu'WebGPU backendNode system
three.webgpu.nodes.jsInternalFull node implementationComplete graphs
three.tsl.jsimport * from 'three/tsl'TSL functionsReferences webgpu

The package.json exports define these entry points:

"exports": {
  ".": "./build/three.module.js",
  "./webgpu": "./build/three.webgpu.js",
  "./tsl": "./build/three.tsl.js"
}

The build process uses Rollup with separate input configurations for each target.

NodeMaterialObserver

The NodeMaterialObserver class determines whether render objects require material refresh before rendering. It tracks property changes that affect shader generation.

SVG
100%

Diagram: NodeMaterialObserver Change Detection Flow

The observer tracks 64 material properties defined in the refreshUniforms array:

Texture Maps: alphaMap, aoMap, bumpMap, clearcoatMap, clearcoatNormalMap, displacementMap, emissiveMap, envMap, gradientMap, iridescenceMap, iridescenceThicknessMap, lightMap, map, matcap, metalnessMap, normalMap, roughnessMap, sheenColorMap, sheenRoughnessMap, specularColorMap, specularIntensityMap, specularMap, transmissionMap, anisotropyMap

Scalar Properties: alphaTest, anisotropy, anisotropyRotation, aoMapIntensity, clearcoat, clearcoatRoughness, dispersion, emissiveIntensity, envMapIntensity, ior, iridescence, iridescenceIOR, lightMapIntensity, metalness, opacity, roughness, sheen, shininess, specularIntensity, thickness, transmission

Vector Properties: attenuationColor, clearcoatNormalScale, color, emissive, normalScale, sheenColor, specular, specularColor

The _lightsCache WeakMap caches light data by render ID to avoid recalculation when light setup hasn't changed.

Node Material System

Node materials compose shaders from connected nodes rather than using fixed templates. Each node represents a shader operation.

Core Node Architecture

SVG
100%

Diagram: Node Class Hierarchy

Each Node subclass implements:

  • build( builder ): Analyzes dependencies and allocates resources
  • generate( builder, output ): Emits shader code for this node
  • getNodeType( builder ): Returns WGSL/GLSL type (e.g., 'vec3', 'float')

NodeBuilder Base Class

NodeBuilder is the abstract base class for shader code generation. It manages node traversal, resource allocation, and code emission. Platform-specific builders (WGSLNodeBuilder, GLSLNodeBuilder) extend it to target different shader languages.

Core Builder Architecture

SVG
100%

Diagram: NodeBuilder Internal Structure

Key Methods:

  • build(): Main entry point. Traverses the node graph starting from material output nodes (colorNode, positionNode, etc.). Returns shader code string.

  • buildCode( node ): Processes a single node by calling node.build( builder ). Stores result in cache to prevent duplicate processing.

  • getVarFromNode( node, type ): Allocates unique variable name for a node. Sequential numbering (e.g., temp0, temp1, varying0) ensures no collisions.

  • getDataFromNode( node ): Retrieves cached data for a node, stored by render ID. Enables per-frame caching.

  • format( code, fromType, toType ): Generates type conversion code (e.g., float to vec3 becomes vec3( value )).

Type Inference: The builder infers WGSL/GLSL types from node outputs. Nodes declare their output type via getNodeType(), enabling automatic type checking.

WGSLNodeBuilder Implementation

WGSLNodeBuilder extends NodeBuilder to generate WGSL shader code with WebGPU-specific features.

WGSL Type Mapping

The builder maintains type translation tables:

Node TypeWGSL TypeExample
floatf32var x: f32 = 1.0;
vec2vec2<f32>var uv: vec2<f32> = vec2(0.5);
vec3vec3<f32>var pos: vec3<f32>;
vec4vec4<f32>var color: vec4<f32>;
inti32var index: i32 = 0;
uintu32var id: u32;
mat3mat3x3<f32>var rot: mat3x3<f32>;
mat4mat4x4<f32>var mvp: mat4x4<f32>;
boolboolvar flag: bool = true;

Bind Group Layout

WebGPU organizes resources into bind groups. The builder allocates bindings sequentially:

SVG
100%

Diagram: WebGPU Bind Group Organization

The binding allocation follows these rules:

  • Each NodeUniformBuffer receives a binding slot
  • NodeSampler and NodeSampledTexture are paired (sampler + texture)
  • NodeStorageBuffer uses storage<access> with read/write/read_write modes
  • Bind groups organize resources by update frequency (frame vs material vs compute)

Code Generation Pipeline

The WGSLNodeBuilder compilation process:

SVG
100%

Diagram: WGSLNodeBuilder Compilation Stages

Setup Phase: Constructor initializes supports object defining WebGPU capabilities:

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

Build Phase: Each shader stage calls buildCode() on the entry node:

  • Vertex: positionNode, normalNode
  • Fragment: colorNode, alphaNode, outputNode
  • Compute: computeNode

Allocation Phase: Parser methods convert abstract nodes into concrete resources:

  • _parseVars(): Temporary variables (var temp0: vec3<f32>;)
  • _parseUniforms(): Uniform buffers and bindings
  • _parseAttributes(): Vertex buffer layout
  • _parseVaryings(): Inter-stage data (@location(0) vUV: vec2<f32>)

Emission Phase: Generate final WGSL text with proper syntax and decorators.

Node-to-WGSL Translation Examples

Example 1: Simple Color Node

Node graph:

const colorNode = vec3( 1.0, 0.5, 0.0 );
material.colorNode = colorNode;

Generated 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 );
}

Example 2: Texture Sampling

Node graph:

import { texture, uv } from 'three/tsl';
material.colorNode = texture( diffuseMap, uv() );

Generated 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;
}

Example 3: Arithmetic Operations

Node graph:

import { add, mul, positionLocal, normalLocal } from 'three/tsl';
material.positionNode = add( positionLocal, mul( normalLocal, 0.1 ) );

Generated 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 );
}

Example 4: Custom Function

Node graph:

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 );

Generated 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 provides a JavaScript-based API for authoring shaders that compile to node graphs. It offers functional composition without writing GLSL or WGSL directly.

TSL Core Abstractions

SVG
100%

Diagram: TSL Core Abstractions and Generated Code

TSL Function Categories

Mathematical Operations (MathNode, OperatorNode):

  • Arithmetic: add(), sub(), mul(), div(), mod(), pow()
  • Trigonometry: sin(), cos(), tan(), asin(), acos(), atan(), atan2()
  • Common: abs(), sign(), floor(), ceil(), fract(), sqrt(), exp(), log()
  • Interpolation: mix(), smoothstep(), step(), clamp(), saturate()
  • Vector: dot(), cross(), length(), normalize(), reflect(), refract(), faceforward()

Texture Operations (TextureNode, TextureSizeNode):

  • Sampling: texture(), textureLoad(), cubeTexture(), texture3D()
  • Properties: textureSize(), textureBias(), textureLevel()
  • Storage: textureStore() (compute shaders only)

Material Properties (MaterialNode, MaterialReferenceNode):

  • PBR: materialRoughness, materialMetalness, materialClearcoat, materialSheen
  • Optical: materialIOR, materialTransmission, materialThickness, materialAttenuationColor
  • Surface: materialColor, materialEmissive, materialOpacity, materialAlphaTest
  • Textures: materialNormalMap, materialAOMap, materialLightMap

Lighting Functions (LightsNode, LightingModel):

  • BRDFs: BRDF_GGX(), BRDF_Lambert(), D_GGX(), F_Schlick()
  • Light Types: PointLightNode, DirectionalLightNode, SpotLightNode, AmbientLightNode
  • Utilities: getDistanceAttenuation(), getSpotAttenuation(), punctualLightIntensityToIrradianceFactor()

Accessors (PositionNode, NormalNode, UVNode):

  • Geometry: positionLocal, positionWorld, positionView, positionViewDirection
  • Normals: normalLocal, normalWorld, normalView, normalGeometry, transformedNormalView
  • Coordinates: uv(), uv2(), uvw() (3D textures)
  • Camera: cameraPosition, cameraViewMatrix, cameraProjectionMatrix, cameraNormalMatrix

Control Flow (IfNode, LoopNode, SwitchNode):

  • Conditionals: If( condition, trueNode, falseNode ), select( condition, a, b )
  • Loops: Loop( callback ), Break, Continue
  • Branching: Switch( node, caseMap )
  • Early exit: Return( value ), Discard

Constants (exported directly):

  • Mathematical: PI, PI2, HALF_PI, TWO_PI, EPSILON, INFINITY
  • Preprocessor: NodeAccess, NodeShaderStage, NodeType, NodeUpdateType

The TSL module provides a declarative API for shader authoring. It compiles to Node instances that the builder processes into WGSL/GLSL.

TSL to Node Graph Example

TSL code compiles to Node instances that the builder processes:

TSL Input:

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 ) );
} );

Equivalent Node Graph:

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: ... }
    ]
  }
}

Generated 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-Specific Features

Multiple Render Targets (MRT)

WebGPU supports rendering to multiple textures simultaneously in a single render pass, commonly used for deferred rendering.

SVG
100%

Diagram: Multiple Render Targets Flow

MRT enables efficient G-buffer generation for deferred rendering. The fragment shader outputs to multiple targets in a single pass.

Compute Shaders

WebGPU provides native compute shader support for general-purpose GPU computation:

FeatureDescriptionCommon Use Cases
Storage BuffersRead/write GPU memoryParticle systems, physics simulation
WorkgroupsParallel execution unitsMassive parallelism (thousands of threads)
Shared MemoryWorkgroup-local memoryFast inter-thread communication
Atomic OperationsThread-safe opsSynchronization, counters
Indirect DispatchGPU-driven dispatchDynamic workload sizing

Compute shader examples in the codebase:

  • webgpu_compute_birds - Flocking simulation with spatial partitioning
  • webgpu_compute_cloth - Cloth physics with constraints
  • webgpu_compute_particles - Particle system updates
  • webgpu_compute_particles_fluid - SPH fluid simulation
  • webgpu_compute_texture - Procedural texture generation
  • webgpu_compute_texture_3d - 3D texture computation
  • webgpu_compute_water - Water surface simulation
  • webgpu_compute_sort_bitonic - GPU sorting algorithm

Storage Buffers and Structured Data

Storage buffers enable structured data access with read/write capabilities:

SVG
100%

Diagram: Storage Buffer Data Flow

Storage buffers support:

  • Large data sets (gigabytes)
  • Random read/write access
  • Structured array types
  • Atomic operations
  • Indirect draw parameters

Timestamp Queries

WebGPU provides precise GPU timing through timestamp queries for performance profiling:

// Create timestamp query from three.core
const query = new TimestampQuery();

// Measure compute shader
renderer.compute( particleComputeNode, query );
const gpuTime = query.getResult(); // nanoseconds

Timestamp queries enable measurement of:

  • Render pass duration
  • Compute shader execution time
  • Individual draw call costs
  • Frame-level GPU performance
  • Pipeline stage profiling

Coordinate System Differences

WebGPU uses different coordinate conventions from WebGL:

AspectWebGLWebGPUImpact
Clip Space Y-1 (bottom) to +1 (top)-1 (bottom) to +1 (top)Same
Clip Space Z-1 (near) to +1 (far)0 (near) to +1 (far)Different
Texture OriginBottom-leftTop-leftDifferent
NDC HandednessRight-handedLeft-handedDifferent
Winding OrderCCW = frontCCW = frontSame

The renderer uses coordinate system constants:

  • WebGLCoordinateSystem = 0
  • WebGPUCoordinateSystem = 1

Three.js automatically handles these differences through internal transformations in the projection matrix and texture sampling.

Material Compilation and Caching

The WebGPU backend uses sophisticated caching to avoid unnecessary shader recompilation:

SVG
100%

Diagram: Material Compilation and Caching Pipeline

The caching system considers:

  1. Material Properties: All values in the refreshUniforms array
  2. Shader Defines: Feature flags (#define directives)
  3. Geometry Layout: Attribute types and vertex format
  4. Light Configuration: Number and types of active lights
  5. Render Context: Render target format, multisampling

Changes to tracked properties trigger observer checks. Non-tracked properties update uniforms without recompilation.

WebGPURenderer Integration

The node material system integrates with WebGPURenderer through several management classes that handle compilation, caching, and GPU resource binding.

SVG
100%

Diagram: WebGPURenderer Class Integration

Render Flow:

  1. Scene Traversal: WebGPURenderer.render() calls renderScene() which processes render lists (opaque, transparent, compute)

  2. Backend Access: _getBackend() returns the WebGPUBackend instance managing the GPUDevice

  3. Material Processing: For each render object, the backend retrieves or compiles the pipeline:

    • NodeMaterialObserver.checkRefresh() detects property changes
    • WGSLNodeBuilder.build() generates WGSL shader code
    • Pipeline cache lookup by material hash
  4. Pipeline Creation: On cache miss, calls device.createRenderPipeline() with:

    • Vertex/fragment shader modules
    • Vertex buffer layout
    • Bind group layouts
    • Render target formats
  5. Resource Binding: BindGroup Manager organizes resources:

    • Group 0: Frame uniforms (camera, time)
    • Group 1: Material uniforms (color, textures)
    • Group 2: Object uniforms (modelMatrix)
  6. Command Encoding: GPUCommandEncoder records draw commands, then queue.submit() executes on GPU

E2E Test Coverage

The WebGPU backend has extensive example coverage in the E2E test suite:

CategoryExample CountTest Status
Core Features~180 examplesTested in CI
Compute Shaders~15 examplesExcluded (native WebGPU required)
Postprocessing~25 examplesTested with exceptions
TSL Examples~10 examplesTested
Long-running~10 examplesExcluded (timeout >1min)

Exception List (excluded from automated testing):

Long-running (>1 minute):

  • webgpu_parallax_uv (11 min)
  • webgpu_cubemap_adjustments (9 min)
  • webgpu_cubemap_mix (2 min)
  • webgpu_water (1 min)

Compute-only (require native 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

Under investigation:

  • webgpu_backdrop_water, webgpu_portal, webgpu_shadowmap
  • webgpu_postprocessing_ao, webgpu_postprocessing_ssgi
  • webgpu_test_memory, webgpu_tsl_vfx_flames

The test suite uses Puppeteer with SwiftShader for consistent headless rendering, comparing screenshots with a 0.1 pixel threshold.

Migration from WebGL

Key differences when migrating from WebGL to WebGPU:

AspectWebGL ApproachWebGPU Approach
Importimport * from 'three'import * from 'three/webgpu'
Material SystemShaderLib templatesNode graphs
Shader LanguageGLSL ES 3.0WGSL
Custom Shadersmaterial.onBeforeCompile()TSL functions or custom nodes
Shader MaterialShaderMaterial, RawShaderMaterialNodeMaterial with nodes
Multiple TargetsLimited (WEBGL_draw_buffers)Native MRT support
ComputeTransform feedback or texturesNative compute shaders
Coordinate SystemWebGL conventionsWebGPU conventions (handled automatically)

Migration Steps:

  1. Change Entry Point: Replace 'three' import with 'three/webgpu'
  2. Update Materials: Convert ShaderMaterial to NodeMaterial with equivalent node graphs
  3. Convert Shaders: Translate GLSL customizations to TSL functions
  4. Adjust Coordinates: Remove manual Z-axis flipping (handled by renderer)
  5. Test Compatibility: Verify in Chrome/Edge 113+, Firefox Nightly

Browser Support:

  • Chrome/Edge 113+ (stable)
  • Firefox Nightly (in development)
  • Safari Technology Preview (experimental)