Field From Expression
Builds a field from a math expression you type, using the point coordinates x, y and z, your own inputs, vectors, matrices, noise and domain operations. The expression is compiled once and then evaluated at native speed, the same cost as a built-in primitive.
Quick start
- Place Field From Expression from the 1. Field panel. The default expression is a unit sphere:
sqrt(x*x + y*y + z*z) - 1. - To preview it, wire the output into
Preview Field 3D. To mesh it, give it a box with
Implicit From Field and wire that into
Mesh From Implicit. - Click
+on the component to add a number input. Right-click the new input, choose Set Nickname, and use that name in the expression:sqrt(x*x + y*y + z*z) - r. - Right-click the component for typed inputs:
- Add Field input takes any other field (a sphere, a mesh, a volume, another expression) and uses its value at each point.
- Add Point input and Add Vector input bind a point or vector as a
vec3. Name onepand writelength(vec3(x, y, z) - p) - rfor a sphere centred on that point.
Inputs
| Name | Nick | Type | Default | Description |
|---|---|---|---|---|
| Expression | Ex | Text | sqrt(x*x + y*y + z*z) - 1 | Math expression |
Outputs
| Name | Nick | Type | Description |
|---|---|---|---|
| Field | F | Field | Parsed expression as a Field. |
Example definition
- Drag f to change the cell size: the period is 2π / f.
- Drag t to thicken or thin the walls.
- Replace the expression with one from the examples on this page.
Syntax at a glance
| You write | It means |
|---|---|
x, y, z |
The coordinates of the point being evaluated |
PI, TAU, E |
π, 2π and Euler's number |
2.5, 1e-3 |
Numbers |
r, thickness |
Your own inputs, by nickname |
vec2(a, b), vec3(a, b, c) |
Vectors |
p.x, p.xy, p.zyx |
Components of a vector |
+ - * / ^ |
Arithmetic; ^ is power |
a < b, a == b, !a |
Comparisons and logic, giving 1 or 0 |
c ? a : b |
a when c is non-zero, otherwise b |
let q = … in … |
Name a value for the rest of the expression |
sin(x), smin(a, b, k) |
Functions: the full list is below |
The result of the whole expression must be a single number: the signed distance, negative inside the shape. Vectors and matrices can only be intermediate values.
Reserved names
x, y and z are the Cartesian coordinates of the point. PI (3.14159…), TAU (6.28318…, that is 2π) and E (2.71828…) are constants. These six names are always available and cannot be used for an input or bound with let: an input with one of these names is refused with an error asking you to rename it.
Types
| Type | How to make it | Notes |
|---|---|---|
| scalar | numbers, coordinates, Number inputs | The default for everything |
vec2 |
vec2(a, b) |
Two scalars |
vec3 |
vec3(a, b, c), or a Point or Vector input |
Three scalars |
mat2 |
mat2(col0, col1) |
Two vec2 columns |
mat3 |
mat3(col0, col1, col2) or mat3_from_axis_angle(axis, angle) |
Three vec3 columns |
Operators
Arithmetic
+ - * / ^, a leading - to negate, and a leading +, which does nothing. Power is right-associative: 2^3^2 is 2^(3^2).
Operators work element by element on vectors. A scalar combined with a vector applies to every component: vec3(x, y, z) * 2. Mixing a vec2 with a vec3 is an error. With a matrix, * is matrix multiplication (see Matrices).
A whole-number exponent works for negative bases: x^2, x^-2, or an input or let name that holds a whole number up to 63. Any other exponent, such as x^0.5 or a computed x^(1+1), is calculated through logarithms and gives NaN for a negative base. pow(a, b) follows the same rule.
A leading minus binds tighter than ^, so -x^2 means (-x)^2, which equals x^2. To negate a power, bracket it: exp(-(x^2)), or subtract it from zero: exp(0 - x^2). A negative exponent needs no brackets: 2^-x is 2^(-x).
Comparisons, logic and conditions
<,<=,>,>=,==and!=compare two values and give1(true) or0(false).&&(and),||(or) and!(not) combine them. Any non-zero value counts as true.cond ? a : bgivesawhencondis non-zero, otherwiseb. The branches can be scalars or vectors of the same width; the condition must be a scalar, so reduce a vector comparison first, as in(p > q).x.- Comparisons work element by element on vectors, like arithmetic.
- Comparisons don't chain: write
0 < x && x < 10, not0 < x < 10.
Precedence, from lowest to highest: ?:, then ||, &&, == and !=, < <= > >=, + and -, * and /, ^, a leading - + or !, and finally . for components.
Comparisons, conditions and step jump from 0 to 1. If that jump decides where the surface is, the field has a break in it and the mesh shows a crack or seam. For blends that change across space, use the smooth tools mix, smoothstep, clamp, smin and smax, and keep hard conditions for logic that doesn't define the surface directly.
Components of a vector
Read or reorder a vector's components by name, using any combination of x, y and z, one to three letters long, with repeats allowed:
| Form | Gives | Examples |
|---|---|---|
| 1 letter | scalar | p.x, p.z |
| 2 letters | vec2 |
p.xy, p.zx, p.xx |
| 3 letters | vec3 |
p.xyz, p.zyx, p.xxz |
Each letter must exist on the vector (.z on a vec2 is an error), and there is no four-component vector.
Naming values with let
let name = value in body names a value so you can use it in body. The value can be a scalar, a vector or a matrix, and the body runs to the closing bracket or the end of the expression:
let q = twist(vec3(x, y, z), k) in length(q.xy) - r
Bindings can be nested, and a later one can use an earlier one: let a = x * 2 in let b = a + 1 in b - z. Inside its body, a let name hides an input with the same name.
Functions
Scalar functions
These take numbers, and also work element by element on vectors.
| Function | What it does |
|---|---|
sin cos tan |
Trigonometry, in radians |
asin acos atan |
Inverse trigonometry, in radians |
atan2(y, x) |
Arctangent that knows the quadrant |
sinh cosh tanh |
Hyperbolic functions |
sqrt |
Square root |
cbrt |
Cube root, smooth through zero |
abs |
Absolute value |
sign |
−1, 0 or +1, smooth through zero |
exp |
e to the power |
ln, log |
Natural logarithm (the two are the same) |
log2, log10 |
Base-2 and base-10 logarithms |
floor ceil round fract |
Rounding; round takes halves away from zero, so round(-2.5) is −3 |
min(a, b), max(a, b) |
Smaller and larger of two values |
mod(a, b) |
Remainder of a / b, never negative: mod(-1, 3) is 2 |
clamp(x, lo, hi) |
Limit x to the range lo to hi |
mix(a, b, t) |
Blend: (1 − t)·a + t·b |
smin(a, b, k), smax(a, b, k) |
Smooth min and max with a fillet radius k (see below) |
pow(a, b) |
a to the power b, same rules as ^ |
square(a) |
a · a |
step(edge, x) |
0 below edge, 1 above, a hard switch |
smoothstep(e0, e1, x) |
0 below e0, 1 above e1, a smooth S-curve between |
smin and smax are the smooth union and intersection. k is the same fillet radius as the Radius input on the boolean components: two surfaces meeting at a right angle get a round fillet of radius k, and as k approaches 0 they become plain min and max. They are polynomial, so they stay safe to mesh: use them rather than an exponential blend, which can overflow.
Vector functions
| Function | Gives | What it does |
|---|---|---|
length(v) |
scalar | Length of a vector |
distance(a, b) |
scalar | length(a - b) |
dot(a, b) |
scalar | Dot product of two vectors of the same width |
cross(a, b) |
vec3 |
Cross product of two vec3 |
normalize(v) |
vector | Unit vector, safe at the origin |
reflect(I, N) |
vector | Reflect I in the plane with normal N |
refract(I, N, eta) |
vector | Refract I through a surface with index ratio eta; zero on total internal reflection |
faceforward(N, I, Nref) |
vector | N, flipped if needed to face against Nref |
Matrices
| Function | What it does |
|---|---|
mat2(c0, c1), mat3(c0, c1, c2) |
Build a matrix from its column vectors |
mat3_from_axis_angle(axis, angle) |
Rotation of angle radians about axis; the axis needn't be unit length |
transpose(M) |
Transpose of a 2×2 or 3×3 matrix |
inverse(M) |
Inverse of a 2×2 or 3×3 matrix |
outer(a, b) |
Outer product of two vectors of the same width |
Use * to apply them: M * v transforms a vector, M * N combines two matrices, and M * s scales every element. To transform a shape, evaluate it at the inverse-transformed point: rotate by evaluating at inverse(M) * vec3(x, y, z). For a pure rotation, transpose(M) is the same as the inverse and cheaper.
Noise
value_noise(p), perlin(p) and simplex(p) give smooth noise between roughly −1 and 1, where p is a vec2 or vec3. An optional second argument is a whole-number seed: simplex(p, 7). All three are continuous, so safe to mesh, and give the same result every time.
value_noiseis cheap and blocky.perlinis smooth gradient noise.simplexis smooth with no grid direction, usually the best choice for organic forms.
Scale the point to set the feature size: simplex(vec3(x, y, z) * 0.2) has features about five units across.
Domain operations
These take a point and return a transformed point, so you can build a shape once and repeat, mirror, twist or bend it. They cost the same as writing the maths out by hand.
| Function | What it does |
|---|---|
mirror_x(p), mirror_y(p), mirror_z(p) |
Mirror in the plane through the origin normal to that axis |
repeat(p, c) |
Tile space every c units; c is a number, or a vec3 for each axis |
radial_repeat(p, n) |
n-fold rotational copies around Z; n is a whole number of 1 or more |
twist(p, k) |
Twist around Z by k radians per unit of height |
bend(p, k) |
Bend in the XZ plane, in proportion to x |
rotate_x(p, a), rotate_y(p, a), rotate_z(p, a) |
Rotate about an axis, right-handed |
rotate_axis(p, axis, a) |
Rotate about any axis |
polar(p) |
vec2(r, θ) from p.xy |
cylindrical(p) |
vec3(r, θ, z) |
spherical(p) |
vec3(r, θ, φ) |
Examples
Every expression below can be pasted straight into the Expression input. Names in the Inputs column are Number inputs unless marked otherwise; add them with + and set their nicknames.
Basic shapes
| Shape | Expression | Inputs |
|---|---|---|
| Sphere of radius 1 | length(vec3(x, y, z)) - 1 |
|
| Sphere of any radius | length(vec3(x, y, z)) - r |
r |
| Ellipsoid | length(vec3(x/rx, y/ry, z/rz)) - 1 |
rx, ry, rz |
| Torus | length(vec2(length(vec2(x, y)) - R, z)) - r |
R, r |
| Box, half-sizes a, b, c | max(max(abs(x) - a, abs(y) - b), abs(z) - c) |
a, b, c |
| Infinite cylinder along Z | length(vec2(x, y)) - r |
r |
| Ground plane | z |
Combining shapes
| Shape | Expression | Inputs |
|---|---|---|
| Union of a sphere and a box | min(length(vec3(x, y, z)) - 6, max(max(abs(x) - 8, abs(y) - 3), abs(z) - 3)) |
|
| The same, with a smooth fillet | smin(length(vec3(x, y, z)) - 6, max(max(abs(x) - 8, abs(y) - 3), abs(z) - 3), k) |
k |
| Hollow sphere, wall t | abs(length(vec3(x, y, z)) - r) - t |
r, t |
| Sphere with a cylindrical hole | max(length(vec3(x, y, z)) - 6, -(length(vec2(x, y)) - 2)) |
Repeating, mirroring and twisting
| Shape | Expression | Inputs |
|---|---|---|
| Two mirrored spheres | length(mirror_x(vec3(x, y, z)) - vec3(d, 0, 0)) - 1 |
d |
| Six spheres in a ring | length(radial_repeat(vec3(x, y, z), 6) - vec3(2, 0, 0)) - 1 |
|
| Endless grid of spheres | length(repeat(vec3(x, y, z), 4)) - 1 |
|
| Twisted bar | max(max(abs(twist(vec3(x, y, z), k).x) - 0.5, abs(twist(vec3(x, y, z), k).y) - 0.1), abs(z) - 1) |
k |
| Sphere winding in a helix | length(twist(vec3(x, y, z), k) - vec3(2, 0, 0)) - 1 |
k |
Lattices
Triply periodic minimal surfaces repeat every 2π / f units. Use abs(…) - t to turn the surface into a wall of thickness about t, then clip it with
Implicit From Field or wrap it with
Conform To Surface.
| Lattice | Expression | Inputs |
|---|---|---|
| Gyroid | sin(x*f)*cos(y*f) + sin(y*f)*cos(z*f) + sin(z*f)*cos(x*f) |
f |
| Gyroid sheet | abs(sin(x*f)*cos(y*f) + sin(y*f)*cos(z*f) + sin(z*f)*cos(x*f)) - t |
f, t |
| Schwarz P | cos(x*f) + cos(y*f) + cos(z*f) |
f |
| Schwarz D | sin(x*f)*sin(y*f)*sin(z*f) + sin(x*f)*cos(y*f)*cos(z*f) + cos(x*f)*sin(y*f)*cos(z*f) + cos(x*f)*cos(y*f)*sin(z*f) |
f |
These surfaces aren't exact distance fields, so mesh them with a finer MinFeatureSize than you would a solid of the same size.
Grading, noise and texture
| Shape | Expression | Inputs |
|---|---|---|
| Rippled sphere | length(vec3(x, y, z)) - 1 + sin(x*8)*cos(y*8)*0.05 |
|
| Ripples you can tune | length(vec3(x, y, z)) - 1 + sin(x*freq)*amp |
freq, amp |
| Radius graded along x | length(vec3(x, y, z)) - mix(0.5, 1.5, smoothstep(-2, 2, x)) |
|
| Gyroid walls thickening with height | abs(sin(x*f)*cos(y*f) + sin(y*f)*cos(z*f) + sin(z*f)*cos(x*f)) - mix(0.1, 0.8, clamp(z / h, 0, 1)) |
f, h |
| Organic surface from noise | length(vec3(x, y, z)) - 1 + simplex(vec3(x, y, z)*f)*amp |
f, amp |
| Sphere eaten away by noise | max(length(vec3(x, y, z)) - 2, simplex(vec3(x, y, z)*3)) |
Points, let and matrices
| Shape | Expression | Inputs |
|---|---|---|
| Sphere centred on a point | length(vec3(x, y, z) - p) - r |
p (Point), r |
| A second sphere at a point | min(length(vec3(x, y, z)) - 1, length(vec3(x, y, z) - p) - 0.3) |
p (Point) |
| Twisted tube, using let | let q = twist(vec3(x, y, z), k) in length(q.xy) - r |
k, r |
| Sphere rotated about Z by a matrix | let M = mat3_from_axis_angle(vec3(0, 0, 1), a) in length(inverse(M) * vec3(x, y, z) - vec3(2, 0, 0)) - 1 |
a |
| A different shape on each side (leaves a seam) | x > 0 ? length(vec3(x, y, z)) - 1 : abs(y) - 0.3 |
Combining other fields
These use Add Field input. Each name is the nickname of a Field input wired from another component.
| Result | Expression | Inputs |
|---|---|---|
| Union of two fields | min(a, b) |
a, b (Field) |
| Smooth union | smin(a, b, k) |
a, b (Field), k |
| Smooth intersection | smax(a, b, k) |
a, b (Field), k |
| Subtract b from a | max(a, -b) |
a, b (Field) |
| Shell of a field | abs(a) - thickness |
a (Field), thickness |
| Blend between two fields | a + t * (b - a) |
a, b (Field), t |
| Texture on an imported mesh | m + sin(x*freq)*cos(y*freq)*amp |
m (Field, from |
| Sphere pushed out by a mesh's distance | length(vec3(x, y, z)) - 1 + 0.1 * m |
m (Field) |
The last two are the reason Field inputs exist: wire any mesh-based field in as m and add procedural detail to it, with no scripting.
Performance
- When every input is a number, or a field built from primitives and arithmetic, the expression compiles to native code and runs as fast as a built-in primitive.
- When an input is a mesh, a volume or another sampled field, the expression is evaluated point by point instead. That is slower, so keep those inputs for where you need them.
- A Number input becomes a constant in the compiled expression, so dragging its slider recompiles the expression at every step.
- Preview and mesh resolution are set downstream:
MinFeatureSizeon
Mesh From Implicit, and the preview feature size in the right-click menu of
Implicit From Field.
Error messages
Mistakes in the expression show as a red balloon on the component, with the column where the problem starts.
| Cause | Example message |
|---|---|
| Syntax error | unexpected token '<end of input>' at column 4 |
| Chained comparison | '<' and '<' do not chain: write 'a < b && b < c', … |
| Unknown function | unknown function 'tanhh' at column 1 |
| Unknown name | unknown identifier 'foo' at column 7 |
| Wrong number of arguments | function 'sin' expects 1 argument but got 2 at column 1 |
| Vector widths don't match | mismatched widths in '+': cannot broadcast vec2 to width 3 |
| Result is a vector | expression must produce a scalar (signed distance), not vec3 |
| Component of a number | component access '.x' requires a vector (vec2 or vec3), got scalar |
| Swizzle too long | swizzle '.xyzx' has unsupported width 4 |
| Matrix where a number is needed | '+' does not accept a mat3 argument |
| Reserved name in let | cannot bind reserved name 'x' in a let-expression at column 1 |
| Input with a reserved name | Input 'E' uses a reserved name (x, y, z, PI, E, TAU). Rename it. |
Messages
What the component may report, shown as a balloon on the canvas.
| Level | Message |
|---|---|
| Error | Expression is empty. |
