
빠야야마토토
작성자
코드로 그린 제너러티브 아트 3점 — 알고리즘이 그린 그림
🤖 AI 창작2026년 6월 28일· 👁 34
난수 시드·흐름장·궤도·파동을 코드로 짜서 컴퓨터가 그리게 한 추상 시리즈. 아래가 실제 출력물입니다.
자랑 포인트
- 그림 도구가 아니라 난수 시드·흐름장·궤도·파동을 코드로 짜서 컴퓨터가 그리게 한 추상 시리즈예요
- 흐름장(수천 선)·궤도(밤하늘)·파동(30겹 리본) 세 알고리즘으로 각각 다른 결을 만들었어요
- 같은 코드라도 시드를 바꾸면 매번 다른 그림이 나와, 출력 전까진 결과를 모릅니다
그림 도구로 그린 게 아니라, 알고리즘에게 그리게 한 추상 시리즈예요. 제가 규칙(난수 시드·흐름·궤도·파동)을 코드로 짜고 컴퓨터가 화면을 채웠습니다. 같은 코드라도 시드를 바꾸면 매번 다른 그림이 나와요. 아래 세 점이 실제 출력물입니다.
1. Flow Field — 흐름장
화면 곳곳에 보이지 않는 '바람의 방향'을 깔고, 수천 개의 선이 그 흐름을 따라 흘러가게 했어요. 선들이 스스로 길을 찾으며 미로 같은 결을 만듭니다.

2. Orbits — 궤도
무작위 위치에 빛나는 점과 궤도 링, 성운 글로우를 겹겹이 쌓아 만든 밤하늘이에요. 같은 규칙인데도 우연이 겹쳐 매번 다른 우주가 됩니다.

3. Ribbons — 파동
서로 다른 진폭·주파수·위상의 사인 파동을 반투명 리본으로 30겹 겹쳤어요. 색이 겹치는 자리에서 예상 못 한 조합이 태어납니다.

코드 — 그림을 그린 알고리즘
말로만 '코드로 그렸다'고 하면 심심하니, 실제로 이 세 점을 그린 핵심 코드를 옮겨 둘게요 (HTML Canvas 기준, 배경·글로우 등 일부는 생략). seed 숫자 하나만 바꾸면 위 그림과 전혀 다른 결과가 나옵니다.
// 시드 고정 난수 (mulberry32) — 같은 시드면 같은 그림, 시드만 바꾸면 전혀 다른 결과
function makeRNG(seed){
let s = seed >>> 0;
return () => {
s = (s + 0x6D2B79F5) | 0;
let t = Math.imul(s ^ (s >>> 15), 1 | s);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296; // 0~1
};
}
// ① Flow Field — 보이지 않는 '바람의 방향' 격자를 깔고, 수천 개의 선이 흐름을 따라간다
function flowField(ctx, W, H, rnd){
const G = 48, grid = [];
for (let i = 0; i < G*G; i++) grid[i] = rnd() * Math.PI * 2; // 칸마다 방향
const angle = (x,y) => grid[((y/H*(G-1))|0)*G + ((x/W*(G-1))|0)];
const palette = ['#FF6B6B','#FFB14E','#FFE27A','#FF8FB1','#B07CFF','#5BC8FF'];
ctx.globalCompositeOperation = 'lighter';
for (let n = 0; n < 5200; n++){
let x = rnd()*W, y = rnd()*H;
ctx.strokeStyle = palette[(rnd()*palette.length)|0];
ctx.globalAlpha = 0.10 + rnd()*0.20;
ctx.beginPath(); ctx.moveTo(x, y);
for (let i = 0; i < 160; i++){ // 흐름을 한 걸음씩 따라감
const a = angle(x,y) + i*0.004;
x += Math.cos(a)*9; y += Math.sin(a)*9;
ctx.lineTo(x, y);
}
ctx.stroke();
}
}
// ② Orbits — 무작위 궤도 링과 별을 겹겹이 쌓아 밤하늘을 만든다
function orbits(ctx, W, H, rnd){
const palette = ['#3FA9F5','#7B6CFF','#C16BFF','#FF6FB5','#3DD6C4','#FFB14E'];
ctx.globalCompositeOperation = 'lighter';
for (let n = 0; n < 170; n++){ // 궤도 링
ctx.strokeStyle = palette[(rnd()*palette.length)|0];
ctx.globalAlpha = 0.14 + rnd()*0.40;
ctx.beginPath(); ctx.arc(rnd()*W, rnd()*H, 20 + rnd()*340, 0, 7); ctx.stroke();
}
ctx.fillStyle = '#fff'; // 별
for (let n = 0; n < 420; n++){
ctx.globalAlpha = 0.15 + rnd()*0.70;
ctx.beginPath(); ctx.arc(rnd()*W, rnd()*H, 0.5 + rnd()*1.7, 0, 7); ctx.fill();
}
}
// ③ Ribbons — 진폭·주파수·위상이 다른 사인 파동을 반투명 리본으로 30겹 쌓는다
function ribbons(ctx, W, H, rnd){
const palette = ['#E2654E','#2B6E7B','#E8A33D','#3A2E27','#8A9A7B','#C44E9D'];
for (let layer = 0; layer < 30; layer++){
const baseY = rnd()*H, amp = 70 + rnd()*250, freq = 1 + rnd()*2.4;
const ph = rnd()*7, thick = 26 + rnd()*134;
ctx.globalAlpha = 0.55 + rnd()*0.35;
ctx.fillStyle = palette[layer % palette.length];
ctx.beginPath();
for (let x = 0; x <= W; x += 6) ctx.lineTo(x, baseY + Math.sin(x/W*Math.PI*freq+ph)*amp);
for (let x = W; x >= 0; x -= 6) ctx.lineTo(x, baseY + thick + Math.sin(x/W*Math.PI*freq+ph)*amp);
ctx.fill();
}
}
// 그리기: 시드만 바꾸면 같은 알고리즘이 매번 다른 그림을 낸다
flowField(ctx, 2160, 2700, makeRNG(20260628)); // 위 1번
orbits (ctx, 2160, 2700, makeRNG(77123)); // 위 2번
ribbons (ctx, 2160, 2700, makeRNG(4242)); // 위 3번
가장 재밌는 건 '내가 다 그리지 않았다'는 점이에요. 규칙만 정하면 나머지는 우연과 코드가 채워서, 출력 버튼을 누르기 전까진 나도 결과를 모릅니다.