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
| Variable | Description | Range |
|---|---|---|
index | Strand index | 0 to count-1 |
count | Total strand count | — |
length | Strand length | World units |
maxLength | Maximum strand length in system | World units |
width | Strand width | World units |
t | Position along strand | 0 (root) to 1 (tip) |
Spatial Coordinates
| Variable | Description | Range |
|---|---|---|
u | UV X coordinate | 0–1 |
v | UV Y coordinate | 0–1 |
x | World X position | World units |
y | World Y position | World units |
z | World Z position | World units |
Clumping
| Variable | Description |
|---|---|
clump_id | Clump index (when using Clumping modifier) |
Constants
| Constant | Value |
|---|---|
pi | 3.14159... |
tau | 6.28318... (2π) |
e | 2.71828... |
Operators
Arithmetic
+ - * / ^ (or **) ()
Comparison
< > <= >= == !=
Returns 1.0 (true) or 0.0 (false).
Ternary
condition ? value_if_true : value_if_false
Functions
Random & Noise
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
- Use
indexfor per-strand randomization — consistent, deterministic - Use
tfor along-strand effects — 0 at root, 1 at tip - Use
clump_idfor per-clump variation — all strands in a clump get the same value - Clamp extreme values —
clamp(expr, 0.2, 0.8)avoids outliers - Start simple — test with constants, add complexity gradually
- Custom variables for tweaking —
$varNamecreates real-time sliders - 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