Capítulo 797 de 859
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.
positionLocal, time, myUniform.mul(2)) is a Node wrapping a typed value.Var/Const only work inside a Fn() scope).a.add(b).mul(c)) instead of prefix syntax — reads like a fluent JS pipeline..xyz, .rgb, .xy etc. work on vector nodes exactly like in GLSL, returning a new node of the swizzled size.Fn() scope — needed whenever a value is reassigned or reused across control flow branches.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 ) } );
Fn(), using a uniform as a parameter, chaining .sin()/.mul(), and assigning the result to a material's positionNode to displace vertices.Control flow (only valid inside Fn())
| Construct | Signature | Notes |
|---|---|---|
If | If( condNode, callback ).ElseIf(...).Else(...) | TSL conditional; branches are recorded on the node graph, not JS if. |
Loop | Loop( count, callback ) or Loop( { start, end, condition }, callback ) | TSL loop construct; supports Break()/Continue(). |
Switch | Switch( 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?) | → VarNode | Mutable local. |
Const(node, name?) | → VarNode | Immutable local. |
Common operators (all available as chained methods, e.g. a.add(b))
| Category | Examples |
|---|---|
| Arithmetic | add, sub, mul, div, mod, abs, sign, negate |
| Comparison/logic | equal, notEqual, lessThan, greaterThan, and, or, not, select |
| Trig/exponential | sin, cos, tan, asin, acos, atan, pow, exp, log, sqrt |
| Vector ops | dot, 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.
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.Var(): reassigning inside a loop/conditional without wrapping in Var() won't behave as a real mutable shader variable.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.a.mul(b).add(c)) is the idiomatic style over nested function calls.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.NodeMaterial property (positionNode, colorNode, opacityNode, etc.) to actually use it in rendering.*Node properties consume TSL graphs.positionLocal, normalWorld, time, cameraPosition...) used as leaves in these graphs.