๐ŸŽ‡ Bird flocks at dusk

BamgasiJMยท2026๋…„ 1์›” 31์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
60/65
post-thumbnail

๐Ÿ“ p5.js

// w03_example_05.js

let baseColorA;
let baseColorB;
let currentColorA;
let currentColorB;

let particles = [];
const PARTICLE_COUNT = 8000;

function setup() {
  createCanvas(800, 800);
  colorMode(RGB, 255);
  noStroke();

  // ๊ทธ๋ผ๋ฐ์ด์…˜ ์ดˆ๊ธฐ ์ƒ‰
  baseColorA = color(40, 60, 90);
  baseColorB = color(220, 200, 180);

  currentColorA = baseColorA;
  currentColorB = baseColorB;

  // ํŒŒํ‹ฐํด ์ƒ์„ฑ
  for (let i = 0; i < PARTICLE_COUNT; i++) {
    particles.push(new BirdParticle());
  }
}

function draw() {
  drawGradientBackground();

  for (let p of particles) {
    p.update();
    p.display();
  }
}

/* ------------------------------
   Gradient Background
-------------------------------- */

function drawGradientBackground() {
  let t = frameCount * 0.005;

  let targetColorA = color(
    40 + noise(t) * 80,
    60 + noise(t + 10) * 80,
    90 + noise(t + 20) * 80
  );

  let targetColorB = color(
    180 + noise(t + 30) * 60,
    170 + noise(t + 40) * 60,
    160 + noise(t + 50) * 60
  );

  currentColorA = lerpColor(currentColorA, targetColorA, 0.01);
  currentColorB = lerpColor(currentColorB, targetColorB, 0.01);

  for (let y = 0; y < height; y++) {
    let distanceFactor = dist(mouseX, mouseY, width / 2, y) / width;

    let verticalFactor = map(y, 0, height, 0, 1);
    let blendFactor = lerp(verticalFactor, distanceFactor, 0.3);

    let c = lerpColor(
      currentColorA,
      currentColorB,
      constrain(blendFactor, 0, 1)
    );

    fill(c);
    rect(0, y, width, 1);
  }
}

/* ------------------------------
   Bird-like Particles
-------------------------------- */

class BirdParticle {
  constructor() {
    this.position = createVector(random(width), random(height));

    this.velocity = p5.Vector.random2D();
    this.velocity.mult(random(0.3, 0.8));

    this.noiseOffset = random(1000);
    this.size = random(0.5, 3);
  }

  update() {
    let angle =
      noise(
        this.position.x * 0.02,
        this.position.y * 0.02,
        frameCount * 3.02 + this.noiseOffset
      ) *
      TWO_PI *
      2;

    let flowVector = p5.Vector.fromAngle(angle);
    flowVector.mult(0.05);

    this.velocity.add(flowVector);
    this.velocity.limit(1);

    this.position.add(this.velocity);
    this.wrapEdges();
  }

  wrapEdges() {
    if (this.position.x < 0) this.position.x = width;
    if (this.position.x > width) this.position.x = 0;
    if (this.position.y < 0) this.position.y = height;
    if (this.position.y > height) this.position.y = 0;
  }

  display() {
    fill(0, 140);
    ellipse(this.position.x, this.position.y, this.size, this.size);
  }
}

์ด ์ฝ”๋“œ๋Š” ๋™์  ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ๊ณผ ์กฐ๋ฅ˜์ฒ˜๋Ÿผ ์›€์ง์ด๋Š” ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ผ๋ฐ์ด์…˜ ์ƒ‰์ƒ ์ƒ์„ฑ๊ณผ ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ํ™œ์šฉํ•œ ์ƒ‰ ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ๋ถ„์„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ๋™์  ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ:
    • ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ฒœ์ฒœํžˆ ๋ณ€ํ•˜๋Š” ์ƒ‰์ƒ
    • ๋งˆ์šฐ์Šค ์œ„์น˜์— ๋ฐ˜์‘ํ•˜๋Š” ๊ทธ๋ผ๋ฐ์ด์…˜ ํŒจํ„ด
  2. ์กฐ๋ฅ˜์ฒ˜๋Ÿผ ์›€์ง์ด๋Š” ํŒŒํ‹ฐํด:
    • ๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜์˜ ์œ ์ฒด์  ์›€์ง์ž„
    • 8000๊ฐœ์˜ ํŒŒํ‹ฐํด์ด ํ•จ๊ป˜ ์›€์ง์ด๋ฉฐ ๋งˆ์น˜ ์ƒˆ๋–ผ ๊ฐ™์€ ํšจ๊ณผ
      3 ์ƒํ˜ธ์ž‘์šฉ:
    • ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ด๋ฉด ๊ทธ๋ผ๋ฐ์ด์…˜์ด ๋ณ€ํ™”ํ•˜์—ฌ ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅ

๊ทธ๋ผ๋ฐ์ด์…˜ ์ƒ‰์ƒ ์ƒ์„ฑ ๊ณผ์ •

1. ์ดˆ๊ธฐ ์ƒ‰์ƒ ์„ค์ •

// ๊ทธ๋ผ๋ฐ์ด์…˜ ์ดˆ๊ธฐ ์ƒ‰
baseColorA = color(40, 60, 90);  // ์ง™์€ ํŒŒ๋ž€์ƒ‰ ๊ณ„์—ด
baseColorB = color(220, 200, 180);  // ์˜…์€ ๋ฒ ์ด์ง€/๋…ธ๋ž€์ƒ‰ ๊ณ„์—ด

2. ๋™์  ์ƒ‰์ƒ ๋ณ€ํ™”

let t = frameCount * 0.005;

let targetColorA = color(
  40 + noise(t) * 80,
  60 + noise(t + 10) * 80,
  90 + noise(t + 20) * 80
);

let targetColorB = color(
  180 + noise(t + 30) * 60,
  170 + noise(t + 40) * 60,
  160 + noise(t + 50) * 60
);

currentColorA = lerpColor(currentColorA, targetColorA, 0.01);
currentColorB = lerpColor(currentColorB, targetColorB, 0.01);
  • noise() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ƒ‰์ƒ์ด ๋ณ€ํ•˜๋„๋ก ์„ค์ •
  • ๊ฐ ์ƒ‰์ƒ ์ฑ„๋„(R, G, B)์— ๋‹ค๋ฅธ ์˜คํ”„์…‹(noise(t +10), noise(t + 20)๋“ฑ)์„ ์ ์šฉ
  • lerpColor() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ ํ˜„์žฌ ์ƒ‰์ƒ๊ณผ ๋ชฉํ‘œ ์ƒ‰์ƒ์„ 0.01์˜ ๋น„์œจ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณด๊ฐ„

3. ์ˆ˜์ง ๊ทธ๋ผ๋ฐ์ด์…˜ ๊ตฌํ˜„

for (let y = 0; y < height; y++) {
  let verticalFactor = map(y, 0, height, 0, 1);
  let c = lerpColor(currentColorA, currentColorB, verticalFactor);
  fill(c);
  rect(0, y, width, 1);
}
  • y์ขŒํ‘œ์— ๋”ฐ๋ผ ์ƒ‰์ƒ์„ ํ˜ผํ•ฉํ•˜์—ฌ ์ˆ˜์ง ๊ทธ๋ผ๋ฐ์ด์…˜ ์ƒ์„ฑ
  • map()ํ•จ์ˆ˜๋กœ y์ขŒํ‘œ๋ฅผ 0~1 ๋ฒ”์œ„๋กœ ๋ณ€ํ™˜
  • lerpColor()๋กœ ๋‘ ์ƒ‰์ƒ์„ ํ˜ผํ•ฉ

๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ํ™œ์šฉํ•œ ์ƒ‰ ๋ณ€๊ฒฝ

1. ๋งˆ์šฐ์Šค ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ์ƒ‰์ƒ ํ˜ผํ•ฉ

let distanceFactor = dist(mouseX, mouseY, width / 2, y) / width;
  • dist() ํ•จ์ˆ˜๋กœ ๋งˆ์šฐ์Šค ์œ„์น˜์™€ ํ™”๋ฉด ์ค‘์•™ x์ถ•์˜ ํŠน์ • y ์ง€์  ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
  • / width๋กœ ์ •๊ทœํ™”ํ•˜์—ฌ 0~1 ๋ฒ”์œ„๋กœ ๋ณ€ํ™˜

2. ์ˆ˜์ง ์œ„์น˜์™€ ๋งˆ์šฐ์Šค ๊ฑฐ๋ฆฌ ํ˜ผํ•ฉ

let blendFactor = lerp(verticalFactor, distanceFactor, 0.3);
  • ์ผ๋ฐ˜์ ์ธ ์ˆ˜์ง ๊ทธ๋ผ๋ฐ์ด์…˜(verticalFactor)๊ณผ ๋งˆ์šฐ์Šค ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ทธ๋ผ๋ฐ์ด์…˜(distanceFactor)์„ 30% ๋น„์œจ๋กœ ํ˜ผํ•ฉ
  • ์ด๋กœ ์ธํ•ด ๊ทธ๋ผ๋ฐ์ด์…˜์ด ์ˆ˜์ง ๋ฐฉํ–ฅ ์™ธ์— ๋งˆ์šฐ์Šค ์œ„์น˜์—๋„ ๋ฐ˜์‘

3. ์ตœ์ข… ์ƒ‰์ƒ ์ ์šฉ

let c = lerpColor(
  currentColorA,
  currentColorB,
  constrain(blendFactor, 0, 1)
);
  • ํ˜ผํ•ฉ ์š”์†Œ๋ฅผ 0~1 ๋ฒ”์œ„๋กœ ์ œํ•œ
  • ๋‘ ์ƒ‰์ƒ์„ ํ˜ผํ•ฉํ•˜์—ฌ ์ตœ์ข… ์ƒ‰์ƒ ์ƒ์„ฑ
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€