Capítulo 797 de 859

Chapter 797: TSL Language Basics

Core Idea

TSL (Three.js Shading Language) is a JS-embedded DSL for writing shaders that compile to both WebGL2 (GLSL) and WebGPU (WGSL) from the same source. You write plain JS functions wrapped in Fn(), compose small typed node expressions (vec3, float, mat4...), and the node graph is what actually gets compiled — TSL functions build a graph, they don't execute imperatively at call time.

Key Concepts

  • Node: the base unit — every TSL expression (positionLocal, time, myUniform.mul(2)) is a Node wrapping a typed value.
  • Fn(fn): wraps a JS function as a TSL shader function; required to declare custom TSL logic (loops, conditionals, Var/Const only work inside a Fn() scope).
  • Method chaining: nodes expose their operators as chainable methods (a.add(b).mul(c)) instead of prefix syntax — reads like a fluent JS pipeline.
  • Swizzle: .xyz, .rgb, .xy etc. work on vector nodes exactly like in GLSL, returning a new node of the swizzled size.
  • Var(node, name?) / Const(node, name?): declares a mutable/immutable local variable inside a Fn() scope — needed whenever a value is reassigned or reused across control flow branches.

Code Examples

import { Fn, vec3, float, positionLocal, uniform, time } from 'three/tsl';

const wobble = Fn( ( { amount } ) => {

	const offset = vec3( 0, float( time ).sin().mul( amount ), 0 ).toVar();
	return positionLocal.add( offset );

} );

material.positionNode = wobble( { amount: uniform( 0.2 ) } );
  • What it demonstrates: declaring a reusable TSL function with Fn(), using a uniform as a parameter, chaining .sin()/.mul(), and assigning the result to a material's positionNode to displace vertices.

Reference Tables

Control flow (only valid inside Fn())

ConstructSignatureNotes
IfIf( condNode, callback ).ElseIf(...).Else(...)TSL conditional; branches are recorded on the node graph, not JS if.
LoopLoop( count, callback ) or Loop( { start, end, condition }, callback )TSL loop construct; supports Break()/Continue().
SwitchSwitch( node ).Case(1, cb).Default(cb)Multi-branch dispatch.
Break() / Continue()Loop control, only valid inside Loop.
Return()Early-exits a Fn().
Var(node, name?)VarNodeMutable local.
Const(node, name?)VarNodeImmutable local.

Common operators (all available as chained methods, e.g. a.add(b))

CategoryExamples
Arithmeticadd, sub, mul, div, mod, abs, sign, negate
Comparison/logicequal, notEqual, lessThan, greaterThan, and, or, not, select
Trig/exponentialsin, cos, tan, asin, acos, atan, pow, exp, log, sqrt
Vector opsdot, cross, normalize, length, distance, reflect, refract, mix, clamp, smoothstep, step
Bitwise (compute)bitAnd, bitOr, bitXor, shiftLeft, shiftRight
Atomics (compute)atomicAdd, atomicMax, atomicMin, atomicOr, atomicAnd, atomicXor, atomicLoad, atomicStore, atomicSub

Type constructors: float(), int(), uint(), bool(), vec2()/vec3()/vec4(), mat2()/mat3()/mat4(), color() — each converts a JS number/array or wraps an existing node, with implicit conversions available via .toFloat(), .toVec3(), etc.

Anti-patterns

  • Using JS if/for to branch on node values: JS control flow runs once at graph-build time, not per-fragment — always use TSL's If/Loop for anything that depends on a node's runtime value.
  • Mutating a node value without Var(): reassigning inside a loop/conditional without wrapping in Var() won't behave as a real mutable shader variable.
  • Forgetting Fn() wrapping: Var, Const, If, Loop are only meaningful inside a Fn() callback — calling them at module scope is a common mistake when porting GLSL logic.

Key Takeaways

  1. TSL code builds a node graph at JS-execution time; the graph is compiled to GLSL/WGSL later — think "shader authoring DSL," not "runs immediately."
  2. Method chaining (a.mul(b).add(c)) is the idiomatic style over nested function calls.
  3. Fn() is the boundary for TSL-specific control flow constructs (If, Loop, Var, Const) — plain JS control flow outside it only affects graph construction, not per-pixel/per-vertex execution.
  4. Assign a composed node graph to a NodeMaterial property (positionNode, colorNode, opacityNode, etc.) to actually use it in rendering.

Connects To

  • MeshStandardNodeMaterial / MeshBasicNodeMaterial etc.: the material classes whose *Node properties consume TSL graphs.
  • ch798 (TSL Built-in Inputs): the built-in constants (positionLocal, normalWorld, time, cameraPosition...) used as leaves in these graphs.
  • ch799 (TSL Utility Functions): higher-level helper functions built on top of these primitives.
  • WebGPURenderer: the renderer that actually compiles and runs TSL-based materials (also runs on WebGL2 via a GLSL fallback).