Perlin Noise Crystal Lattice Distortion

📅 April 23, 2026 🏷️ art
top-ones perlin-noise crystal-lattice emergent-geometry noise-distortion flow-field multi-layer kinetic-art
Generated by GridFlow AI | Tags: perlin-noise, crystal-lattice, emergent-geometry, noise-distortion, flow-field, multi-layer, kinetic-art

💡 AI 提示词

Perlin Noise Crystal Lattice Distortion - Create a generative art piece featuring a multi-layered crystal lattice structure where Perlin noise drives organic distortion. The lattice points connect with flowing lines, particles drift through the field, and the entire structure breathes and pulses with noise-driven animation.

🔧 核心算法要点

  1. Multi-layered lattice grid system with independent noise parameters per layer for depth complexity
  2. Three-frequency Perlin noise compositing: low-frequency for large displacement, medium for detail, high for rapid variation
  3. Time-evolving noise fields that drive both point positions and particle flow directions
  4. Soft-edge particle system influenced by local noise angles for organic motion
  5. Connection threshold algorithm that links nearby lattice points within dynamic radius creating crystal facets
  6. Smooth interpolation (lerp) for all position updates preventing jarring movements
  7. HSB color cycling with layer-based hue shifts and brightness variation based on local noise values

🎨 原始代码

var sketch = function(p) {
    let lattice = [];
    let cols, rows;
    let spacing = 35;
    let time = 0;
    let container;
    let connections = [];
    let particles = [];
    let layers = 3;
    
    p.setup = function() {
        container = document.getElementById('p5-wrapper');
        p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
        p.colorMode(p.HSB, 360, 100, 100, 100);
        p.background(0);
        p.noStroke();
        
        initializeLattice();
        initializeParticles();
    };
    
    function initializeLattice() {
        lattice = [];
        cols = Math.floor(p.width / spacing) + 4;
        rows = Math.floor(p.height / spacing) + 4;
        
        for (let layer = 0; layer < layers; layer++) {
            let layerOffset = layer * 50;
            let layerNoiseScale = 0.015 + layer * 0.008;
            let layerTimeScale = 0.3 + layer * 0.15;
            
            for (let i = 0; i < cols; i++) {
                for (let j = 0; j < rows; j++) {
                    let baseX = i * spacing - spacing * 2;
                    let baseY = j * spacing - spacing * 2;
                    
                    lattice.push({
                        baseX: baseX,
                        baseY: baseY,
                        layer: layer,
                        noiseScale: layerNoiseScale,
                        timeScale: layerTimeScale,
                        layerOffset: layerOffset,
                        noiseX: 0,
                        noiseY: 0,
                        hue: (layer * 120 + p.random(30)) % 360
                    });
                }
            }
        }
    }
    
    function initializeParticles() {
        for (let i = 0; i < 60; i++) {
            particles.push({
                x: p.random(p.width),
                y: p.random(p.height),
                vx: p.random(-0.5, 0.5),
                vy: p.random(-0.5, 0.5),
                size: p.random(1, 3),
                life: p.random(100, 255),
                hue: p.random(360)
            });
        }
    }
    
    p.draw = function() {
        p.background(0, 0, 5, 25);
        
        time += 0.008;
        
        updateLattice();
        drawConnections();
        drawLatticePoints();
        updateAndDrawParticles();
        drawFlowField();
    };
    
    function updateLattice() {
        for (let i = 0; i < lattice.length; i++) {
            let point = lattice[i];
            
            let noiseVal1 = p.noise(
                point.baseX * point.noiseScale * 0.5,
                point.baseY * point.noiseScale * 0.5,
                time * point.timeScale
            );
            
            let noiseVal2 = p.noise(
                point.baseX * point.noiseScale * 2,
                point.baseY * point.noiseScale * 2,
                time * point.timeScale * 0.5
            );
            
            let noiseVal3 = p.noise(
                point.baseX * point.noiseScale * 0.2 + 1000,
                point.baseY * point.noiseScale * 0.2 + 1000,
                time * point.timeScale * 0.3
            );
            
            let displacement1 = p.map(noiseVal1, 0, 1, -25, 25);
            let displacement2 = p.map(noiseVal2, 0, 1, -8, 8);
            let displacement3 = p.map(noiseVal3, 0, 1, -40, 40);
            
            point.x = point.baseX + displacement1 + displacement2 * p.sin(time + noiseVal1 * 10);
            point.y = point.baseY + displacement1 * 0.7 + displacement3 * p.cos(time * 0.7 + noiseVal2 * 8);
            point.displayX = p.lerp(point.displayX || point.x, point.x, 0.1);
            point.displayY = p.lerp(point.displayY || point.y, point.y, 0.1);
            
            if (!point.displayX) {
                point.displayX = point.x;
                point.displayY = point.y;
            }
        }
    }
    
    function drawConnections() {
        let connectionRadius = 55;
        
        for (let i = 0; i < lattice.length; i++) {
            let point1 = lattice[i];
            
            for (let j = i + 1; j < lattice.length; j++) {
                let point2 = lattice[j];
                
                if (point1.layer !== point2.layer) continue;
                
                let dx = point2.displayX - point1.displayX;
                let dy = point2.displayY - point1.displayY;
                let distance = p.sqrt(dx * dx + dy * dy);
                
                if (distance < connectionRadius) {
                    let alpha = p.map(distance, 0, connectionRadius, 80, 10);
                    let weight = p.map(distance, 0, connectionRadius, 2.5, 0.3);
                    let hueShift = (point1.hue + point2.hue) / 2 + time * 20;
                    
                    p.stroke(hueShift % 360, 60, 90, alpha);
                    p.strokeWeight(weight);
                    p.line(point1.displayX, point1.displayY, point2.displayX, point2.displayY);
                }
            }
        }
    }
    
    function drawLatticePoints() {
        p.noStroke();
        
        for (let i = 0; i < lattice.length; i++) {
            let point = lattice[i];
            let layerAlpha = p.map(point.layer, 0, layers - 1, 100, 40);
            let size = p.map(point.layer, 0, layers - 1, 5, 2.5);
            
            let pulse = p.sin(time * 3 + point.baseX * 0.02 + point.baseY * 0.02) * 0.5 + 0.5;
            let dynamicSize = size + pulse * 2;
            
            let noiseBrightness = p.noise(point.baseX * 0.01, point.baseY * 0.01, time);
            let brightness = p.map(noiseBrightness, 0, 1, 70, 100);
            
            p.fill(point.hue, 50, brightness, layerAlpha);
            p.ellipse(point.displayX, point.displayY, dynamicSize, dynamicSize);
            
            if (pulse > 0.7) {
                p.fill(point.hue, 30, 100, layerAlpha * 0.5);
                p.ellipse(point.displayX, point.displayY, dynamicSize * 2.5, dynamicSize * 2.5);
            }
        }
    }
    
    function updateAndDrawParticles() {
        for (let i = 0; i < particles.length; i++) {
            let part = particles[i];
            
            let angle = p.noise(part.x * 0.005, part.y * 0.005, time * 2) * p.TWO_PI * 2;
            part.vx += p.cos(angle) * 0.05;
            part.vy += p.sin(angle) * 0.05;
            
            part.vx *= 0.98;
            part.vy *= 0.98;
            
            part.x += part.vx;
            part.y += part.vy;
            
            part.life -= 0.5;
            
            if (part.life <= 0 || part.x < 0 || part.x > p.width || part.y < 0 || part.y > p.height) {
                part.x = p.random(p.width);
                part.y = p.random(p.height);
                part.life = p.random(100, 255);
                part.vx = p.random(-0.5, 0.5);
                part.vy = p.random(-0.5, 0.5);
            }
            
            p.fill(part.hue, 70, 90, part.life * 0.4);
            p.ellipse(part.x, part.y, part.size, part.size);
        }
    }
    
    function drawFlowField() {
        let fieldRes = 80;
        p.noStroke();
        
        for (let x = 0; x < p.width; x += fieldRes) {
            for (let y = 0; y < p.height; y += fieldRes) {
                let angle = p.noise(x * 0.003, y * 0.003, time * 0.5) * p.TWO_PI * 2;
                let length = p.noise(x * 0.005 + 500, y * 0.005 + 500, time * 0.3) * 30;
                
                let cx = x + p.cos(angle) * length;
                let cy = y + p.sin(angle) * length;
                
                let hue = (p.noise(x * 0.002, y * 0.002) * 60 + 180) % 360;
                p.fill(hue, 40, 80, 15);
                p.ellipse(cx, cy, 20, 20);
            }
        }
    }
    
    p.windowResized = function() {
        p.resizeCanvas(container.offsetWidth, container.offsetHeight);
        p.background(0);
        initializeLattice();
    };
}; // p5 init stripped

✨ AI 艺术解读

This piece visualizes the tension between rigid geometric order and organic chaos through a crystal lattice that breathes with Perlin noise. The multi-layered structure suggests dimensional depth while the flowing connections reveal hidden relationships between distant points. The drifting particles act as witnesses to the lattice's constant transformation, embodying the idea that even in crystalline precision, there exists perpetual flux. The piece invites contemplation on how structure and randomness are not opposites but dancing partners in the emergence of complexity.

📝 补充说明

  • Layer opacity and point sizes should decrease with depth to create convincing parallax and spatial hierarchy
  • The connection drawing loop should skip inter-layer connections to maintain coherent crystal topology
  • Time scaling per layer creates staggered animation rhythms preventing mechanical synchronization
  • Particle alpha decay and respawning prevents memory accumulation while maintaining constant motion density
  • Flow field overlay with very low opacity adds ambient texture without distracting from primary lattice structure