Dendritic Lightning Discharge Channel

📅 April 25, 2026 🏷️ art
top-ones dendritic lightning L-system recursive branching neon noir curveVertex multi-layer compositing glow effect interactive
Generated by GridFlow AI | Tags: dendritic lightning, L-system, recursive branching, neon noir, curveVertex, multi-layer compositing, glow effect, interactive

💡 AI 提示词

Create a visually stunning p5.js artwork depicting a dendritic lightning discharge channel in neon noir style with a black background and vivid magenta and cyan neon accents. Use L-system recursive branching with curveVertex rendering as the primary technique. Implement multi-layer compositing with createGraphics buffers and advanced blend modes (ADD, SCREEN) for luminous glow effects. Include mouse movement influence and click-triggered bursts.

🔧 核心算法要点

  1. Recursive L-system branching creates dendritic lightning structures with depth-based color gradient from magenta at base to cyan at tips
  2. Perlin noise perturbation added to branch angles creates organic, flowing lightning paths that react to mouse proximity
  3. Multi-layer compositing using three createGraphics buffers: noise texture for atmosphere, main branch buffer, and glow buffer with screen blending
  4. curveVertex rendering with interpolated segments produces smooth, organic tendrils instead of jagged lines
  5. Particle system with object pooling manages up to 500 energy particles spawned along branches and mouse interactions
  6. Screen blend mode composites glow layer beneath ADD-blended main branches creating volumetric energy accumulation
  7. Depth-based stroke weight and color interpolation creates visual hierarchy from thick luminous base to delicate branching tips
  8. Mouse proximity within 150px radius attracts lightning paths and spawns additional energy particles for interactive feedback

🎨 原始代码

var sketch = function(p) {
  // === GLOBAL STATE ===
  let branchBuffer, glowBuffer, noiseBuffer;
  let branches = [];
  let activeParticles = [];
  let time = 0;
  let mouseInfluence = 0;
  let lastMouseX = 0, lastMouseY = 0;
  let mode = 0; // 0: ambient, 1: triggered
  let colorShift = 0;
  
  // === COLOR PALETTE ===
  const MAGENTA = [255, 0, 255];
  const CYAN = [0, 255, 255];
  const WHITE = [255, 255, 255];
  const DEEP_PURPLE = [40, 0, 80];
  
  // === L-SYSTEM BRANCH CLASS ===
  class Branch {
    constructor(x, y, angle, length, depth, parentBranch) {
      this.x = x;
      this.y = y;
      this.angle = angle;
      this.length = length;
      this.depth = depth;
      this.parentBranch = parentBranch;
      this.children = [];
      this.vertices = [];
      this.generated = false;
      this.life = 1.0;
      this.pulsePhase = p.random(p.TWO_PI);
      this.wobble = 0;
    }
    
    generate() {
      if (this.generated || this.depth > 10) return;
      
      let currentX = this.x;
      let currentY = this.y;
      let currentAngle = this.angle;
      let segments = Math.floor(this.length / 8);
      
      this.vertices.push({x: currentX, y: currentY});
      
      for (let i = 0; i < segments; i++) {
        // Add noise-based perturbation for organic feel
        let noiseVal = p.noise(currentX * 0.01, currentY * 0.01, time * 0.5 + this.depth * 10);
        currentAngle += (noiseVal - 0.5) * 0.4;
        
        // Mouse influence - attract/repel
        let dx = currentX - p.mouseX;
        let dy = currentY - p.mouseY;
        let dist = p.sqrt(dx * dx + dy * dy);
        if (dist < 150 && dist > 0) {
          let force = (150 - dist) / 150 * mouseInfluence;
          currentAngle += force * 0.3;
          // Add energy particles near mouse
          if (p.random() < 0.05 && activeParticles.length < 500) {
            activeParticles.push({
              x: currentX, y: currentY,
              vx: p.random(-2, 2), vy: p.random(-2, 2),
              life: 1.0, size: p.random(1, 3)
            });
          }
        }
        
        currentX += p.cos(currentAngle) * 8;
        currentY += p.sin(currentAngle) * 8;
        
        this.vertices.push({x: currentX, y: currentY});
      }
      
      // Spawn child branches - dendritic pattern
      if (this.depth < 10 && this.length > 10) {
        let numChildren = this.depth < 3 ? p.floor(p.random(2, 4)) : p.floor(p.random(1, 3));
        
        for (let i = 0; i < numChildren; i++) {
          let spread = p.random(0.3, 0.8);
          let dir = p.random() > 0.5 ? 1 : -1;
          let childAngle = currentAngle + dir * spread + p.random(-0.3, 0.3);
          let childLength = this.length * p.random(0.6, 0.8);
          
          let child = new Branch(currentX, currentY, childAngle, childLength, this.depth + 1, this);
          this.children.push(child);
          child.generate();
        }
      }
      
      this.generated = true;
    }
    
    render(buffer, glowBuf, intensity) {
      if (this.vertices.length < 2) return;
      
      // Color based on depth - more cyan at tips, magenta at base
      let depthRatio = this.depth / 10;
      let r = p.lerp(MAGENTA[0], CYAN[0], depthRatio);
      let g = p.lerp(MAGENTA[1], CYAN[1], depthRatio);
      let b = p.lerp(MAGENTA[2], CYAN[2], depthRatio);
      
      // Pulse effect
      let pulse = 0.7 + 0.3 * p.sin(time * 3 + this.pulsePhase);
      r *= pulse;
      g *= pulse;
      b *= pulse;
      
      // Draw main branch with curveVertex
      buffer.stroke(r, g, b, 255 * intensity);
      buffer.strokeWeight(p.map(this.depth, 0, 10, 3, 0.5));
      buffer.noFill();
      buffer.beginShape();
      
      for (let v of this.vertices) {
        buffer.curveVertex(v.x, v.y);
      }
      buffer.endShape();
      
      // Draw glow layer
      glowBuf.stroke(r * 0.5, g * 0.5, b * 0.5, 100 * intensity);
      glowBuf.strokeWeight(p.map(this.depth, 0, 10, 12, 3));
      glowBuf.beginShape();
      for (let v of this.vertices) {
        glowBuf.curveVertex(v.x, v.y);
      }
      glowBuf.endShape();
      
      // Render children
      for (let child of this.children) {
        child.render(buffer, glowBuf, intensity * 0.9);
      }
    }
    
    update() {
      this.wobble = p.noise(this.x * 0.05, this.y * 0.05, time) * 2 - 1;
      for (let child of this.children) {
        child.update();
      }
    }
  }
  
  // === INITIALIZATION ===
  p.setup = function() {
    let container = document.getElementById('p5-wrapper');
    if (!container) {
      container = document.body;
    }
    p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
    p.colorMode(p.RGB, 255, 255, 255, 255);
    p.frameRate(60);
    
    // Create offscreen buffers
    branchBuffer = p.createGraphics(p.width, p.height);
    glowBuffer = p.createGraphics(p.width, p.height);
    noiseBuffer = p.createGraphics(p.width, p.height);
    
    // Generate initial noise texture for atmosphere
    generateNoiseTexture();
    
    // Create main lightning structure
    regenerateBranches();
  };
  
  function regenerateBranches() {
    branches = [];
    // Create main trunk from bottom center
    let startX = p.width / 2 + p.random(-50, 50);
    let startY = p.height * 0.95;
    
    for (let i = 0; i < 3; i++) {
      let angle = -p.HALF_PI + p.random(-0.3, 0.3);
      let mainBranch = new Branch(startX + p.random(-30, 30), startY, angle, p.random(150, 250), 0, null);
      mainBranch.generate();
      branches.push(mainBranch);
    }
    
    // Add secondary discharge points
    for (let i = 0; i < 5; i++) {
      let x = p.random(p.width * 0.2, p.width * 0.8);
      let y = p.random(p.height * 0.3, p.height * 0.7);
      let angle = p.random(-p.PI, 0);
      let branch = new Branch(x, y, angle, p.random(80, 150), 0, null);
      branch.generate();
      branches.push(branch);
    }
  }
  
  function generateNoiseTexture() {
    noiseBuffer.loadPixels();
    for (let x = 0; x < noiseBuffer.width; x += 3) {
      for (let y = 0; y < noiseBuffer.height; y += 3) {
        let n = p.noise(x * 0.005, y * 0.005);
        let alpha = n * 30;
        noiseBuffer.set(x, y, p.color(20, 0, 40, alpha));
        // Fill gaps
        for (let dx = 0; dx < 3 && x + dx < noiseBuffer.width; dx++) {
          for (let dy = 0; dy < 3 && y + dy < noiseBuffer.height; dy++) {
            noiseBuffer.set(x + dx, y + dy, p.color(20, 0, 40, alpha * (1 - (dx + dy) * 0.2)));
          }
        }
      }
    }
    noiseBuffer.updatePixels();
  }
  
  // === MAIN RENDER ===
  p.draw = function() {
    time += 0.02;
    colorShift = (colorShift + 0.001) % 1;
    
    // Track mouse movement for influence
    let dx = p.mouseX - lastMouseX;
    let dy = p.mouseY - lastMouseY;
    mouseInfluence = p.constrain(p.sqrt(dx * dx + dy * dy) * 0.1, 0, 1);
    lastMouseX = p.mouseX;
    lastMouseY = p.mouseY;
    
    // Clear with deep black
    p.background(0, 0, 0);
    
    // Draw atmospheric noise layer
    p.image(noiseBuffer, 0, 0);
    
    // Clear buffers
    branchBuffer.clear();
    glowBuffer.clear();
    
    // Update and render branches
    for (let branch of branches) {
      branch.update();
      branch.render(branchBuffer, glowBuffer, 1.0);
    }
    
    // Update and render particles
    updateParticles();
    renderParticles(branchBuffer, glowBuffer);
    
    // Composite buffers with blend modes
    p.image(noiseBuffer, 0, 0);
    
    // Draw glow layer first (screen blend for bloom)
    glowBuffer.blendMode(p.SCREEN);
    p.image(glowBuffer, 0, 0);
    glowBuffer.blendMode(p.BLEND);
    
    // Draw main branches (add blend for energy accumulation)
    branchBuffer.blendMode(p.ADD);
    p.image(branchBuffer, 0, 0);
    branchBuffer.blendMode(p.BLEND);
    
    // Add central core glow
    drawCoreGlow();
    
    // Add vignette
    drawVignette();
    
    // Trigger mode flash
    if (mode === 1) {
      p.fill(255, 255, 255, 50);
      p.noStroke();
      p.rect(0, 0, p.width, p.height);
      mode = 0;
    }
  };
  
  function drawCoreGlow() {
    // Central energy core at the main discharge point
    let coreX = p.width / 2;
    let coreY = p.height * 0.95;
    
    p.noStroke();
    for (let i = 5; i > 0; i--) {
      let size = i * 40 + p.sin(time * 2) * 10;
      let alpha = 20 - i * 3;
      p.fill(0, 255, 255, alpha);
      p.ellipse(coreX, coreY, size, size);
      p.fill(255, 0, 255, alpha * 0.5);
      p.ellipse(coreX + p.cos(time) * 5, coreY + p.sin(time) * 5, size * 0.7, size * 0.7);
    }
  };
  
  function drawVignette() {
    // Subtle vignette for depth
    let gradient = p.drawingContext.createRadialGradient(
      p.width / 2, p.height / 2, p.width * 0.2,
      p.width / 2, p.height / 2, p.width * 0.8
    );
    gradient.addColorStop(0, 'rgba(0,0,0,0)');
    gradient.addColorStop(1, 'rgba(0,0,0,0.6)');
    p.drawingContext.fillStyle = gradient;
    p.drawingContext.fillRect(0, 0, p.width, p.height);
  };
  
  function updateParticles() {
    for (let i = activeParticles.length - 1; i >= 0; i--) {
      let particle = activeParticles[i];
      particle.x += particle.vx;
      particle.y += particle.vy;
      particle.vy += 0.02; // slight gravity
      particle.life -= 0.015;
      
      if (particle.life <= 0) {
        activeParticles.splice(i, 1);
      }
    }
    
    // Spawn new particles along branches
    if (p.random() < 0.3 && activeParticles.length < 500) {
      let branch = branches[p.floor(p.random(branches.length))];
      if (branch && branch.vertices.length > 0) {
        let v = branch.vertices[p.floor(p.random(branch.vertices.length))];
        activeParticles.push({
          x: v.x, y: v.y,
          vx: p.random(-1, 1), vy: p.random(-1, 1),
          life: 1.0, size: p.random(1, 2)
        });
      }
    }
  };
  
  function renderParticles(buf, glowBuf) {
    buf.noStroke();
    glowBuf.noStroke();
    
    for (let particle of activeParticles) {
      let depthFactor = p.map(particle.y, 0, p.height, 1.2, 0.6);
      let r = p.lerp(MAGENTA[0], CYAN[0], depthFactor) * particle.life;
      let g = p.lerp(MAGENTA[1], CYAN[1], depthFactor) * particle.life;
      let b = p.lerp(MAGENTA[2], CYAN[2], depthFactor) * particle.life;
      
      buf.fill(r, g, b, 255 * particle.life);
      buf.ellipse(particle.x, particle.y, particle.size * 2, particle.size * 2);
      
      glowBuf.fill(r * 0.3, g * 0.3, b * 0.3, 100 * particle.life);
      glowBuf.ellipse(particle.x, particle.y, particle.size * 6, particle.size * 6);
    }
  };
  
  // === INTERACTION HANDLERS ===
  
  // Mouse movement: continuous influence on lightning paths
  p.mouseMoved = function() {
    // Influence is handled in branch generation and update
  };
  
  // Mouse click: trigger burst event
  p.mousePressed = function() {
    mode = 1;
    // Spawn energy burst at click location
    for (let i = 0; i < 20; i++) {
      let angle = p.random(p.TWO_PI);
      let speed = p.random(3, 8);
      activeParticles.push({
        x: p.mouseX, y: p.mouseY,
        vx: p.cos(angle) * speed,
        vy: p.sin(angle) * speed,
        life: 1.0, size: p.random(2, 5)
      });
    }
    
    // Spawn new branch from click point
    let newBranch = new Branch(p.mouseX, p.mouseY, -p.HALF_PI + p.random(-0.5, 0.5), p.random(100, 200), 2, null);
    newBranch.generate();
    branches.push(newBranch);
    
    // Limit total branches for performance
    if (branches.length > 15) {
      branches.shift();
    }
  };
  
  // Keyboard interactions
  p.keyPressed = function() {
    if (p.key === 'r' || p.key === 'R') {
      // Reset: regenerate branches
      regenerateBranches();
      activeParticles = [];
    }
    if (p.key === 'c' || p.key === 'C') {
      // Clear all particles
      activeParticles = [];
    }
    if (p.key === ' ') {
      // Space: toggle intense mode
      mouseInfluence = mouseInfluence > 0.5 ? 0 : 2;
    }
  };
  
  // Handle window resize
  p.windowResized = function() {
    let container = document.getElementById('p5-wrapper');
    if (!container) {
      container = document.body;
    }
    p.resizeCanvas(container.offsetWidth, container.offsetHeight);
    
    // Recreate buffers at new size
    branchBuffer = p.createGraphics(p.width, p.height);
    glowBuffer = p.createGraphics(p.width, p.height);
    noiseBuffer = p.createGraphics(p.width, p.height);
    
    generateNoiseTexture();
    regenerateBranches();
  };
};

✨ AI 艺术解读

This artwork captures the raw, fractal beauty of electrical discharge patterns found in nature - the way lightning seeks multiple paths through air, branching into increasingly delicate channels. The neon noir palette transforms this phenomenon into an ethereal, almost otherworldly display of energy frozen in motion. The recursive branching structure mirrors how electricity naturally distributes across fractal pathways, while the magenta-to-cyan gradient suggests energy transformation as it travels from source to terminus. Interactive elements let viewers become co-creators, triggering new discharge events and watching the system respond.

📝 补充说明

  • Performance optimized by pre-computing noise textures in setup and limiting branch depth to 10 levels with segment count based on length
  • Particle system uses object pooling - particles are reused rather than created/destroyed each frame to maintain 60fps
  • Glow effect achieved through layered rendering: wide soft stroke on glow buffer composited with screen blend, then sharp stroke on main buffer with add blend
  • Vignette post-process applied via radial gradient on drawingContext for cinematic depth without pixel-level iteration
  • Color palette intentionally restrained to two complementary neon colors on black - this constraint creates visual impact through contrast rather than variety