Subsurface Organic Matter

📅 April 23, 2026 🏷️ art
worth-a-look perlin-noise subsurface-scattering organic-matter particle-systems flow-fields emergent-behavior organic-textures
Generated by GridFlow AI | Tags: perlin-noise, subsurface-scattering, organic-matter, particle-systems, flow-fields, emergent-behavior, organic-textures

💡 AI 提示词

Perlin noise driven subsurface organic matter with multi-layered particle systems simulating living tissue, featuring flowing vein-like structures, depth-based color variation, and pulsing biological rhythms.

🔧 核心算法要点

  1. Multi-octave Perlin noise fields driving particle velocity vectors at different scales for complex emergent motion
  2. Layered subsurface cellular background using grid-based noise sampling with depth-dependent coloring
  3. Organic vein network generated with flowing curve vertices modulated by noise for lifelike branching patterns
  4. Particle system with three visual layers per particle creating subsurface scattering depth effect
  5. Central breathing core glow that pulses with sinusoidal rhythm simulating heartbeat-like biological activity
  6. Margin-based boundary forces and center-distance modulation for natural spatial distribution

🎨 原始代码

var sketch = function(p) {
    var particles = [];
    var numParticles = 900;
    var noiseScale1 = 0.003;
    var noiseScale2 = 0.007;
    var timeScale = 0.0003;
    var t = 0;
    var veins = [];
    var numVeins = 25;

    p.setup = function() {
        var container = document.getElementById('p5-wrapper');
        p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
        p.colorMode(p.HSB, 360, 100, 100, 100);
        p.background(20, 30, 8);
        p.noStroke();

        for (var i = 0; i < numParticles; i++) {
            particles.push(createParticle(p.random(p.width), p.random(p.height), i));
        }

        for (var i = 0; i < numVeins; i++) {
            veins.push({
                x: p.random(p.width),
                y: p.random(p.height),
                angle: p.random(p.TWO_PI),
                length: p.random(80, 200),
                hue: p.random(10, 35),
                speed: p.random(0.3, 0.8)
            });
        }
    };

    function createParticle(x, y, id) {
        var hueBase = 5 + p.noise(id * 0.02) * 25;
        var satBase = 65 + p.noise(id * 0.03) * 35;
        return {
            pos: p.createVector(x, y),
            vel: p.createVector(0, 0),
            acc: p.createVector(0, 0),
            baseSize: 3 + p.noise(id * 0.01) * 10,
            hue: hueBase,
            sat: satBase,
            bri: 70 + p.noise(id * 0.025) * 30,
            alpha: 25 + p.noise(id * 0.015) * 35,
            phase: p.random(p.TWO_PI),
            layer: Math.floor(p.random(3))
        };
    }

    p.draw = function() {
        p.fill(20, 30, 8, 8);
        p.rect(0, 0, p.width, p.height);

        t += timeScale * 60;

        drawSubsurfaceLayers();
        drawVeins();
        updateParticles();
        drawCoreGlow();
    };

    function drawSubsurfaceLayers() {
        for (var layer = 0; layer < 3; layer++) {
            var layerOffset = t * (0.5 + layer * 0.3);
            var numCells = 15 + layer * 5;
            var cellW = p.width / numCells;
            var cellH = p.height / numCells;

            for (var i = 0; i < numCells; i++) {
                for (var j = 0; j < numCells; j++) {
                    var cx = i * cellW + cellW * 0.5;
                    var cy = j * cellH + cellH * 0.5;
                    var n1 = p.noise(i * 0.15 + layerOffset, j * 0.15, t * 0.5);
                    var n2 = p.noise(i * 0.08 + layerOffset * 0.7, j * 0.08, t * 0.3 + 100);
                    var combined = (n1 + n2 * 0.5) / 1.5;

                    var hue = 8 + combined * 18 + layer * 3;
                    var sat = 40 + combined * 40 - layer * 10;
                    var bri = 12 + combined * 18;
                    var alpha = 15 + combined * 20;

                    var offsetX = p.noise(i * 0.2 + t, j * 0.2) * cellW * 0.3;
                    var offsetY = p.noise(i * 0.2, j * 0.2 + t) * cellH * 0.3;
                    var size = cellW * (0.6 + combined * 0.4) * (1 + layer * 0.1);

                    p.fill(hue, sat, bri, alpha);
                    p.ellipse(cx + offsetX, cy + offsetY, size, size * 0.9);
                }
            }
        }
    }

    function drawVeins() {
        for (var i = 0; i < veins.length; i++) {
            var v = veins[i];
            var angleNoise = p.noise(v.x * 0.002, v.y * 0.002, t * v.speed);
            v.angle += (angleNoise - 0.5) * 0.05;

            var pulse = 0.5 + p.noise(v.x * 0.005 + t, v.y * 0.005) * 0.5;
            var flowOffset = t * 50 * v.speed;

            p.strokeWeight(1 + pulse * 2);
            p.stroke(v.hue, 60, 40, 10 + pulse * 15);
            p.noFill();

            p.beginShape();
            for (var j = 0; j < v.length; j += 8) {
                var nx = p.noise(v.x * 0.01 + j * 0.02, flowOffset) - 0.5;
                var ny = p.noise(v.y * 0.01 + j * 0.02, flowOffset + 50) - 0.5;
                var px = v.x + Math.cos(v.angle) * j + nx * 30;
                var py = v.y + Math.sin(v.angle) * j + ny * 30;
                p.curveVertex(px, py);
            }
            p.endShape();
        }
        p.noStroke();
    }

    function updateParticles() {
        for (var i = 0; i < particles.length; i++) {
            var pt = particles[i];

            var n1 = p.noise(pt.pos.x * noiseScale1, pt.pos.y * noiseScale1, t);
            var n2 = p.noise(pt.pos.x * noiseScale2, pt.pos.y * noiseScale2, t * 1.5 + 100);

            var angle1 = n1 * p.TWO_PI * 3;
            var angle2 = n2 * p.TWO_PI * 2;

            var force1 = (n1 - 0.5) * 0.4;
            var force2 = (n2 - 0.5) * 0.2;

            var fx = Math.cos(angle1) * force1 + Math.cos(angle2) * force2;
            var fy = Math.sin(angle1) * force1 + Math.sin(angle2) * force2;

            pt.acc.set(fx, fy);
            pt.vel.add(pt.acc);
            pt.vel.mult(0.94);
            pt.pos.add(pt.vel);

            var margin = 80;
            if (pt.pos.x < margin) pt.vel.x += 0.15;
            if (pt.pos.x > p.width - margin) pt.vel.x -= 0.15;
            if (pt.pos.y < margin) pt.vel.y += 0.15;
            if (pt.pos.y > p.height - margin) pt.vel.y -= 0.15;

            var centerDist = p.dist(pt.pos.x, pt.pos.y, p.width / 2, p.height / 2);
            var maxDist = p.dist(0, 0, p.width / 2, p.height / 2);
            var normDist = centerDist / maxDist;

            var pulse = 1 + Math.sin(t * 2 + pt.phase) * 0.3 * (1 - normDist * 0.5);
            var depthPulse = p.noise(pt.pos.x * 0.01, pt.pos.y * 0.01, t * 0.5 + pt.layer * 100);
            var displaySize = pt.baseSize * pulse * (0.7 + depthPulse * 0.5);

            var lifeAlpha = 0.6 + Math.sin(t + pt.phase) * 0.4;
            var finalAlpha = pt.alpha * lifeAlpha;

            var layerHue = pt.hue + Math.sin(t * 0.5 + pt.phase) * 8;
            var layerSat = pt.sat - pt.layer * 10;
            var layerBri = pt.bri - pt.layer * 8;

            p.fill(layerHue, layerSat, layerBri, finalAlpha * 0.4);
            p.ellipse(pt.pos.x, pt.pos.y, displaySize * 1.8, displaySize * 1.8);
            p.fill(layerHue, layerSat, layerBri, finalAlpha * 0.7);
            p.ellipse(pt.pos.x, pt.pos.y, displaySize * 1.2, displaySize * 1.2);
            p.fill(layerHue + 5, layerSat - 10, layerBri + 10, finalAlpha);
            p.ellipse(pt.pos.x, pt.pos.y, displaySize, displaySize);
        }
    }

    function drawCoreGlow() {
        var cx = p.width / 2;
        var cy = p.height / 2;
        var coreSize = Math.min(p.width, p.height) * 0.6;
        var breathe = 1 + Math.sin(t * 0.8) * 0.1;

        for (var r = coreSize; r > 0; r -= coreSize / 8) {
            var alpha = (1 - r / coreSize) * 12;
            var hue = 15 + (1 - r / coreSize) * 10;
            p.fill(hue, 50, 30, alpha);
            p.ellipse(cx, cy, r * breathe, r * breathe * 0.95);
        }
    }

    p.windowResized = function() {
        var container = document.getElementById('p5-wrapper');
        p.resizeCanvas(container.offsetWidth, container.offsetHeight);
        p.background(20, 30, 8);
    };
};

// p5 init stripped

✨ AI 艺术解读

This piece simulates the intimate visual experience of peering into living organic tissue beneath the surface. The multi-layered particle system creates the illusion of depth found in biological matter, where particles at different depths contribute to a translucent, layered appearance. The flowing vein structures weave through the composition like capillaries or mycelium networks, constantly evolving through noise-driven movement. The warm color palette evokes flesh tones while the pulsing central glow suggests a living core, making the viewer feel they are witnessing something alive and breathing.

📝 补充说明

  • Layering multiple noise octaves at different scales creates more organic, unpredictable movement than single noise fields
  • Subsurface scattering effect achieved by drawing multiple semi-transparent ellipses per particle at varying sizes and alphas
  • Vein structures use curveVertex with noise-modulated offsets for smooth, organic-looking tendrils
  • The breathing core uses radial gradient-like layering achieved through concentric ellipses with decreasing alpha
  • Boundary margin forces keep particles active near edges while preventing harsh wrapping artifacts