Guide to Three.js NodeMaterial for Shaders
This article explores how to leverage the Three.js NodeMaterial system to create sophisticated shaders without writing a single line of raw GLSL. We will cover the core concepts of node-based material creation, demonstrate how to construct shaders programmatically using the Node API, and highlight visual tool integration to streamline your WebGL and WebGPU development workflow.
Understanding the NodeMaterial System
Traditionally, custom shaders in Three.js require writing GLSL
(OpenGL Shading Language) within a ShaderMaterial or
RawShaderMaterial. While powerful, this approach isolates
shader logic from JavaScript, making maintenance, code sharing, and
WebGPU migration difficult.
The NodeMaterial system solves this by representing
shader operations as a graph of JavaScript objects. Each node represents
a specific piece of data or mathematical operation—such as colors,
texture coordinates, math functions, or lighting inputs. When rendered,
Three.js parses this graph and dynamically compiles it into the
appropriate shading language (GLSL for WebGL or WGSL for WebGPU).
Building Shaders via Code
To build a shader programmatically, you import specific nodes from
the three/addons/nodes/Nodes.js module. By chaining these
nodes together, you can define both vertex and fragment behaviors.
Here is a basic example of creating a pulsing color effect using the Node API:
import { MeshBasicNodeMaterial, color, timerLocal, sine } from 'three/addons/nodes/Nodes.js';
// Create a time-based node that pulses between 0 and 1
const time = timerLocal();
const pulse = sine(time);
// Create a base color and multiply it by the pulse value
const baseColor = color(0x00ff00);
const shiftingColor = baseColor.mul(pulse);
// Assign the node output to a NodeMaterial
const material = new MeshBasicNodeMaterial();
material.colorNode = shiftingColor;In this example, timerLocal() tracks elapsed time,
sine() performs the mathematical sine operation, and
mul() multiplies the color vector. The
colorNode property of the
MeshBasicNodeMaterial accepts this final node chain,
dictating the material’s output color.
Utilizing Common Node Types
To build more complex shaders, you can leverage a wide array of built-in nodes:
- Input Nodes:
positionLocal,uv(), andnormalLocalprovide direct access to mesh attributes. - Math Nodes: Standard operations like
add(),sub(),mul(),div(),dot(), andcross()allow for complex vector and scalar calculations. - Texture Nodes:
texture(map)allows you to sample textures and manipulate their UV coordinates programmatically. - Procedural Nodes: Noise functions and math utility nodes help generate patterns without external assets.
For instance, to distort a texture using UV coordinates, you can add a noise node to the UV coordinates before passing them to the texture node.
Visual Shader Creation
If you prefer a visual, node-based interface similar to Blender’s Shader Editor or Unreal Engine’s Material Editor, Three.js provides an official Web-based Node Editor. This tool is available in the official Three.js repository under the examples directory.
Using the visual editor, you can: 1. Drag and drop nodes representing colors, math, textures, and lighting. 2. Connect ports visually to define the shader logic. 3. Preview the material in real-time on various 3D geometries. 4. Export the completed graph as a JSON file.
To use the exported visual shader in your project, you load the JSON
using the NodeMaterialLoader class, which reconstructs the
node graph instantly in your application code.
Advantages of NodeMaterial
Adopting the NodeMaterial system offers several distinct advantages over traditional raw GLSL:
- Universal Compatibility: Shaders written with NodeMaterial automatically compile to GLSL for WebGL renderers and WGSL for WebGPU renderers.
- Modular Code: Node graphs are highly reusable. You can easily share logic blocks across different materials.
- Dynamic Updating: Because nodes are JavaScript objects, you can update properties dynamically at runtime without manually parsing and updating raw shader strings.