Course outline
Node Materials
In one line
A node material describes its look as a graph of small operations: colorNode, positionNode, normalNode and more. three.js turns the graph into a shader for whichever backend runs, and a uniform() node changes without recompiling.
Think of it like this
A recipe card can be written as a list of steps: take the flour, mix with water, bake. Each step takes ingredients from earlier steps. A node material is a recipe card for a surface: take the uv, mix two colours by it, multiply by the light. The kitchen (the renderer) reads the card and cooks it its own way, WGSL on WebGPU and GLSL on WebGL 2.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- node
- A small object that stands for one value or operation computed on the GPU, such as
uv(),timeormix(a, b, t). - graph
- Nodes connected together: each node's inputs are other nodes. The renderer walks it to write the shader.
- node material
- A material whose properties can take nodes. In three.js, classes such as
MeshStandardNodeMaterialfromthree/webgpu. - uniform
- A value the shader reads that stays the same for all vertices and pixels in a draw, and that JavaScript can change between frames.
- program
- One compiled shader stage.
renderer.info.memory.programscounts them: a vertex and a fragment program per distinct material. - view space
- Coordinates measured from the camera (Topic 2.4).
normalNodeis expected in it.
Step by step
01Choose the node class
Take the node version of the material you want: MeshStandardNodeMaterial for lit surfaces, MeshBasicNodeMaterial for flat colour, MeshPhysicalNodeMaterial for clear coat. Its normal properties (roughness, side) still work.
02Fill a slot with a graph
material.colorNode = mix(color("#2a9df4"), color("#ff6b2c"), uv().x) says: blend blue to orange by the pixel's horizontal uv. uv() is a function call: it makes the node. Forgetting the parentheses is the most common first mistake.
03Move vertices with positionNode
positionLocal.add(vec3(0, 0, wave)) starts from each vertex's local position and adds a push along z. time is a node that holds the seconds since the first frame, so the wave moves with no JavaScript in the loop.
04Let a uniform be the knob
const amount = uniform(0.12) makes a value you can read and write from JavaScript. Use it for anything a slider or a button will change, such as a paint colour or a flake size.
05Count the programs
The second example logs renderer.info.memory.programs. Two materials give 4 (two stages each). A new uniform value keeps 4. A new graph gives 6. A second plain material of an already compiled kind adds none.
Try it yourself
- 1
Make the wave tall
In the first example change
uniform(0.12)touniform(0.4). The wave swings 0.4 m up and down on a 1.2 m plane. The console lines do not change, because the geometry in JavaScript is still flat. - 2
Use a plain material
Change
new THREE.MeshStandardNodeMaterial(tonew THREE.MeshStandardMaterial(. The console printsis a node material: undefined, but the plane still shows the gradient and the wave:WebGPURendererconverted the material and copiedcolorNodeandpositionNodewith it. OnWebGLRendererthose two properties would be ignored.
Code & diagrams
A wave moves along the plane and the colour runs from blue to orange. The console shows the CPU still sees a flat plane. No `output` because of the fallback warning line.
Console
Run the scene to see its console.`forceWebGL` keeps the numbers the same everywhere. Programs count shader stages: a vertex stage and a fragment stage for each distinct material.
Console
Run the scene to see its console.Expected console output
MeshStandardMaterial -> MeshStandardNodeMaterial | colour copied: true
programs before the first frame: 0
after the first frame: 4
after changing the uniform: 4
after replacing the colorNode: 6From NodeMaterial.js and MeshStandardNodeMaterial.js in three r186.
const m = new MeshStandardNodeMaterial();
m.colorNode = ...; // base colour (vec3 or colour)
m.roughnessNode = ...; // number 0 to 1
m.metalnessNode = ...; // number 0 to 1
m.emissiveNode = ...; // colour the surface gives off by itself
m.opacityNode = ...; // number 0 to 1 (set m.transparent = true)
m.normalNode = ...; // direction, in VIEW space
m.positionNode = ...; // replaces positionLocal, before the camera
// null means: use the ordinary property, such as m.color or m.roughnessBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the parentheses on uv
In the first example change uv().x to uv.x.
Break #2
Replace a node without needsUpdate
In the second example delete the node.needsUpdate = true; line.
Myth vs fact
Myth
Node materials run in JavaScript, so they are slow.
Fact
JavaScript only builds the description, once. The GPU runs the generated shader, as fast as one written by hand for the same maths.
Myth
Changing a node's value recompiles the shader.
Fact
A uniform() node's .value changes freely. Only changing the graph (a different node tree) compiles a new program.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Materials that produce the same shader share one compiled program, each keeping its own uniform values. In a test run, adding a second plain
MeshStandardMaterialafter the first frame added no programs. - ▸
MeshStandardNodeMaterial.normalNodeexpects view space: a normal made in object space needstransformNormalToView(...)first. Topic 13.4 does exactly that for the paint flakes. - ▸
Node materials are not a WebGL feature: they need
WebGPURenderer, on either of its backends. A project that must keepWebGLRenderercannot use them.
Remember this
- 1
The problem this solves:
MeshStandardMaterialhas fixed knobs: a colour, a roughness, a map. To change how colour or position is made you needed GLSL text edits (Phase 12). A node material has the same knobs as hooks you can fill with your own graph, in JavaScript, with no shader text. - 2
What a node is. A node is a small object that stands for a value computed on the GPU: a number, a vector, the uv of this pixel, the current time, the result of
mix(a, b, t). You combine nodes with functions and methods, and the result is another node. Nothing is calculated in JavaScript: you are building a description, and three.js writes the shader from it when the object is first drawn. - 3
The slots.
MeshStandardNodeMaterialisMeshStandardMaterialwith extra properties that take a node:colorNode(the base colour),roughnessNode,metalnessNode,emissiveNode(light the surface gives off),opacityNode,normalNode(the surface direction, in view space), andpositionNode(replaces each vertex's local position before the camera transform). Anything you leavenulluses the normal property, so you replace only what you need. - 4
Plain materials still work.
WebGPURendererconverts every classic material it meets into the matching node material and copies its properties over (renderer.library.fromMaterial(material)), which is why your whole showroom works unchanged. A side effect on this renderer: node properties you put on a plain material are copied too, so they happen to work. Do not rely on that: write the node class, so the code also reads right. - 5
Uniforms are values you can change.
uniform(0.12)is a node holding a number the GPU reads every frame. Changingamount.valuecosts nothing. Replacing a node in the graph is different: the shader text changes, so three.js must compile a new program. The second example counts this: programs stay at 4 when the uniform changes and rise to 6 whencolorNodeis replaced andneedsUpdateis set. - 6
Limits to know.
positionNodemoves vertices on the GPU only: the CPU copy of the geometry does not change (the first example printsvertex 10 z on the CPU: 0), so bounding boxes, raycasting and frustum culling use the undisplaced shape, as in Phase 12. Moving vertices does not update normals either: add anormalNodeif lighting must follow.
Explain it without notes
What is a node, and why is a node material described as a graph?
When does changing a node material cost a shader compile, and when is it free?
Practice
Make a MeshStandardNodeMaterial whose roughness goes from 0.1 at the left of a plane to 0.9 at the right.
Make the plane's colour pulse between orange and blue once per second with no JavaScript in the animation loop.
Done when you can
Name the main slots of a node material and what each replaces
Build a colour from
uv()andmixand a vertex wave frompositionNodeTell when a change is free (uniform) and when it recompiles (new graph)
Explain why
positionNodeleaves the CPU geometry unchanged