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

  1. Use index for per-strand randomization - Gives each strand unique but consistent value
  1. Use t for along-strand effects - Perfect for tapering (0=root, 1=tip)
  1. Use clump_id for per-clump variation - Makes each clump behave differently
  1. Use ternary for conditionals - condition ? a : b is cleaner than select()
  1. Use smoothstep for natural transitions - Smoother than linear lerp
  1. Clamp extreme values - clamp(rand(index), 0.2, 0.8) avoids outliers
  1. Test with simple values first - Start with constants, add complexity gradually
  1. Use custom variables for tweaking - $varName creates real-time sliders
  1. Keep expressions readable - Use parentheses for clarity
  1. 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 index or clump_id)
  • Verify random function called correctly
  • Try different seed values

Too much variation:

  • Reduce random range
  • Use clamp() to limit
  • Smooth with lerp() or smoothstep()

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