Aegean Drift
Generated by GridFlow AI | Tags: Generative Art, Perlin Noise, Ethereal, Clouds, Atmospheric, Abstract, Dynamic, Soft Body
💡 AI 提示词
Ethereal Cloud Scape🧠 核心算法要点
- A layered cloud system is implemented, where multiple distinct groups of cloud particles are rendered. Each layer simulates depth through variations in particle size and movement speed, with foreground layers being larger and faster.
- Particle movement is driven by a 3D Perlin noise field. Each cloud particle has a unique `noiseSeed` for its starting coordinates in this field, and a global `timeOffset` increments the Z-axis of the noise, causing continuous, organic evolution of movement directions.
- Parallax effect is achieved by dynamically mapping particle properties (size, speed, vertical position range) to their layer index, creating a visual distinction between distant and closer clouds.
- Clouds are rendered as overlapping, semi-transparent ellipses, utilizing `p.blendMode(p.ADD)` to create luminous, glowing effects where colors and transparencies combine, enhancing their ethereal quality.
- Each individual cloud particle is composed of two slightly offset ellipses: a larger, subtly tinted base and a smaller, whiter core. This technique adds perceived volume and softness to each cloud element.
- The canvas is fully responsive, dynamically resizing with the browser window. All cloud parameters, including initial positions, sizes, and movement magnitudes, are scaled proportionally to the canvas dimensions.
- A subtle vertical color gradient is painted as the background, transitioning from a darker blue/purple at the top to a lighter hue towards the bottom, setting an atmospheric sky backdrop for the cloudscape.
💻 原始 p5.js 代码
var sketch = function(p) {
let cloudLayers = [];
let noiseScale = 0.003; // Overall scale for Perlin noise field, controls 'blobbiness'
let noiseEvolutionSpeed = 0.0001; // Speed of cloud shape and movement evolution
let timeOffset = 0; // Z-axis for 3D Perlin noise evolution
let numLayers = 3; // Number of distinct cloud layers for depth effect
let layerDensity = 35; // Number of particles (ellipses) per layer
let baseCloudColor;
let accentCloudColor;
let backgroundColorTop;
let backgroundColorBottom;
p.setup = function() {
let container = document.getElementById('p5-wrapper'); p.createCanvas(container.offsetWidth, container.offsetHeight).parent('p5-wrapper');
p.noStroke();
p.blendMode(p.ADD); // Crucial for the ethereal, glowing effect where transparent shapes overlap
// Define colors for the background gradient and clouds
backgroundColorTop = p.color(20, 30, 60); // Darker blue/purple for the top of the sky
backgroundColorBottom = p.color(60, 80, 120); // Lighter blue/purple for the horizon
// Base color for the main cloud body, slightly blue-tinted, very transparent
baseCloudColor = p.color(180, 200, 230, 10); // RGBA: low alpha for transparency
// Accent color for highlights/core, whiter, slightly less transparent (but still low alpha)
accentCloudColor = p.color(255, 255, 255, 8);
// Initialize cloud layers
for (let i = 0; i < numLayers; i++) {
let layer = [];
// Deeper layers (smaller 'i') are further away, so they appear smaller and move slower
let sizeMultiplier = p.map(i, 0, numLayers - 1, 0.05, 0.12); // Scales cloud particle size
let speedMultiplier = p.map(i, 0, numLayers - 1, 0.5, 1.5); // Scales cloud particle movement speed
// Adjust vertical placement for layers: background clouds higher, foreground lower
let yRangeStart = p.map(i, 0, numLayers - 1, p.height * 0.2, p.height * 0.4);
let yRangeEnd = p.map(i, 0, numLayers - 1, p.height * 0.6, p.height * 0.8);
for (let j = 0; j < layerDensity; j++) {
layer.push({
x: p.random(p.width),
y: p.random(yRangeStart, yRangeEnd),
size: p.random(p.width * sizeMultiplier * 0.03, p.width * sizeMultiplier * 0.07), // Size relative to canvas width
noiseSeed: p.createVector(p.random(1000), p.random(1000), p.random(1000)), // Unique 3D noise seed for each particle
speed: speedMultiplier
});
}
cloudLayers.push(layer);
}
};
p.draw = function() {
// Draw background gradient
p.background(0); // Clear with black first to ensure full redraw
for (let y = 0; y < p.height; y++) {
let inter = p.map(y, 0, p.height, 0, 1);
let c = p.lerpColor(backgroundColorTop, backgroundColorBottom, inter);
p.stroke(c);
p.line(0, y, p.width, y);
}
p.noStroke(); // Disable stroke for clouds to ensure soft edges
// Update global time offset for noise evolution
timeOffset += noiseEvolutionSpeed;
// Draw cloud layers from back to front (implicit through loop order if layers are ordered by 'i')
for (let i = 0; i < numLayers; i++) {
let layer = cloudLayers[i];
for (let j = 0; j < layer.length; j++) {
let particle = layer[j];
// Calculate noise-based displacement for movement from a 3D Perlin noise field
// Each particle has a unique 'noiseSeed' that acts as its starting point in the noise field.
// 'timeOffset' pushes this point along the Z-axis, causing the noise value (and thus movement)
// to evolve organically over time.
let noiseX_input = particle.noiseSeed.x + timeOffset * particle.speed * 0.5;
let noiseY_input = particle.noiseSeed.y + timeOffset * particle.speed * 0.5;
let noiseZ_input = particle.noiseSeed.z + timeOffset * particle.speed * 0.5;
// Sample 3D noise for X and Y movement directions
let noiseValX = p.noise(noiseX_input * noiseScale, noiseY_input * noiseScale, noiseZ_input * noiseScale);
let noiseValY = p.noise(noiseX_input * noiseScale + 100, noiseY_input * noiseScale + 100, noiseZ_input * noiseScale + 100); // Offset to decorrelate X and Y noise
// Map noise values (0-1) to movement direction (-1 to 1)
let dirX = p.map(noiseValX, 0, 1, -1, 1);
let dirY = p.map(noiseValY, 0, 1, -1, 1);
// Add a general rightward drift to all clouds, more pronounced for foreground layers
let globalDrift = p.map(i, 0, numLayers - 1, p.width * 0.00005, p.width * 0.0002);
// Movement magnitude scaled by canvas width and particle's layer-specific speed
let movementMagnitude = p.width * 0.0005 * particle.speed;
particle.x += dirX * movementMagnitude + globalDrift;
particle.y += dirY * movementMagnitude * 0.5; // Reduce vertical movement for a more horizontal drift
// Wrap around canvas edges to maintain continuous cloud presence
if (particle.x < -particle.size * 2) particle.x = p.width + particle.size * 2;
if (particle.x > p.width + particle.size * 2) particle.x = -particle.size * 2;
if (particle.y < -particle.size * 2) particle.y = p.height + particle.size * 2;
if (particle.y > p.height + particle.size * 2) particle.y = -particle.size * 2;
// Draw particle as a soft ellipse for the main body
p.fill(baseCloudColor);
p.ellipse(particle.x, particle.y, particle.size, particle.size * 0.75); // Slightly wider than tall
// Add a smaller, whiter core for more ethereal look, slightly offset by noise
// Use different noise samples for core offset to add subtle internal movement/texture
let coreOffsetX = p.map(p.noise(noiseX_input * noiseScale + 300), 0, 1, -particle.size * 0.1, particle.size * 0.1);
let coreOffsetY = p.map(p.noise(noiseY_input * noiseScale + 400), 0, 1, -particle.size * 0.1, particle.size * 0.1);
p.fill(accentCloudColor);
p.ellipse(particle.x + coreOffsetX, particle.y + coreOffsetY,
particle.size * 0.6, particle.size * 0.45);
}
}
};
p.windowResized = function() {
let container = document.getElementById('p5-wrapper'); p.resizeCanvas(container.offsetWidth, container.offsetHeight);
// Re-initialize cloud layers to adapt particle sizes and positions to the new canvas dimensions.
// This ensures the artwork remains aesthetically consistent and responsive.
cloudLayers = [];
for (let i = 0; i < numLayers; i++) {
let layer = [];
let sizeMultiplier = p.map(i, 0, numLayers - 1, 0.05, 0.12);
let speedMultiplier = p.map(i, 0, numLayers - 1, 0.5, 1.5);
let yRangeStart = p.map(i, 0, numLayers - 1, p.height * 0.2, p.height * 0.4);
let yRangeEnd = p.map(i, 0, numLayers - 1, p.height * 0.6, p.height * 0.8);
for (let j = 0; j < layerDensity; j++) {
layer.push({
x: p.random(p.width),
y: p.random(yRangeStart, yRangeEnd),
size: p.random(p.width * sizeMultiplier * 0.03, p.width * sizeMultiplier * 0.07),
noiseSeed: p.createVector(p.random(1000), p.random(1000), p.random(1000)),
speed: speedMultiplier
});
}
cloudLayers.push(layer);
}
};
};
new p5(sketch);
🎨 AI 艺术解读
This piece, 'Aegean Drift,' captures the serene, ever-changing beauty of an ethereal cloudscape. Layers of soft, luminous forms drift and coalesce, guided by the unseen currents of a generative noise field. The delicate interplay of transparency, blending modes, and a subtle color palette evokes a sense of peace and weightlessness, inviting contemplation of the vast, shifting expanse above. It is a visual whisper of celestial ballet, where form and movement are born from algorithmic breath, offering a momentary glimpse into an infinitely unfolding sky.
📝 补充说明
- The use of `p.blendMode(p.ADD)` is fundamental to achieving the ethereal, glowing effect. It causes overlapping colors to add their RGB values, intensifying brightness and creating luminous highlights within the clouds.
- Each cloud particle having a unique `noiseSeed` ensures that their organic movement paths are distinct and non-repeating over time, contributing to the perceived naturalness of the cloudscape.
- The `p.map()` function is extensively used to scale various properties (like particle size, speed, and vertical range) based on their layer index, which is crucial for creating a convincing sense of depth and parallax.
- Re-initializing the `cloudLayers` array within `p.windowResized` is a robust approach. It ensures that when the canvas size changes, new cloud particles are generated with sizes and positions appropriate for the new dimensions, preventing visual distortion.
- The combination of a slightly blue-tinted `baseCloudColor` and a pure white `accentCloudColor` with very low alpha values, applied to two concentric, offset ellipses, creates a volumetric and soft appearance for each cloud element.