Expression-Based Systems
Generative art emerges from systems—simple rules that create complex, evolving visual output. In After Effects, expressions become your generative engine: mathematical functions, noise fields, recursive patterns, and emergent behaviors that produce art you couldn't design by hand. These techniques reward experimentation—small parameter changes yield dramatically different results.
Create self-similar branching structures that evolve over time. Apply to a shape layer's path or use with the Repeater to generate tree-like forms, neural networks, or organic growth patterns.
// Apply to Shape Layer > Contents > Shape > Path // Creates evolving fractal branch pattern iterations = 5; // Depth of recursion angle = 25; // Branch angle (degrees) shrink = 0.7; // Size reduction per iteration growth = time * 30; // Animation speed function branch(len, iter) { if (iter <= 0) return []; points = []; end = [0, -len]; points.push([0,0], end); // Left branch leftPoints = branch(len * shrink, iter - 1); for (i = 0; i < leftPoints.length; i++) { rotated = rotatePoint(leftPoints[i], -angle); points.push(add(rotated, end)); } // Right branch rightPoints = branch(len * shrink, iter - 1); for (i = 0; i < rightPoints.length; i++) { rotated = rotatePoint(rightPoints[i], angle); points.push(add(rotated, end)); } return points; } function rotatePoint(p, a) { rad = degreesToRadians(a); return [ p[0] * Math.cos(rad) - p[1] * Math.sin(rad), p[0] * Math.sin(rad) + p[1] * Math.cos(rad) ]; } // Animate growth animatedLen = 100 * Math.min(growth / 100, 1); branch(animatedLen, iterations);
angle to 60° for snowflake patternswiggle(0.5, 5) to angles for organic wind effectnoise(time) to drive angle changes over timeCreate particle systems that follow invisible force fields. Each particle reads its direction from a noise field, producing organic, river-like motion. Perfect for abstract fluid art, smoke trails, or ethereal energy flows.
// Apply to Position of multiple layers // Each layer gets unique seed via index seed = index * 100; // Unique per layer scale = 0.003; // Noise scale (smaller = smoother) speed = 50; // Movement speed // Get current position pos = value; // Sample noise field at position noiseVal = noise(pos[0] * scale + seed, pos[1] * scale + time * 0.1); // Convert noise to angle (0-360 degrees) angle = noiseVal * Math.PI * 4; // Calculate velocity velocity = [Math.cos(angle) * speed * 0.016, Math.sin(angle) * speed * 0.016]; // Apply movement pos + velocity;
Setup: Create 50-100 small shape layers (dots or short lines). Apply this expression to all their Position properties. Each will follow the invisible flow field, creating emergent stream patterns.
// Add to Rotation for particles to align with flow
seed = index * 100;
scale = 0.003;
pos = position;
noiseVal = noise(pos[0] * scale + seed, pos[1] * scale + time * 0.1);
radiansToDegrees(noiseVal * Math.PI * 4);
Create self-referential animations where each frame influences the next. Using valueAtTime() to read past states creates organic accumulation—like paint building up or erosion patterns forming.
// Apply to Scale - creates pulsing growth/decay cycles // Layer references its own past state feedback = 0.3; // Influence of past (0-1) decay = 0.98; // Natural decay factor noiseAmp = 10; // Random variation // Get previous frame's scale prevScale = valueAtTime(time - 0.033); // One frame back (30fps) // Apply feedback with noise variation = noise(time * 2) * noiseAmp; newScale = prevScale * decay + variation; // Blend with original value * (1 - feedback) + [newScale, newScale] * feedback;
// Apply to Position - creates wandering drift // Particle remembers where it's been memory = 0.4; // How much past affects present wanderStrength = 20; // Random walk intensity // Sample position from 5 frames ago pastPos = valueAtTime(Math.max(0, time - 0.167)); // Generate new target based on noise seed = index * 1000; targetX = pastPos[0] + (noise(time + seed) - 0.5) * wanderStrength; targetY = pastPos[1] + (noise(time + seed + 100) - 0.5) * wanderStrength; // Blend current toward target current = value; [ current[0] + (targetX - current[0]) * memory, current[1] + (targetY - current[1]) * memory ];
Mathematical harmonics create intricate geometric art. By combining multiple oscillating frequencies, you generate patterns that feel both precise and organic—like celestial mechanics or sound visualized.
// Apply to Position for elegant orbital motion // Classic Lissajous curves with time evolution freqX = 3; // Horizontal frequency freqY = 4; // Vertical frequency amplitude = 300; // Size of pattern phaseShift = time * 0.2; // Rotation over time // Base Lissajous t = time * 0.5; x = Math.sin(t * freqX + phaseShift) * amplitude; y = Math.sin(t * freqY) * amplitude; // Add secondary harmonic for complexity x += Math.sin(t * freqX * 2.3) * (amplitude * 0.3); y += Math.cos(t * freqY * 1.7) * (amplitude * 0.3); // Center on composition center = [thisComp.width / 2, thisComp.height / 2]; [center[0] + x, center[1] + y];
// Apply to multiple layers with index-based variation // Creates spirograph-like layered patterns layerOffset = index * 0.1; // Each layer different baseFreq = 5; radius = 150 + index * 10; // Expanding rings t = time * 0.3; angle = t * (baseFreq + layerOffset); // Epitrochoid math (spirograph) R = 100; r = 30 + index * 5; d = 50; x = (R + r) * Math.cos(angle) - d * Math.cos((R + r) / r * angle); y = (R + r) * Math.sin(angle) - d * Math.sin((R + r) / r * angle); center = [thisComp.width / 2, thisComp.height / 2]; [center[0] + x, center[1] + y];
1:1 - Circle or diagonal line1:2 - Figure-eight2:3 - Complex knot pattern3:4 - Intricate weave5:7 - Dense, almost chaoticSimulate flocking behavior where each particle follows simple rules: avoid crowding, align with neighbors, move toward the center. The result is lifelike swarm motion that emerges from local interactions—no central control.
// Simplified boid-like behavior // Apply to Position, use with multiple layers myPos = value; myIndex = index; separation = 80; // Personal space radius alignStrength = 0.05; // How much to match neighbors cohesionStrength = 0.01; // Pull toward center maxSpeed = 200; // Find neighbors (simplified - checks all layers) avgPos = [0, 0]; avgVel = [0, 0]; separationForce = [0, 0]; count = 0; for (i = 1; i <= thisComp.numLayers; i++) { if (i == myIndex) continue; otherPos = thisComp.layer(i).position; dist = length(myPos - otherPos); if (dist < 300) { // Perception radius avgPos += otherPos; avgVel += thisComp.layer(i).position.velocity || [0,0]; count++; // Separation: push away if too close if (dist < separation && dist > 0) { force = (myPos - otherPos) / dist * (separation - dist); separationForce += force; } } } if (count > 0) { avgPos /= count; avgVel /= count; // Cohesion: pull toward center of neighbors cohesion = (avgPos - myPos) * cohesionStrength; // Alignment: match average velocity alignment = avgVel * alignStrength; // Apply forces velocity = (cohesion + alignment + separationForce * 0.1); // Limit speed if (length(velocity) > maxSpeed * 0.016) { velocity = normalize(velocity) * maxSpeed * 0.016; } myPos + velocity; } else { myPos; }
Note: For performance with many particles, consider using AE's built-in Particle World or third-party plugins like Trapcode Particular. This expression approach works beautifully for 20-50 layers.
Simulate chemical reactions that create organic patterns—spots, stripes, and coral-like structures found in nature (zebra stripes, leopard spots, shell patterns). In AE, we approximate this with displacement and feedback.
// Apply to Displacement Map offset or turbulent displace // Creates evolving organic texture patterns feed = 0.055; // Pattern density (0.01-0.1) kill = 0.062; // Pattern type (0.045-0.07) scale = 50; // Pattern size // Sample multiple noise octaves n1 = noise([time * 0.1, 0]); n2 = noise([time * 0.2 + 100, 0]); n3 = noise([time * 0.05 + 200, 0]); // Combine for complexity pattern = (n1 * feed + n2 * kill + n3 * 0.5) * scale; // Add subtle animation pattern + Math.sin(time * 0.5) * 5;
Visual Approach: Apply this to a solid layer with Fractal Noise or Cell Pattern, then use that as a displacement map for your collage layers. The evolving pattern will create organic, living texture shifts.
// Alternative: Direct position displacement for "living" edges // Apply to Position of a shape layer vertex or mask path basePos = value; displaceScale = 20; freq = 0.5; // Displacement based on position and time dx = noise(basePos[0] * 0.01, time * freq) * displaceScale; dy = noise(basePos[1] * 0.01 + 100, time * freq) * displaceScale; [basePos[0] + dx, basePos[1] + dy];
Connect your generative systems to audio for synesthetic experiences. Sound frequencies drive visual parameters—bass pulses through scale, treble sparkles through rotation, midtones flow through position.
// Apply to Scale - pulses with audio amplitude // Requires audio layer in composition audioLayer = thisComp.layer("Audio"); // Name of your audio layer samplePoint = time; // Current time sample duration = 0.1; // Sample window // Get audio amplitude (both channels averaged) audioAmp = audioLayer.audioAmplitude; left = audioAmp.channelLeft; right = audioAmp.channelRight; avgAmp = (left + right) / 2; // Smooth the response smoothAmp = avgAmp.smooth(0.2, 5); // Apply to scale with base value baseScale = 100; reactivity = 0.5; // How much audio affects scale [baseScale + smoothAmp * reactivity, baseScale + smoothAmp * reactivity];
// Apply to Rotation - responds to frequency bands // Using Audio Spectrum effect as driver // Reference Audio Spectrum keyframes or expression spec = effect("Audio Spectrum")("Audio Spectrum"); // Extract different frequency ranges bass = spec.valueAtTime(time)[0]; // Low frequencies mid = spec.valueAtTime(time)[5]; // Mid frequencies treble = spec.valueAtTime(time)[10]; // High frequencies // Combine for complex rotation baseRot = value; rotSpeed = bass * 0.5 + mid * 0.3; wobble = Math.sin(time * treble * 0.1) * 10; baseRot + rotSpeed + wobble;
smooth() to reduce jittery responseGrid-based systems where each cell's state depends on its neighbors. Conway's Game of Life is the classic, but variations produce endless pattern types—caves, mazes, coral, cities.
// Simplified cell state using noise as "neighbor influence" // Apply to Opacity or Scale for grid of shapes gridX = Math.floor(position[0] / 50); // Cell column gridY = Math.floor(position[1] / 50); // Cell row // Pseudo-neighborhood using noise neighbors = noise(gridX * 0.1 + time * 0.2, gridY * 0.1); // State transition rules if (neighbors > 0.6) { // "Alive" - bright 100; } else if (neighbors > 0.3) { // "Dying" - fading 50 + Math.sin(time * 3) * 20; } else { // "Dead" - dim 10; }
Setup: Create a grid of small squares using the Repeater (20x20 copies), then apply this expression to Opacity. Each "cell" responds to its virtual neighbors, creating evolving patterns across the grid.
The real magic happens when you layer these systems. Try these combinations:
For artists working with photography-based collage—static images assembled in Photoshop, then brought into After Effects for subtle animation—these generative techniques serve as invisible glue. They don't replace your collage work; they breathe life into it. Instead of manually keyframing every element, you establish behaviors that evolve organically, creating motion that feels natural rather than mechanical.
Photoshop → After Effects Bridge: Export your collage layers as separate PNGs with transparency. In AE, these become the foundation upon which generative motion operates—dust motes drifting across a figure, a cutout flower hovering in dreamlike suspension, or the entire composition pulsing subtly to ambient sound.
Add generative particles over your collage—dust, snow, or ethereal energy—that move with organic, non-repeating paths. Unlike manually animated particles that loop predictably, noise-driven flow fields create infinite variation.
// Apply to Position of particle layers over your collage // Creates gentle, organic drift rather than mechanical motion seed = index * 100; pos = value; noiseVal = noise(pos[0] * 0.002 + seed, pos[1] * 0.002 + time * 0.05); angle = noiseVal * Math.PI * 4; velocity = [Math.cos(angle) * 0.5, Math.sin(angle) * 0.5]; // Slow, subtle pos + velocity;
A cutout figure or object from your collage can hover with meditative, breathing motion. Lissajous curves provide mathematical harmony—movement that feels underwater or in a dream state.
// Apply to Position of a photo cutout // Slow, meditative floating—no keyframes needed freqX = 2; freqY = 3; // Harmonious frequencies t = time * 0.2; // Very slow time scale x = Math.sin(t * freqX) * 15; // Small range—subtle y = Math.sin(t * freqY) * 10 + Math.cos(t * 5) * 5; value + [x, y];
Photo elements of branches, vines, or neural networks can appear to grow or spread organically. Apply fractal branching to a shape layer mask that reveals your photo, or displace the edges of a cutout to make it breathe.
// Apply to a mask path or shape layer revealing your photo // Angle "breathes" over time for organic movement baseAngle = 25; breath = Math.sin(time * 0.3) * 5; // Slow oscillation baseAngle + breath; // Combine with fractal branching code from Section 1 // Use the result to animate a mask expansion or trim paths
Your entire collage can breathe with the soundtrack—imperceptible mathematics, but felt emotionally. Scale, opacity, or position respond to audio amplitude, creating a living relationship between image and sound.
// Apply to Scale or Opacity of your collage layer // Subtle pulse synchronized to ambient sound base = 100; audio = thisComp.layer("Audio").audioAmplitude; amp = (audio.channelLeft + audio.channelRight) / 2; // Very subtle reactivity—felt, not obvious [base + amp * 0.05, base + amp * 0.05];
The key insight: these mathematical systems become the atmosphere of your collage—present but not overwhelming, supporting the photographic content rather than competing with it. The motion should feel like a natural property of the image, as if the photograph always contained this latent energy waiting to be released.
A layered composition combining multiple techniques:
// LAYER 1: Background flow field (50 small dots) // Position expression - gentle drift seed = index * 100; scale = 0.002; pos = value; noiseVal = noise(pos[0] * scale + seed, pos[1] * scale + time * 0.05); angle = noiseVal * Math.PI * 4; velocity = [Math.cos(angle) * 2, Math.sin(angle) * 2]; pos + velocity; // LAYER 2: Central Lissajous orbiter // Position expression - mathematical harmony freqX = 3; freqY = 4; t = time * 0.3; x = Math.sin(t * freqX) * 200 + Math.sin(t * 7) * 50; y = Math.sin(t * freqY) * 200 + Math.cos(t * 5) * 50; [thisComp.width/2 + x, thisComp.height/2 + y]; // LAYER 3: Fractal branches (shape layer) // Path expression with time-evolving angle angle = 25 + Math.sin(time * 0.2) * 10; // Breathing angle // (Use fractal branching code from Section 1) // LAYER 4: Audio-reactive overlay // Scale linked to audio amplitude base = 100; audio = thisComp.layer("Audio").audioAmplitude; amp = (audio.channelLeft + audio.channelRight) / 2; [base + amp * 0.3, base + amp * 0.3];