Files
english/.opencode/skills/threejs/references/13-node-materials.md
2026-04-12 01:06:31 +07:00

6.7 KiB

Node Materials (TSL - Three Shading Language)

Modern node-based material system for creating custom shaders visually.

What is TSL?

Three Shading Language (TSL) is a node-based system for creating materials and shaders:

  • Functional approach to shader composition
  • Type-safe node graph
  • Unified GLSL/WGSL output (WebGL & WebGPU)
  • No manual shader code required

Basic Node Material

import * as THREE from 'three/webgpu';
import { color, texture, normalMap, MeshStandardNodeMaterial } from 'three/nodes';

const material = new MeshStandardNodeMaterial();

// Set base color node
material.colorNode = color(0xff0000);

// Or use texture
material.colorNode = texture(colorTexture);

// Combine nodes
material.colorNode = texture(colorTexture).mul(color(0xffffff));

// Normal mapping
material.normalNode = normalMap(normalTexture);

Node Types

Input Nodes

import {
  attribute,
  uniform,
  texture,
  cubeTexture,
  instancedArray,
  storage
} from 'three/nodes';

// Geometry attributes
const positionNode = attribute('position');
const normalNode = attribute('normal');
const uvNode = attribute('uv');

// Uniforms
const timeNode = uniform(0); // value

// Textures
const colorNode = texture(diffuseTexture);
const envNode = cubeTexture(cubeMapTexture);

// Instanced data
const instanceColorNode = instancedArray('instanceColor');

// Storage buffers (compute)
const storageNode = storage(buffer, 'vec4', count);

Math Nodes

import { add, sub, mul, div, pow, sin, cos, length, normalize } from 'three/nodes';

// Basic operations
const result = add(a, b);     // a + b
const result = sub(a, b);     // a - b
const result = mul(a, b);     // a * b
const result = div(a, b);     // a / b

// Trigonometry
const result = sin(angle);
const result = cos(angle);

// Vector operations
const result = length(vector);
const result = normalize(vector);

// Chaining
const result = mul(texture(tex), color(0xff0000));

Procedural Nodes

import { checker, dots, noise, voronoi } from 'three/nodes';

// Checker pattern
material.colorNode = checker(uvNode.mul(10));

// Noise
material.colorNode = noise(uvNode.mul(5));

// Voronoi cells
material.colorNode = voronoi(uvNode.mul(3));

Custom Shader Function

import { Fn, vec3, float } from 'three/nodes';

// Define custom function
const customColor = Fn(([uv, time]) => {
  const r = sin(uv.x.mul(10).add(time));
  const g = cos(uv.y.mul(10).add(time));
  const b = float(0.5);
  return vec3(r, g, b);
});

// Use in material
material.colorNode = customColor(uvNode, timeNode);

Animation with Nodes

import { uniform, oscSine, timerLocal } from 'three/nodes';

// Oscillating value
const oscillator = oscSine(timerLocal(0.5)); // frequency = 0.5

// Pulsing color
material.colorNode = color(0xff0000).mul(oscillator.add(0.5));

// Rotating UV
const rotatedUV = uvNode.rotateUV(timerLocal());
material.colorNode = texture(tex, rotatedUV);

Advanced Effects

Fresnel Effect

import { normalView, positionView, dot, pow } from 'three/nodes';

const fresnel = pow(
  float(1).sub(dot(normalView, positionView.normalize())),
  3
);

material.colorNode = mix(baseColor, edgeColor, fresnel);

Vertex Displacement

import { positionLocal, normalLocal, timerLocal, sin } from 'three/nodes';

// Displace vertices along normal
const displacement = sin(positionLocal.y.add(timerLocal())).mul(0.5);
material.positionNode = positionLocal.add(normalLocal.mul(displacement));

Custom Normal Mapping

import { normalMap, normalView, TBNViewMatrix } from 'three/nodes';

const normalMapNode = normalMap(normalTexture);
const transformedNormal = TBNViewMatrix.mul(normalMapNode);
material.normalNode = transformedNormal;

Compute Shaders (WebGPU)

import { compute, uniform, storage, Fn } from 'three/nodes';

// Define compute shader
const computeShader = Fn(() => {
  const storageBuffer = storage(buffer, 'vec4', count);
  const index = instanceIndex; // built-in

  // Modify buffer
  const value = storageBuffer.element(index);
  storageBuffer.element(index).assign(value.mul(2));
})();

// Create compute node
const computeNode = compute(computeShader, 256); // workgroup size

// Execute
renderer.compute(computeNode);

Node Material Types

import {
  MeshStandardNodeMaterial,
  MeshPhysicalNodeMaterial,
  MeshBasicNodeMaterial,
  PointsNodeMaterial,
  LineBasicNodeMaterial,
  SpriteNodeMaterial
} from 'three/nodes';

// Standard PBR
const material = new MeshStandardNodeMaterial();
material.colorNode = colorNode;
material.roughnessNode = roughnessNode;
material.metalnessNode = metalnessNode;

// Physical (clearcoat, transmission, etc.)
const material = new MeshPhysicalNodeMaterial();
material.clearcoatNode = clearcoatNode;
material.transmissionNode = transmissionNode;

Post-Processing with Nodes

import { pass, PassNode } from 'three/nodes';

// Custom post-processing pass
const customPass = new PassNode('customPass', (input, output) => {
  // input: previous pass texture
  // output: render target

  // Apply effect
  const modifiedColor = input.mul(color(1, 0.5, 0.5));
  output.assign(modifiedColor);
});

// Add to post-processing chain
postProcessing.addPass(customPass);

Practical Example: Animated Material

import * as THREE from 'three/webgpu';
import {
  MeshStandardNodeMaterial,
  texture,
  uniform,
  timerLocal,
  sin,
  cos,
  vec2
} from 'three/nodes';

const material = new MeshStandardNodeMaterial();

// Animated UV scroll
const time = timerLocal();
const scrollSpeed = uniform(0.1);
const uvOffset = vec2(
  time.mul(scrollSpeed),
  sin(time).mul(0.1)
);
const scrolledUV = uv().add(uvOffset);

// Apply to color
material.colorNode = texture(diffuseTexture, scrolledUV);

// Animated emission
const pulseSpeed = uniform(2);
const emission = sin(time.mul(pulseSpeed)).mul(0.5).add(0.5);
material.emissiveNode = color(1, 0.5, 0).mul(emission);

Migration from ShaderMaterial

// Old way (ShaderMaterial)
const material = new THREE.ShaderMaterial({
  uniforms: {
    time: { value: 0 }
  },
  vertexShader: `...`,
  fragmentShader: `...`
});

// New way (Node Material)
const material = new MeshStandardNodeMaterial();
material.colorNode = customFunction(timerLocal());
// Much cleaner, type-safe, and reusable

When to Use Node Materials

  • Creating complex procedural materials
  • Need both WebGL and WebGPU support
  • Want visual/functional shader composition
  • Reusable shader components
  • Compute shader integration (WebGPU only)

Note: Node materials require WebGPU renderer for full features. Some features work with WebGL backend but compute shaders require WebGPU.