펄린 노이즈

Perlin noise

구름처럼 자연스럽게 이어지는 난수를 만드는 함수. Ken Perlin이 1983년 영화 트론의 CG를 위해 고안했습니다.

다른 이름: Gradient noiseProcedural noise
···
js
const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const small = document.createElement('canvas');
const sctx = small.getContext('2d');
const SW = 96, SH = 60;
small.width = SW; small.height = SH;
const img = sctx.createImageData(SW, SH);

// 클래식 그래디언트(펄린) 노이즈: 격자점마다 랜덤 방향 벡터를 두고 내적을 보간
const GRID = 16;
const grads = [];
for (let i = 0; i < GRID * GRID; i++) { const a = Math.random() * Math.PI * 2; grads.push([Math.cos(a), Math.sin(a)]); }
function grad(ix, iy) { return grads[((iy % GRID + GRID) % GRID) * GRID + ((ix % GRID + GRID) % GRID)]; }
function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(a, b, t) { return a + (b - a) * t; }
function perlin(x, y) {
  const x0 = Math.floor(x), y0 = Math.floor(y), xf = x - x0, yf = y - y0;
  const g00 = grad(x0, y0), g10 = grad(x0 + 1, y0), g01 = grad(x0, y0 + 1), g11 = grad(x0 + 1, y0 + 1);
  const d00 = g00[0] * xf + g00[1] * yf, d10 = g10[0] * (xf - 1) + g10[1] * yf;
  const d01 = g01[0] * xf + g01[1] * (yf - 1), d11 = g11[0] * (xf - 1) + g11[1] * (yf - 1);
  const u = fade(xf), v = fade(yf);
  return lerp(lerp(d00, d10, u), lerp(d01, d11, u), v);
}

const cs = getComputedStyle(document.documentElement);
const rgb = (cs.getPropertyValue('--accent').trim() || '#5b5bf7').match(/[0-9a-f]{2}/gi).map(x => parseInt(x, 16));
function render(t) {
  for (let y = 0; y < SH; y++) for (let x = 0; x < SW; x++) {
    const n = (perlin(x * 0.12 + t * 0.6, y * 0.12) + 1) / 2;
    const i = (y * SW + x) * 4;
    img.data[i] = rgb[0] * n; img.data[i + 1] = rgb[1] * n; img.data[i + 2] = rgb[2] * n; img.data[i + 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();
render(0); // 첫 프레임부터 바로 보이게
let t = 0;
(function loop() { t += 0.02; render(t); requestAnimationFrame(loop); })();

Math.random()이 만드는 난수는 이웃한 값끼리 전혀 상관이 없어서 화면에 그리면 그냥 잡음(스태틱)이 됩니다. 펄린 노이즈는 격자의 각 꼭짓점에 방향(그래디언트)을 하나씩 배정하고, 격자 안의 임의의 점에서는 주변 꼭짓점들의 그래디언트를 부드럽게 보간해서 값을 얻습니다. 그 결과 가까운 점끼리는 비슷한 값을, 먼 점끼리는 다른 값을 가지는 "이어지는" 난수가 만들어집니다.

이 데모는 시간 축을 세 번째 차원처럼 써서(2차원 노이즈를 시간에 따라 다른 오프셋에서 샘플링) 구름이 흐르는 듯한 애니메이션을 만듭니다. 낮은 해상도로 계산한 뒤 확대해서 그리면 비용을 크게 줄일 수 있습니다.

지형·구름 텍스처, 카메라 흔들림, 캐릭터의 미세한 떨림처럼 "규칙적이지도 무작위하지도 않은" 움직임이 필요한 모든 곳에 쓰입니다. 2002년 Perlin이 제안한 개선판 심플렉스 노이즈는 고차원에서 더 빠르고 격자 방향 편향이 적습니다.

언제 쓰나

카메라 흔들림, 캐릭터 idle 애니메이션, 텍스처 생성 등 "자연스러운 불규칙함"이 필요할 때.