The Expression language
Write a tiny bit of code once, and DNA runs it on every element — every point, pixel, voxel, or vertex — at GPU speed.
The Expression node is the most powerful single node in DNA. Instead of wiring a chain of nodes to nudge an attribute, you write one short snippet, and it runs across the whole input at once. If you've used VEX in Houdini, you'll feel right at home — but you don't need to know any of that to get started.
The mental model
Three steps, every time:
Read some attributes (the colour, the position, a custom value).
Do some maths with them.
Write attributes back.
That's the whole language. Here's the classic example — push each point up in Z by how bright it is:
@P.z = luminance(@Cd) * 100.0;
@P is the position attribute, @Cd is the colour. The .z on the left says "only change the Z component". Read colour, turn it into brightness, write it into height. Run on every element, instantly.
Reads, writes, and globals
@namereads (or writes) a per-element attribute —@P,@Cd,@N,@pscale, and any custom attribute you've made upstream.$NAMEreads a global — a value that's the same for the whole frame, like$T(time in seconds),$F(frame number), or$BPM(tempo). See Globals & parameters for the full list.
// Wobble every point with time-driven noise along its normal
@P += @N * noise(@P * 0.1 + $T) * 5.0;
// Pick a colour based on height
@Cd = @P.y > 0 ? {1, 0, 0, 1} : {0, 0, 1, 1};
Curly braces {...} build a vector or colour. The ? : is a quick if/else.
It works on (almost) everything
The Expression node is polymorphic — whatever you feed it decides what "every element" means:
| You feed in | It runs once per | Handy attributes |
|---|---|---|
| Points / a collection | point | @P, @Cd, @N, @pscale |
| A Rasters (images) image | pixel | @Cd (pixel colour), @P.xy, @uv |
| A Distance fields & volumes volume | voxel | @P, @density |
| A Geometry (meshes) mesh | vertex | @P, @N, @uv |
So the same snippet that displaces points can displace a mesh's vertices — just rewire the input.
Feeding an image in? @Cd is the pixel colour and @P.xy is the pixel position. @P.z = luminance(@Cd) * 100 turns a depth map into a 3D point cloud.
Exposed sliders
Drop a param("name", default) into your code and DNA adds a live slider for it right on the node:
@P.z = luminance(@Cd) * param("height", 100.0);
A "height" slider appears under the node's parameters. Dragging it updates instantly — no recompile, no waiting. You can also wire something into that slider's pin (an input.midi knob, an input.osc stream, an envelope) to drive it live.
More than one element at a time
Beyond per-element maths, the Expression language can work across a whole collection at once with map, filter, reduce, scan, and fold — sum every particle's mass, drop the dead ones, build a running total, and so on. Those get their own page.
// Total up every point's mass, then normalise so it sums to 1
@_total = reduce(@pscale, 0.0, |acc, m| acc + m);
@pscale = map(@pscale, |p| p / @_total);
See Built-in functions for the full set.
A few things aren't available inside expressions yet: matrix types, text strings, and for / while loops. Use a ternary (cond ? a : b) for branching, and the loop.for_start loop nodes when you need real iteration over a graph.
Text and nodes are equals
Anything you write as an expression could also be built by wiring nodes, and vice versa — they're two ways into the same engine, not a "real" way and an "escape hatch". Text wins when you're describing a pipeline ("for each particle, halve its mass, drop the dead ones, sum the rest"); nodes win when the structure is the point. You can even Convert to Expression an existing node graph into editable code.
Where to go next
Expression attributes — the
@attributes you can read and writeGlobals & parameters — the
$globals like time, frame, and tempoBuilt-in functions — built-in maths, noise, and the map/filter/reduce family
Expression cookbook — ready-to-paste recipes
Scripting nodes — driving structure with code