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:

  1. Read some attributes (the colour, the position, a custom value).

  2. Do some maths with them.

  3. 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

// 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 inIt runs once perHandy attributes
Points / a collectionpoint@P, @Cd, @N, @pscale
A Rasters (images) imagepixel@Cd (pixel colour), @P.xy, @uv
A Distance fields & volumes volumevoxel@P, @density
A Geometry (meshes) meshvertex@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

See also