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:

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: