FollicleScript Expressions
FollicleScript is FollicleFX's expression language for procedural hair control. Write simple expressions that are compiled to bytecode and evaluated on the GPU for real-time performance on 100,000+ strands.
Quick Start
Click the ƒx button next to any parameter to open the Expression Editor.
Simple Examples:
0.8 # Constant value
rand(index) # Random value per strand
0.5 + rand(index) * 0.3 # Base + random variation
lerp(0.7, 1.0, v) # Gradient from bottom to top
When to Use Expressions
Use expressions for:
- Per-strand randomization
- UV-based gradients
- Conditional logic
- Procedural patterns
- Clump-based variation
Use sliders for:
- Global adjustments
- Quick tweaking
- Simple uniform values
Variables
Strand Properties
| Variable | Description | Range | |----------|-------------|-------| | index | Strand index | 0 to count-1 | | count | Total strand count | - | | length | Strand length | 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 (if using Clumping modifier) |
Constants
| Constant | Value | |----------|-------| | pi | 3.14159... | | tau | 6.28318... (2π) | | e | 2.71828... |
Operators
Arithmetic
| Operator | Description | Example | |----------|-------------|---------| | + | Addition | 0.5 + 0.3 | | - | Subtraction | 1.0 - 0.2 | | * | Multiplication | 0.5 * 2.0 | | / | Division | 1.0 / 2.0 | | ^ or | Power | 2 ^ 3 or 2 3 | | () | Grouping | (1 + 2) * 3 |
Comparison
| Operator | Description | Returns | |----------|-------------|---------| | < | Less than | 1.0 (true) or 0.0 (false) | | > | Greater than | 1.0 or 0.0 | | <= | Less or equal | 1.0 or 0.0 | | >= | Greater or equal | 1.0 or 0.0 | | == | Equal | 1.0 or 0.0 | | != | Not equal | 1.0 or 0.0 |
Ternary Operator
condition ? value_if_true : value_if_false
Examples:
v > 0.5 ? 1.0 : 0.5 # Returns 1.0 if v > 0.5, else 0.5
rand(index) < 0.3 ? 0 : 1 # 30% chance of 0, 70% chance of 1
index < 100 ? 0.8 : 1.0 # First 100 strands get 0.8
Functions
Random & Noise
rand(seed)
- Deterministic random 0-1
- Same seed = same result
- Example:
rand(index)- different per strand
rand3(min, max, seed)
- Random in range [min, max]
- Example:
rand3(0.5, 1.5, index)- random 0.5 to 1.5
noise(x) / noise(x, y) / noise(x, y, z)
- Perlin noise
- Smooth, organic patterns
- Example:
noise(u * 10, v * 10)- 2D noise pattern
Math Functions
abs(x) - Absolute value
abs(-0.5) # Returns 0.5
sqrt(x) - Square root
sqrt(4.0) # Returns 2.0
pow(x, y) - Power
pow(2, 3) # Returns 8.0
exp(x) - e^x
exp(1) # Returns 2.718...
log(x) - Natural logarithm
log(e) # Returns 1.0
sign(x) - Returns -1, 0, or 1
sign(-5) # Returns -1
sign(0) # Returns 0
sign(5) # Returns 1
mod(x, y) - Modulo
mod(5, 2) # Returns 1
min(a, b) - Minimum
min(0.3, 0.7) # Returns 0.3
max(a, b) - Maximum
max(0.3, 0.7) # Returns 0.7
Trigonometry
sin(x) / cos(x) / tan(x)
- Standard trigonometric functions
- Input in radians
sin(pi / 2) # Returns 1.0
cos(0) # Returns 1.0
Rounding
floor(x) - Round down
floor(3.7) # Returns 3.0
ceil(x) - Round up
ceil(3.2) # Returns 4.0
frac(x) - Fractional part
frac(3.7) # Returns 0.7
Interpolation
clamp(x, min, max) - Clamp to range
clamp(1.5, 0.0, 1.0) # Returns 1.0
clamp(-0.5, 0.0, 1.0) # Returns 0.0
lerp(a, b, t) - Linear interpolation
lerp(0.0, 1.0, 0.5) # Returns 0.5
lerp(0.5, 1.5, 0.25) # Returns 0.75
smoothstep(edge0, edge1, x) - Smooth S-curve
smoothstep(0.0, 1.0, 0.5) # Returns 0.5 with smooth curve
select(cond, a, b) - If cond > 0 then a, else b
select(v > 0.5, 1.0, 0.5) # Same as ternary operator
Texture Sampling
map(u, v) - Sample bound texture at UV
map(u, v) # Returns texture value at UV coordinate
Custom Variables
Create dynamic UI sliders using $varName syntax:
$amount * rand(index) + $offset
This automatically creates "amount" and "offset" sliders in the UI. Up to 16 custom variables are supported.
Example:
$base + rand(index) * $variation
Creates two sliders: "base" and "variation" that you can adjust in real-time.
Common Patterns
Per-Strand Randomization
Basic Random:
rand(index) # Random 0-1 per strand
Random Range:
0.8 + rand(index) * 0.4 # Range: 0.8 to 1.2
rand3(0.5, 1.5, index) # Random 0.5 to 1.5
Clamped Random:
clamp(rand(index), 0.2, 0.8) # Random 0.2 to 0.8
Gradients
Bottom to Top:
v # 0 at bottom, 1 at top
Top to Bottom:
1.0 - v # 1 at top, 0 at bottom
Left to Right:
u # 0 at left, 1 at right
Custom Range:
lerp(0.5, 1.5, v) # 0.5 at bottom, 1.5 at top
Along-Strand Effects
Root to Tip:
t # 0 at root, 1 at tip
Tip to Root:
1.0 - t # 1 at root, 0 at tip
Taper:
lerp(1.0, 0.1, t) # 1.0 at root, 0.1 at tip
Smooth Taper:
smoothstep(0.0, 1.0, t) # Smooth S-curve taper
Center/Edge Falloff
Dense in Center:
1.0 - abs(u - 0.5) * 2.0 # 1.0 at center, 0.0 at edges
Dense at Edges:
abs(u - 0.5) * 2.0 # 0.0 at center, 1.0 at edges
Radial Falloff:
1.0 - sqrt((u - 0.5)^2 + (v - 0.5)^2) * 2.0
Patterns
Sine Wave:
sin(u * tau) * 0.5 + 0.5 # Horizontal sine wave (0-1)
Vertical Stripes:
sin(v * tau * 2) * 0.5 + 0.5 # 2 vertical stripes
Checkerboard:
(floor(u * 4) + floor(v * 4)) % 2
Noise Variation
2D Organic Pattern:
noise(u * 10, v * 10) # Organic noise pattern
3D Noise with Strand Variation:
noise(u * 5, v * 5, index) # Per-strand noise variation
Multi-Scale Noise:
noise(u * 3, v * 3) * 0.5 + noise(u * 10, v * 10) * 0.5
Per-Clump Variation
Random per Clump:
rand(clump_id) # Same for all strands in clump
Clump Strength Variation:
0.5 + rand(clump_id) * 0.5 # Clumps vary 0.5 to 1.0
Alternating Clumps:
clump_id % 2 # Alternates 0 and 1
Modifier-Specific Examples
Density
UV-Based Density:
lerp(0.5, 1.0, v) # Denser at top
Center Dense:
1.0 - abs(u - 0.5) * 1.5 # Dense in center
Random Patches:
noise(u * 5, v * 5) > 0.3 ? 1.0 : 0.0
Clumping
Per-Clump Strength:
rand3(0.7, 1.0, clump_id) # Each clump 70-100% strength
UV-Based Clumping:
lerp(0.5, 1.0, v) # More clumping at top
Random Stray Strands:
rand(index) < 0.1 ? 0.0 : 1.0 # 10% strands don't clump
Frizz
Tip Frizz:
lerp(0.0, 1.0, t) # More frizz toward tips
Flyaway Selection:
rand(index) < 0.05 ? 3.0 : 0.5 # 5% get high frizz (flyaways)
UV-Based Frizz:
v * 2.0 # More frizz at top
Cut
Random Trim:
rand3(0.8, 1.0, index) # Keep 80-100% length
XGen-Style Remove 2%:
1.0 - rand3(0.01, 0.03, index) # Remove 1-3%
UV Gradient Cut:
lerp(0.7, 1.0, v) # Shorter at bottom
Width
Taper to Tips:
lerp(1.0, 0.1, t) # Thick at root, thin at tip
Random Width:
rand3(0.8, 1.2, index) # Vary 80-120%
Tips & Best Practices
- Use
indexfor per-strand randomization - Gives each strand unique but consistent value
- Use
tfor along-strand effects - Perfect for tapering (0=root, 1=tip)
- Use
clump_idfor per-clump variation - Makes each clump behave differently
- Use ternary for conditionals -
condition ? a : bis cleaner thanselect()
- Use
smoothstepfor natural transitions - Smoother than linearlerp
- Clamp extreme values -
clamp(rand(index), 0.2, 0.8)avoids outliers
- Test with simple values first - Start with constants, add complexity gradually
- Use custom variables for tweaking -
$varNamecreates real-time sliders
- Keep expressions readable - Use parentheses for clarity
- Check value ranges - Most parameters expect 0-1 range
Debugging Expressions
Expression doesn't work:
- Check syntax (missing parentheses, typos)
- Verify variable names (case-sensitive)
- Test with simple constant first
Values out of range:
- Use
clamp()to limit output - Check min/max in
rand3() - Verify lerp parameters
No variation:
- Check seed parameter (use
indexorclump_id) - Verify random function called correctly
- Try different seed values
Too much variation:
- Reduce random range
- Use
clamp()to limit - Smooth with
lerp()orsmoothstep()
Performance Notes
- Expressions are compiled to bytecode
- GPU-evaluated for real-time performance
- Simple expressions (arithmetic, rand) are very fast
- Complex expressions (noise, trig) are slightly slower
- No practical limit on expression complexity
- All 100,000+ strands evaluated in parallel
---
Next: Texture Masks - Control modifiers with textures Previous: Cards Tab