Perlin Marble Veining Effect

📅 April 23, 2026 🏷️ art
worth-a-look perlin-noise fluid-dynamics particle-system organic-veins generative-art flow-field procedural-marble emergent-patterns
Generated by GridFlow AI | Tags: perlin-noise, fluid-dynamics, particle-system, organic-veins, generative-art, flow-field, procedural-marble, emergent-patterns

💡 AI 提示词

Create an organic marble veining effect using Perlin noise as the foundation for a fluid flow field, with particles tracing vein paths that emerge from noise-driven dynamics.

🔧 核心算法要点

  1. Multi-octave Perlin noise combining three noise layers at different scales to create complex base marble texture
  2. Flow field computed from noise gradients where particle movement follows noise-derived angle fields
  3. Curl noise added to flow field for natural swirling motion simulating fluid dynamics in stone formation
  4. Particle system with velocity, drag, and maximum speed constraints creating organic vein tracer paths
  5. Dynamic vein generation based on noise thresholds - medium values produce directional veins while high values create nodes
  6. Dual-layer particle rendering with trail effects for depth, including fade-in and fade-out lifecycle management
  7. Vein coloration influenced by local noise values creating darker streaks in high-density regions
  8. Secondary elliptical overlays at vein intersections for enhanced mineral deposit appearance

🎨 原始代码

var sketch = function(p) {
    var container;
    var canvas;
    var particles = [];
    var field = [];
    var cols, rows;
    var cellSize = 5;
    var time = 0;
    var noiseScale = 0.008;
    
    p.setup = function() {
        container = document.getElementById('p5-wrapper');
        canvas = p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
        p.colorMode(p.HSB, 360, 100, 100, 100);
        p.noiseDetail(4, 0.5);
        
        cols = Math.ceil(p.width / cellSize) + 2;
        rows = Math.ceil(p.height / cellSize) + 2;
        
        for (var i = 0; i < 500; i++) {
            particles.push({
                x: p.random(p.width),
                y: p.random(p.height),
                vx: 0,
                vy: 0,
                life: p.random(150, 350),
                maxLife: 0,
                baseHue: p.randomGaussian(30, 15),
                speed: p.random(0.8, 2.5),
                age: 0
            });
            particles[i].maxLife = particles[i].life;
        }
    };
    
    p.draw = function() {
        p.noStroke();
        p.fill(0, 0, 95, 3);
        p.rect(0, 0, p.width, p.height);
        
        for (var x = 0; x < cols; x++) {
            for (var y = 0; y < rows; y++) {
                var idx = x + y * cols;
                var n1 = p.noise(x * noiseScale, y * noiseScale, time);
                var n2 = p.noise(x * noiseScale * 2.5, y * noiseScale * 2.5, time * 1.5 + 100);
                var n3 = p.noise(x * noiseScale * 0.3, y * noiseScale * 0.3, time * 0.5 + 200);
                field[idx] = n1 * 0.5 + n2 * 0.3 + n3 * 0.2;
            }
        }
        
        p.noFill();
        for (var x = 1; x < cols - 1; x++) {
            for (var y = 1; y < rows - 1; y++) {
                var idx = x + y * cols;
                var nVal = field[idx];
                
                if (nVal > 0.55 && nVal < 0.65) {
                    var angle = p.map(p.noise(x * noiseScale * 3, y * noiseScale * 3, time * 0.8), 0, 1, 0, p.TWO_PI);
                    var px = x * cellSize;
                    var py = y * cellSize;
                    var len = p.map(nVal, 0.55, 0.65, 8, 25);
                    
                    p.stroke(40, 25, 85, 35);
                    p.strokeWeight(p.map(nVal, 0.55, 0.65, 0.5, 1.2));
                    p.line(px, py, px + len * p.cos(angle), py + len * p.sin(angle));
                }
                
                if (nVal > 0.72) {
                    var px = x * cellSize;
                    var py = y * cellSize;
                    var thickness = p.map(nVal, 0.72, 1, 1, 3.5);
                    
                    p.stroke(35, 30, 70, 45);
                    p.strokeWeight(thickness);
                    p.ellipse(px, py, thickness * 1.5, thickness * 1.5);
                }
            }
        }
        
        for (var i = 0; i < particles.length; i++) {
            var part = particles[i];
            var col = Math.floor(part.x / cellSize);
            var row = Math.floor(part.y / cellSize);
            col = p.constrain(col, 0, cols - 1);
            row = p.constrain(row, 0, rows - 1);
            
            var nAngle = p.noise(col * noiseScale * 2, row * noiseScale * 2, time * 0.6) * p.TWO_PI * 4;
            part.vx += p.cos(nAngle) * 0.15;
            part.vy += p.sin(nAngle) * 0.15;
            
            var curl = p.noise(col * noiseScale * 4, row * noiseScale * 4, time * 0.3 + 50) * p.TWO_PI;
            part.vx += p.cos(curl) * 0.05;
            part.vy += p.sin(curl) * 0.05;
            
            var drag = 0.96;
            var maxSpeed = 3;
            part.vx *= drag;
            part.vy *= drag;
            
            var speed = p.sqrt(part.vx * part.vx + part.vy * part.vy);
            if (speed > maxSpeed) {
                part.vx = (part.vx / speed) * maxSpeed;
                part.vy = (part.vy / speed) * maxSpeed;
            }
            
            part.x += part.vx * part.speed;
            part.y += part.vy * part.speed;
            part.age++;
            
            if (part.x < 0) part.x += p.width;
            if (part.x > p.width) part.x -= p.width;
            if (part.y < 0) part.y += p.height;
            if (part.y > p.height) part.y -= p.height;
            
            var lifeRatio = part.life / part.maxLife;
            var fadeIn = p.min(part.age / 30, 1);
            var fadeOut = p.max(0, lifeRatio);
            var alpha = fadeIn * fadeOut * 70;
            
            if (alpha > 0.5) {
                var baseBright = p.map(nVal, 0, 1, 55, 92);
                var veinInfluence = p.constrain(p.map(field[col + row * cols], 0.5, 0.75, 1, 0), 0, 1);
                
                var hue = part.baseHue + veinInfluence * 20;
                var sat = 15 + veinInfluence * 35;
                var bright = baseBright - veinInfluence * 30;
                
                p.stroke(hue, sat, bright, alpha);
                p.strokeWeight(0.8 + lifeRatio * 0.6);
                p.point(part.x, part.y);
                
                if (part.age > 5 && lifeRatio > 0.3) {
                    p.stroke(hue, sat * 0.7, bright + 5, alpha * 0.4);
                    p.strokeWeight(1.2);
                    p.point(part.x - part.vx * 2, part.y - part.vy * 2);
                }
            }
            
            if (part.life <= 0 || (lifeRatio < 0.2 && p.random() < 0.01)) {
                part.x = p.random(p.width);
                part.y = p.random(p.height);
                part.vx = 0;
                part.vy = 0;
                part.life = p.random(200, 400);
                part.maxLife = part.life;
                part.age = 0;
                part.baseHue = p.randomGaussian(30, 15);
                part.speed = p.random(0.8, 2.5);
            } else {
                part.life -= 1;
            }
        }
        
        p.noStroke();
        for (var x = 0; x < cols; x++) {
            for (var y = 0; y < rows; y++) {
                var idx = x + y * cols;
                var n = field[idx];
                
                if (n < 0.4) {
                    var px = x * cellSize;
                    var py = y * cellSize;
                    var a = p.map(n, 0, 0.4, 8, 0);
                    
                    p.fill(200, 30, 85, a);
                    p.ellipse(px, py, 3, 3);
                }
            }
        }
        
        time += 0.003;
    };
    
    p.windowResized = function() {
        p.resizeCanvas(container.offsetWidth, container.offsetHeight);
        cols = Math.ceil(p.width / cellSize) + 2;
        rows = Math.ceil(p.height / cellSize) + 2;
    };
};

✨ AI 艺术解读

This piece captures the millions-year geological process of marble formation compressed into an eternal moment. The veins emerge organically from noise fields as if mineral-laden water once flowed through ancient limestone, leaving behind traces of its journey. Each particle traces a path that mirrors how real marble's characteristic veining forms through pressure and fluid movement through rock strata. The result feels both ancient and timeless, with complexity that suggests hand-crafted artistry despite emerging entirely from mathematical rules.

📝 补充说明

  • Increase cellSize for larger, more visible veins or decrease for denser micro-veining patterns
  • Modify noiseDetail parameters to change the 'roughness' of the marble texture from smooth to granular
  • Particle count directly impacts density of final veining - more particles create busier, more complex patterns
  • The three noise octave combination creates natural-looking stone without appearing artificially generated
  • Consider adding additional color layers for calcite and pyrite inclusions in the marble for extra realism