Lorenz Phase Silk
Generated by GridFlow AI | Tags: lorenz-attractor, flow-field, ribbon-trails, phase-space, chaos-theory, monochromatic, generative-art
💡 AI 提示词
Lorenz Attractor Phase Space Trajectories rendered as flowing silk ribbons in a monochromatic blue palette with multi-layer compositing and glow effects🔧 核心算法要点
- Lorenz system integration using Runge-Kutta-style Euler steps with parameters sigma=10, rho=28, beta=8/3
- Multiple offscreen p.createGraphics() buffers for layered compositing with different blend modes
- curveVertex() ribbon trails with variable stroke weight and alpha gradients for organic flowing forms
- Per-pixel background atmosphere using loadPixels()/updatePixels() with layered noise functions
- Mouse-based flow field distortion and attraction point influencing trajectory paths
- Keyboard and mouse interactions for spawning new trajectories and cycling blend modes
🎨 原始代码
var sketch = function(p) {
var bgBuffer, trailBuffer, glowBuffer, particleBuffer;
var trajectories = [];
var numTrajectories = 12;
var baseHue = 220;
var blendModeIndex = 0;
var blendModes = [p.BLEND, p.ADD, p.SCREEN, p.OVERLAY];
var showParticles = true;
var showGlow = true;
var sigma = 10, rho = 28, beta = 8/3;
var dt = 0.005;
var noiseScale = 0.003;
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.frameRate(60);
p.background(0, 0, 5);
bgBuffer = p.createGraphics(p.width, p.height);
trailBuffer = p.createGraphics(p.width, p.height);
glowBuffer = p.createGraphics(p.width, p.height);
particleBuffer = p.createGraphics(p.width, p.height);
initBuffers();
initTrajectories();
};
function initBuffers() {
[bgBuffer, trailBuffer, glowBuffer, particleBuffer].forEach(function(buf) {
buf.colorMode(p.HSB, 360, 100, 100, 100);
buf.pixelDensity(1);
});
}
function initTrajectories() {
trajectories = [];
for (var i = 0; i < numTrajectories; i++) {
var startX = p.random(-20, 20);
var startY = p.random(-20, 20);
var startZ = p.random(10, 50);
trajectories.push(createTrajectory(startX, startY, startZ, i));
}
}
function createTrajectory(sx, sy, sz, idx) {
var points = [];
var x = sx, y = sy, z = sz;
var maxPoints = p.floor(p.random(400, 800));
var hueOffset = p.map(idx, 0, numTrajectories, 0, 60);
var brightnessBase = p.random(50, 90);
var saturationBase = p.random(60, 95);
for (var i = 0; i < maxPoints; i++) {
var dx = sigma * (y - x);
var dy = x * (rho - z) - y;
var dz = x * y - beta * z;
x += dx * dt;
y += dy * dt;
z += dz * dt;
var mappedX = p.map(x, -25, 25, 0, p.width);
var mappedY = p.map(y, -25, 25, 0, p.height);
var mappedZ = p.map(z, 0, 60, 0, 1);
var n = p.noise(mappedX * noiseScale, mappedY * noiseScale, p.frameCount * 0.001);
var mouseInfluence = getMouseInfluence(mappedX, mappedY);
points.push({
x: mappedX + mouseInfluence.x + (n - 0.5) * 15,
y: mappedY + mouseInfluence.y + (n - 0.5) * 15,
z: mappedZ,
hue: baseHue + hueOffset,
brightness: brightnessBase * mappedZ,
saturation: saturationBase * (0.5 + 0.5 * mappedZ),
age: i,
maxAge: maxPoints
});
}
return { points: points, index: idx, hueOffset: hueOffset };
}
function getMouseInfluence(px, py) {
var influence = { x: 0, y: 0 };
var mx = p.mouseX, my = p.mouseY;
if (mx > 0 && mx < p.width && my > 0 && my < p.height) {
var dx = px - mx;
var dy = py - my;
var dist = p.sqrt(dx * dx + dy * dy);
var maxDist = p.min(p.width, p.height) * 0.4;
if (dist < maxDist) {
var strength = p.map(dist, 0, maxDist, 50, 0);
var angle = p.atan2(dy, dx) + p.PI * 0.5;
influence.x = p.cos(angle) * strength;
influence.y = p.sin(angle) * strength;
}
}
return influence;
}
function drawBackground() {
bgBuffer.background(0, 0, 5, 15);
bgBuffer.loadPixels();
var step = 3;
for (var x = 0; x < p.width; x += step) {
for (var y = 0; y < p.height; y += step) {
var n1 = p.noise(x * 0.002, y * 0.002, p.frameCount * 0.0005);
var n2 = p.noise(x * 0.005 + 100, y * 0.005 + 100, p.frameCount * 0.0008);
var val = p.map(n1 * n2, 0, 1, 0, 25);
for (var dx = 0; dx < step; dx++) {
for (var dy = 0; dy < step; dy++) {
var idx = 4 * ((y + dy) * p.width + (x + dx));
bgBuffer.pixels[idx] = baseHue * 0.6;
bgBuffer.pixels[idx + 1] = val;
bgBuffer.pixels[idx + 2] = 15 + val * 0.5;
bgBuffer.pixels[idx + 3] = val * 4;
}
}
}
}
bgBuffer.updatePixels();
}
function drawRibbonTrails() {
trailBuffer.clear();
glowBuffer.clear();
trajectories.forEach(function(traj) {
var ribbonWidth = p.map(traj.index, 0, numTrajectories, 1, 4);
glowBuffer.strokeWeight(ribbonWidth * 6);
glowBuffer.noFill();
glowBuffer.beginShape();
traj.points.forEach(function(pt, i) {
if (i > 1) {
glowBuffer.stroke(baseHue + traj.hueOffset, 40, 80, p.map(i, 0, pt.maxAge, 5, 30));
glowBuffer.curveVertex(pt.x, pt.y);
}
});
glowBuffer.endShape();
trailBuffer.strokeWeight(ribbonWidth);
trailBuffer.noFill();
trailBuffer.beginShape();
traj.points.forEach(function(pt, i) {
if (i > 1) {
var alpha = p.map(i, 0, pt.maxAge, 0, 60);
trailBuffer.stroke(pt.hue, pt.saturation, pt.brightness, alpha);
trailBuffer.curveVertex(pt.x, pt.y);
}
});
trailBuffer.endShape();
var thinRibbon = p.random() > 0.7;
if (thinRibbon) {
trailBuffer.strokeWeight(ribbonWidth * 0.3);
trailBuffer.beginShape();
traj.points.forEach(function(pt, i) {
if (i > 1) {
var alpha = p.map(i, 0, pt.maxAge, 0, 40);
trailBuffer.stroke(baseHue + 20, 20, 100, alpha);
trailBuffer.curveVertex(pt.x, pt.y);
}
});
trailBuffer.endShape();
}
});
}
function drawFlowParticles() {
particleBuffer.clear();
if (!showParticles) return;
for (var i = 0; i < 80; i++) {
var px = p.random(p.width);
var py = p.random(p.height);
var noiseVal = p.noise(px * 0.003, py * 0.003, p.frameCount * 0.002);
var angle = noiseVal * p.TWO_PI * 2;
var speed = p.map(noiseVal, 0, 1, 0.5, 2);
px += p.cos(angle) * speed;
py += p.sin(angle) * speed;
if (px < 0) px = p.width;
if (px > p.width) px = 0;
if (py < 0) py = p.height;
if (py > p.height) py = 0;
var life = p.random(50, 150);
particleBuffer.noStroke();
particleBuffer.fill(baseHue + p.random(-10, 10), 30, 100, 20);
particleBuffer.ellipse(px, py, p.random(1, 3), p.random(1, 3));
}
}
p.draw = function() {
p.background(0, 0, 3);
drawBackground();
drawRibbonTrails();
drawFlowParticles();
p.image(bgBuffer, 0, 0);
if (showGlow) {
glowBuffer.blendMode(p.ADD);
p.image(glowBuffer, 0, 0);
}
trailBuffer.blendMode(blendModes[blendModeIndex]);
p.image(trailBuffer, 0, 0);
particleBuffer.blendMode(p.ADD);
p.image(particleBuffer, 0, 0);
drawInteractionOverlay();
};
function drawInteractionOverlay() {
if (p.mouseX > 0 && p.mouseX < p.width && p.mouseY > 0 && p.mouseY < p.height) {
p.push();
p.blendMode(p.ADD);
p.noFill();
p.stroke(baseHue, 20, 100, 30);
p.strokeWeight(1);
var radius = 80;
for (var r = radius; r > 10; r -= 15) {
p.ellipse(p.mouseX, p.mouseY, r, r);
}
p.blendMode(p.BLEND);
p.fill(baseHue, 30, 100, 50);
p.noStroke();
p.ellipse(p.mouseX, p.mouseY, 6, 6);
p.pop();
}
}
p.mousePressed = function() {
if (p.mouseX > 0 && p.mouseX < p.width && p.mouseY > 0 && p.mouseY < p.height) {
for (var i = 0; i < 3; i++) {
var sx = p.map(p.mouseX, 0, p.width, -25, 25) + p.random(-5, 5);
var sy = p.map(p.mouseY, 0, p.height, -25, 25) + p.random(-5, 5);
var sz = p.random(15, 45);
trajectories.push(createTrajectory(sx, sy, sz, trajectories.length));
}
while (trajectories.length > numTrajectories + 6) {
trajectories.shift();
}
trailBuffer.push();
trailBuffer.blendMode(p.ADD);
trailBuffer.noStroke();
for (var j = 0; j < 20; j++) {
var angle = p.random(p.TWO_PI);
var dist = p.random(50, 150);
var bx = p.mouseX + p.cos(angle) * dist;
var by = p.mouseY + p.sin(angle) * dist;
var size = p.random(10, 40);
trailBuffer.fill(baseHue + p.random(-15, 15), 50, 100, 15);
trailBuffer.ellipse(bx, by, size, size);
}
trailBuffer.pop();
}
};
p.keyPressed = function() {
if (p.key === 'b' || p.key === 'B') {
blendModeIndex = (blendModeIndex + 1) % blendModes.length;
} else if (p.key === 'r' || p.key === 'R') {
initTrajectories();
} else if (p.key === 'g' || p.key === 'G') {
showGlow = !showGlow;
} else if (p.key === 'p' || p.key === 'P') {
showParticles = !showParticles;
} else if (p.key === 's' || p.key === 'S') {
baseHue = (baseHue + 30) % 360;
}
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
var oldWidth = bgBuffer.width;
var oldHeight = bgBuffer.height;
bgBuffer.resizeCanvas(p.width, p.height);
trailBuffer.resizeCanvas(p.width, p.height);
glowBuffer.resizeCanvas(p.width, p.height);
particleBuffer.resizeCanvas(p.width, p.height);
initTrajectories();
};
}; // p5 init stripped
✨ AI 艺术解读
This piece transforms the mathematical chaos of the Lorenz attractor into ethereal silk ribbons flowing through virtual phase space. The monochromatic deep blue palette evokes the cold beauty of mathematical precision, while the ribbon trails reveal the deterministic yet unpredictable nature of chaotic systems. Each trajectory begins its journey close to others but diverges into unique paths—a visual metaphor for sensitivity to initial conditions. The glowing additive blending creates an otherworldly luminescence, as if capturing light itself tracing the invisible pathways of mathematical chaos.
📝 补充说明
- Using curveVertex() instead of line() or point() creates inherently smooth, organic ribbon forms that follow the mathematical trajectories
- Multi-buffer compositing with blend modes (ADD, SCREEN, OVERLAY) achieves glow effects impossible with single-canvas rendering
- Pre-computing Lorenz attractor points in setup() and storing them in trajectory objects avoids expensive integration during draw(), maintaining 60fps
- Pixel-level rendering at step=3 with nested loops provides atmospheric depth without severe performance penalty
- The monochromatic palette (baseHue=220) with brightness/saturation variations creates visual sophistication through restraint rather than color variety