Attribute Wrangle

Run an expression on any input, once per point, pixel, or voxel. The expression runs on the GPU.

What it does

Runs a short expression across every element of the input: points, image pixels, volume voxels, distance-field samples or mesh vertices. The input type sets what is available to read and write. A point cloud gives @P (position), @Cd (colour), @N (normal) and @pscale; an image gives @Cd for pixel colour and @P.xy for pixel position; a volume gives @density; a distance field gives @dist. The expression compiles to run on the GPU across every element at once.

With Output Field on, the same expression evaluates as an analytic field instead of being baked to a fixed set of points.

When to use it

ParameterTypeDefaultWhat it does
inputAny—Any input type. Collection: per-point. Raster: per-pixel. Volume: per-voxel.
codeString"@P.z = luminance(@Cd) * 10.0"VEX-like expression. Use @P, @Cd, @N, @pscale, $T, $F.\nRaster input: @Cd = pixel color, @P.xy = pixel position.\nExample: @P.z = luminance(@Cd) * 100
output_fieldBooleanfalseEmit an analytic Field (evaluated per-position, no bake) instead of a point Collection. Kind follows the written attribute: Float→Scalar, Vec3→Vector, Vec4→Color.
input2Any—Optional second input (slot 1). Field: sample_input(1, pos). Raster: @Cd1. `input` is slot 0.
evaluateString"Per Cook"Per Cook: evaluate now. Per Step: the simulation re-evaluates this expression every step with live particle state. One of: Per Cook, Per Step.
output_scatterBooleanfalseEmit a scatter-accumulation Raster (histogram / splat grid) instead of a Collection. The body deposits per-agent via atomic_add(x, y, ch, v).
scatter_widthNumber256Scatter accumulator width in cells (Raster X). Maps to ScatterAccumulate.width.
scatter_heightNumber256Scatter accumulator height in cells (Raster Y). Maps to ScatterAccumulate.height.
scatter_channelsNumber1Scatter accumulator channels per cell (1..=4). Maps to ScatterAccumulate.channels.
input3Any—Optional third input (slot 2). Raster: @Cd2. `input`=slot 0, `input2`=slot 1.
input4Any—Optional fourth input (slot 3). Raster: @Cd3.
strengthNumber1Effect strength / wet-dry (0 = no effect, 1 = full). Editable, wirable, keyframable.

Gotchas

With Output Field on, the field kind follows what is written: a single number gives a Scalar field, a 3-value write gives a Vector field, a 4-value write (with alpha) gives a Colour field. Writing to the distinctly-named @sdf attribute instead produces a Distance Field. An expression that is not field-compatible, for example one reading @Cd with nothing wired into the second input, falls back to the normal point-cloud output.

Reading a live simulation attribute, such as the density carried by a running fluid or cloth sim, defers evaluation to every step regardless of the Evaluate setting. Evaluate matters only for expressions that do not already depend on live sim state.

Write param("name", 1.0) anywhere in the expression and it becomes a numbered, editable pin on the node. Dragging it updates the value live, with no recompile delay.

Worked example

  1. Add an Attribute Wrangle and wire an image into Input.

  2. In the expression, write @P.z = luminance(@Cd) * 100; to push each pixel into 3D depth based on its brightness.

  3. Turn on Output Field and instead write @density = luminance(sample_input(0, @P)); for a Scalar field version of the same idea, sampled at any position rather than baked to fixed points.

  4. Add param("strength", 100.0) in place of the literal 100 and drag the new Strength pin to tune the effect without touching the code.

See also

Lua · p5.js · Shader · Attributes