FollicleScript Expressions

FollicleScript is FollicleFX's expression language for procedural hair control. Write simple expressions that are evaluated in real time on 100,000+ strands.

Quick Start

Click the ƒx button next to any parameter to open expression mode.

0.8                          # Constant value
rand(index)                  # Random 0-1 per strand
rand3(0.7, 1.0, index)      # Random in range per strand
lerp(0.5, 1.0, v)           # UV-based gradient
v > 0.5 ? 1.0 : 0.5         # Conditional

Variables

Strand Properties

VariableDescriptionRange
indexStrand index0 to count-1
countTotal strand count—
lengthStrand lengthWorld units
maxLengthMaximum strand length in systemWorld units
widthStrand widthWorld units
tPosition along strand0 (root) to 1 (tip)

Spatial Coordinates

VariableDescriptionRange
uUV X coordinate0–1
vUV Y coordinate0–1
xWorld X positionWorld units
yWorld Y positionWorld units
zWorld Z positionWorld units

Clumping

VariableDescription
clump_idClump index (when using Clumping modifier)

Constants

ConstantValue
pi3.14159...
tau6.28318... (2π)
e2.71828...

Operators

Arithmetic

+ - * / ^ (or **) ()

Comparison

< > <= >= == !=

Returns 1.0 (true) or 0.0 (false).

Ternary

condition ? value_if_true : value_if_false

Functions

Random & Noise

FunctionDescription
rand(seed)Deterministic random 0–1
rand3(min, max, seed)Random in range [min, max]
noise(x) / noise(x, y) / noise(x, y, z)Smooth organic noise

Math

FunctionDescription
abs(x)Absolute value
sqrt(x)Square root
pow(x, y)Power
exp(x)e^x
log(x)Natural logarithm
sign(x)Returns -1, 0, or 1
mod(x, y)Modulo
min(a, b)Minimum
max(a, b)Maximum

Trigonometry

sin(x) cos(x) tan(x) — Input in radians.

Rounding

floor(x) ceil(x) frac(x)

Interpolation

FunctionDescription
clamp(x, min, max)Clamp to range
lerp(a, b, t)Linear interpolation
smoothstep(edge0, edge1, x)Smooth S-curve
select(cond, a, b)If cond > 0 then a, else b

Texture Sampling

map(u, v)    # Sample bound texture at UV coordinate

Reading painted data

New in 1.5.1. Anything you can paint can be read from an expression and plugged into almost any parameter, on the strands and on the guides alike.

attr("ffx_density")          # a channel you authored in Blender
map()                        # the texture map bound to this parameter
vgroup("scalp_top")          # a Blender vertex group, as 0-1 per strand

attr(name) — read a channel

Reads a channel that exists at that point in the stack. Channels come from painting, from a vertex group, or from an earlier modifier that writes one. It works in Clump, Coil, Cut, Frizz and Loc, in the Primitive tab's Length and Width Scale, in Guide Curl, and in modifier masks. Surface Offset expressions cannot read painted data; see Modifiers Overview for the full list.

attr("ffx_clump_mask") * 0.8      # clump hard only where painted
lerp(0.2, 1.0, attr("thickness")) # width follows a painted thickness channel

map() — read a bound texture

With no arguments, map() samples the map bound to that parameter at the strand's own root, so one expression works for every strand without passing coordinates around.

lerp(20, 120, map())     # dense where the map is white
map() * 0.4              # apply only where painted

map() returns 0 where no map is bound, so map() * X is simply off until you bind one.

vgroup(name) — read a vertex group

Reads a Blender weight group, interpolated onto each strand's root.

The channel picker

Rather than typing a channel name, use the channel picker beside the parameter. It lists the channels the stack is actually offering at that point, so what you can choose is what really exists.

When a name cannot be found

Changed in 1.5.1. If an expression contains a name that cannot be resolved — a misspelt channel, a map that is not bound, a misspelt function — the parameter falls back to the value on its slider. It does not fall back to zero, and it does not fall back to 1.0. The panel also names the parameter responsible, so a groom that comes out wrong tells you which field caused it.

Upgrading from 1.3.0? Five Clump parameters — Preserve Length, Stray Rate, Stray Amount, Stray Falloff and Crossover Rate — used to be set to 1.0 by an unresolved name, and a misspelt function name evaluated to 0. A groom carrying such a typo will look different in 1.5.1, almost always back to what you meant.

One expression language

New in 1.5.1. The same expression means the same thing in every modifier, on both the guide and the strand side. An expression that works in Frizz behaves identically in Clump.

Custom Variables (Dynamic Sliders)

Use $varName syntax to create UI sliders automatically:

$base + rand(index) * $variation

This creates "base" and "variation" sliders that you can adjust in real-time. Up to 16 custom variables supported.

Common Patterns

Per-Strand Randomization

rand(index)                      # Random 0-1
rand3(0.8, 1.2, index)          # Random range
clamp(rand(index), 0.2, 0.8)   # Clamped random

Gradients

v                                # Bottom to top (0→1)
1.0 - v                          # Top to bottom (1→0)
u                                # Left to right
lerp(0.5, 1.5, v)               # Custom range gradient

Along-Strand Effects

t                                # Root to tip (0→1)
1.0 - t                          # Tip to root
lerp(1.0, 0.1, t)               # Taper
smoothstep(0.0, 1.0, t)         # Smooth taper

Center/Edge Falloff

1.0 - abs(u - 0.5) * 2.0        # Dense in center
abs(u - 0.5) * 2.0              # Dense at edges

Per-Clump Variation

rand(clump_id)                   # Random per clump
0.5 + rand(clump_id) * 0.5      # Clumps vary 0.5–1.0
clump_id % 2                    # Alternating clumps

Conditional Logic

v > 0.5 ? 1.0 : 0.5             # Top/bottom split
rand(index) * 0.5               # Per-strand variation
index < 100 ? 0.8 : 1.0         # First 100 strands different

Noise Patterns

noise(u * 10, v * 10)            # 2D organic pattern
noise(u * 5, v * 5, index)      # Per-strand noise
0.7 + noise(u * 3, v * 3) * 0.3 # Noise in range

Modifier-Specific Examples

Clumping — Per-Clump Tightness

rand3(0.6, 0.9, clump_id)

Frizz — Per-Strand Variation

rand(index) * 0.5

Cut — Random Trim (Keep 80–100%)

rand3(0.8, 1.0, index)

Cut — Remove 1–3% Per Strand

1.0 - rand3(0.01, 0.03, index)

Density — UV-Based

lerp(0.5, 1.0, v)

Width — Taper to Tips

lerp(1.0, 0.1, t)

Coil — Per-Strand Count Variation

5.0 + rand(index) * 3.0

Tips

  1. Use index for per-strand randomization — consistent, deterministic
  2. Use t for along-strand effects — 0 at root, 1 at tip
  3. Use clump_id for per-clump variation — all strands in a clump get the same value
  4. Clamp extreme values — clamp(expr, 0.2, 0.8) avoids outliers
  5. Start simple — test with constants, add complexity gradually
  6. Custom variables for tweaking — $varName creates real-time sliders
  7. Most parameters expect 0–1 — check the parameter's range

Performance Notes

  • Expressions are evaluated in real time on all strands simultaneously
  • Simple expressions (arithmetic, rand) are very fast
  • Complex expressions (noise, trig) are slightly slower
  • No practical limit on expression complexity