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.
Diagram: Node-to-Shader Compilation Pipeline
The compilation process:
NodeMaterialdefines a shader as a graph ofNodeinstancesWGSLNodeBuilder.build()traverses the graph, determining types and dependencies- Resource allocation creates uniform buffers, storage buffers, and bind group layouts
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 File | Package Import | Purpose | Dependencies |
|---|---|---|---|
three.module.js | import * from 'three' | WebGL renderer | ShaderLib |
three.webgpu.js | import * from 'three/webgpu' | WebGPU backend | Node system |
three.webgpu.nodes.js | Internal | Full node implementation | Complete graphs |
three.tsl.js | import * from 'three/tsl' | TSL functions | References 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.
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
Diagram: Node Class Hierarchy
Each Node subclass implements:
build( builder ): Analyzes dependencies and allocates resourcesgenerate( builder, output ): Emits shader code for this nodegetNodeType( 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
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 callingnode.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.,floattovec3becomesvec3( 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 Type | WGSL Type | Example |
|---|---|---|
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; |
Bind Group Layout
WebGPU organizes resources into bind groups. The builder allocates bindings sequentially:
Diagram: WebGPU Bind Group Organization
The binding allocation follows these rules:
- Each
NodeUniformBufferreceives a binding slot NodeSamplerandNodeSampledTextureare paired (sampler + texture)NodeStorageBufferusesstorage<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:
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
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.
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:
| Feature | Description | Common Use Cases |
|---|---|---|
| Storage Buffers | Read/write GPU memory | Particle systems, physics simulation |
| Workgroups | Parallel execution units | Massive parallelism (thousands of threads) |
| Shared Memory | Workgroup-local memory | Fast inter-thread communication |
| Atomic Operations | Thread-safe ops | Synchronization, counters |
| Indirect Dispatch | GPU-driven dispatch | Dynamic workload sizing |
Compute shader examples in the codebase:
webgpu_compute_birds- Flocking simulation with spatial partitioningwebgpu_compute_cloth- Cloth physics with constraintswebgpu_compute_particles- Particle system updateswebgpu_compute_particles_fluid- SPH fluid simulationwebgpu_compute_texture- Procedural texture generationwebgpu_compute_texture_3d- 3D texture computationwebgpu_compute_water- Water surface simulationwebgpu_compute_sort_bitonic- GPU sorting algorithm
Storage Buffers and Structured Data
Storage buffers enable structured data access with read/write capabilities:
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(); // nanosecondsTimestamp 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:
| Aspect | WebGL | WebGPU | Impact |
|---|---|---|---|
| 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 Origin | Bottom-left | Top-left | Different |
| NDC Handedness | Right-handed | Left-handed | Different |
| Winding Order | CCW = front | CCW = front | Same |
The renderer uses coordinate system constants:
WebGLCoordinateSystem = 0WebGPUCoordinateSystem = 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:
Diagram: Material Compilation and Caching Pipeline
The caching system considers:
- Material Properties: All values in the
refreshUniformsarray - Shader Defines: Feature flags (#define directives)
- Geometry Layout: Attribute types and vertex format
- Light Configuration: Number and types of active lights
- 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.
Diagram: WebGPURenderer Class Integration
Render Flow:
Scene Traversal:
WebGPURenderer.render()callsrenderScene()which processes render lists (opaque, transparent, compute)Backend Access:
_getBackend()returns theWebGPUBackendinstance managing theGPUDeviceMaterial Processing: For each render object, the backend retrieves or compiles the pipeline:
NodeMaterialObserver.checkRefresh()detects property changesWGSLNodeBuilder.build()generates WGSL shader code- Pipeline cache lookup by material hash
Pipeline Creation: On cache miss, calls
device.createRenderPipeline()with:- Vertex/fragment shader modules
- Vertex buffer layout
- Bind group layouts
- Render target formats
Resource Binding: BindGroup Manager organizes resources:
- Group 0: Frame uniforms (camera, time)
- Group 1: Material uniforms (color, textures)
- Group 2: Object uniforms (modelMatrix)
Command Encoding:
GPUCommandEncoderrecords draw commands, thenqueue.submit()executes on GPU
E2E Test Coverage
The WebGPU backend has extensive example coverage in the E2E test suite:
| Category | Example Count | Test Status |
|---|---|---|
| Core Features | ~180 examples | Tested in CI |
| Compute Shaders | ~15 examples | Excluded (native WebGPU required) |
| Postprocessing | ~25 examples | Tested with exceptions |
| TSL Examples | ~10 examples | Tested |
| Long-running | ~10 examples | Excluded (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_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
Under investigation:
webgpu_backdrop_water,webgpu_portal,webgpu_shadowmapwebgpu_postprocessing_ao,webgpu_postprocessing_ssgiwebgpu_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:
| Aspect | WebGL Approach | WebGPU Approach |
|---|---|---|
| Import | import * from 'three' | import * from 'three/webgpu' |
| Material System | ShaderLib templates | Node graphs |
| Shader Language | GLSL ES 3.0 | WGSL |
| Custom Shaders | material.onBeforeCompile() | TSL functions or custom nodes |
| Shader Material | ShaderMaterial, RawShaderMaterial | NodeMaterial with nodes |
| Multiple Targets | Limited (WEBGL_draw_buffers) | Native MRT support |
| Compute | Transform feedback or textures | Native compute shaders |
| Coordinate System | WebGL conventions | WebGPU conventions (handled automatically) |
Migration Steps:
- Change Entry Point: Replace
'three'import with'three/webgpu' - Update Materials: Convert
ShaderMaterialtoNodeMaterialwith equivalent node graphs - Convert Shaders: Translate GLSL customizations to TSL functions
- Adjust Coordinates: Remove manual Z-axis flipping (handled by renderer)
- Test Compatibility: Verify in Chrome/Edge 113+, Firefox Nightly
Browser Support:
- Chrome/Edge 113+ (stable)
- Firefox Nightly (in development)
- Safari Technology Preview (experimental)