반응-확산 패턴

Reaction–diffusion

두 화학 물질이 서로 반응하며 퍼져나갈 때 저절로 생기는 얼룩·줄무늬 패턴. 앨런 튜링이 1952년 생물의 무늬(표범 반점, 얼룩말 줄무늬)를 설명하려고 제안했습니다.

다른 이름: Gray–Scott modelTuring pattern
···
js
const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const cs = getComputedStyle(document.documentElement);
const rgb = (cs.getPropertyValue('--accent-3').trim() || '#18c29c').match(/[0-9a-f]{2}/gi).map((x) => parseInt(x, 16));
const GW = 110, GH = 66;
const small = document.createElement('canvas');
small.width = GW; small.height = GH;
const sctx = small.getContext('2d');
const img = sctx.createImageData(GW, GH);

let U = new Float32Array(GW * GH).fill(1);
let V = new Float32Array(GW * GH).fill(0);
for (let k = 0; k < 6; k++) {
  const cx = (0.2 + Math.random() * 0.6) * GW, cy = (0.2 + Math.random() * 0.6) * GH;
  for (let y = 0; y < GH; y++) for (let x = 0; x < GW; x++) {
    if ((x - cx) ** 2 + (y - cy) ** 2 < 16) { U[y * GW + x] = 0.5; V[y * GW + x] = 0.25; }
  }
}
const FEED = 0.055, KILL = 0.062, DU = 1, DV = 0.5;
function idx(x, y) { return ((y + GH) % GH) * GW + ((x + GW) % GW); }
// Karl Sims 튜토리얼의 가중 3x3 라플라시안 — 4점 라플라시안보다 완만해서 dt=1 에서도 안정적이다
function lap(F, x, y) {
  return F[idx(x - 1, y - 1)] * 0.05 + F[idx(x, y - 1)] * 0.2 + F[idx(x + 1, y - 1)] * 0.05
    + F[idx(x - 1, y)] * 0.2 - F[idx(x, y)] + F[idx(x + 1, y)] * 0.2
    + F[idx(x - 1, y + 1)] * 0.05 + F[idx(x, y + 1)] * 0.2 + F[idx(x + 1, y + 1)] * 0.05;
}
function step() {
  const U2 = new Float32Array(U), V2 = new Float32Array(V);
  for (let y = 0; y < GH; y++) for (let x = 0; x < GW; x++) {
    const i = y * GW + x;
    const u = U[i], v = V[i], uvv = u * v * v;
    U2[i] = u + (DU * lap(U, x, y) - uvv + FEED * (1 - u));
    V2[i] = v + (DV * lap(V, x, y) + uvv - (FEED + KILL) * v);
  }
  U = U2; V = V2;
}
function render() {
  for (let i = 0; i < GW * GH; i++) {
    const n = Math.max(0, Math.min(1, V[i] * 2.4));
    const j = i * 4;
    img.data[j] = 13 + n * (rgb[0] - 13); img.data[j + 1] = 13 + n * (rgb[1] - 13); img.data[j + 2] = 18 + n * (rgb[2] - 18); img.data[j + 3] = 255;
  }
  sctx.putImageData(img, 0, 0);
  ctx.imageSmoothingEnabled = true;
  ctx.drawImage(small, 0, 0, c.width, c.height);
}
function resize() {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  c.width = innerWidth * dpr; c.height = innerHeight * dpr;
}
addEventListener('resize', resize);
resize();
for (let i = 0; i < 3000; i++) step(); // 스크린샷 시점에 이미 무늬가 자라 있도록 미리 계산
render();
(function loop() {
  for (let i = 0; i < 6; i++) step();
  render();
  requestAnimationFrame(loop);
})();

격자의 모든 칸에 물질 U와 V의 농도를 하나씩 둡니다. U는 계속 채워지고(먹이, feed), V는 계속 줄어들며(제거, kill), 동시에 둘이 만나면 V가 U를 잡아먹으며 V로 바꿉니다. 여기에 각 물질이 주변 칸으로 퍼지는 확산(라플라시안 계산)을 더하면, 이 단순한 규칙만으로 놀랍도록 유기적인 얼룩·산호·미로 무늬가 저절로 나타납니다. 이 데모는 그레이-스콧(Gray–Scott) 모델이라는 구체적인 변형을 씁니다.

패턴은 feed·kill 두 숫자에 극도로 민감합니다. 값을 조금만 바꿔도 점, 줄무늬, 미로, 소용돌이 사이를 오갑니다. 계산은 작은 격자(예: 120×70)에서 하고 화면에는 확대해서 그리는 게 일반적입니다 — 격자를 세밀하게 키울수록 무늬는 촘촘해지지만 계산량이 늘어납니다.

산호·세포·미로 같은 유기적 텍스처, 로고나 포스터의 배경 패턴, 절차적 텍스처 생성에 씁니다. 초기 상태에 무작위 얼룩을 몇 개 심어두면 매번 다른 모양으로 자라납니다.

언제 쓰나

유기적인 배경 텍스처, 산호·세포 느낌의 질감이 필요할 때. feed/kill 값을 실시간으로 바꿔볼 수 있게 노출하면 탐색이 빨라집니다.