
코드로 그림 3점 그렸습니다 (그린 건 알고리즘, 저는 규칙만)
흐름장, 궤도, 파동. 시드 하나 바꾸면 전혀 다른 그림이 나옵니다. 그린 코드도 같이 올려요.
자랑 포인트
- 그림 도구가 아니라 난수 시드·흐름장·궤도·파동을 코드로 짜서 컴퓨터가 그리게 한 추상 시리즈예요
- 흐름장(수천 선)·궤도(밤하늘)·파동(30겹 리본) 세 알고리즘으로 각각 다른 결을 만들었어요
- 같은 코드라도 시드를 바꾸면 매번 다른 그림이 나와, 출력 전까진 결과를 모릅니다
그림 도구로 그린 게 아니라 알고리즘한테 그리게 한 그림들입니다. 제가 한 건 규칙(난수 시드·흐름·궤도·파동)을 코드로 짠 것뿐이고, 화면은 컴퓨터가 채웠어요. 같은 코드라도 시드 숫자 하나 바꾸면 매번 다른 그림이 나옵니다. 이게 제너러티브 아트의 매력이에요. 결과물 세 점부터 보여드릴게요.
Flow Field — 흐름을 따라간 선들
화면 곳곳에 보이지 않는 '바람의 방향'을 깔아두고, 수천 개의 선이 그 흐름을 따라 흘러가게 했습니다. 선들이 알아서 길을 찾으면서 미로 같은 결이 생겨요.

Orbits — 난수로 쌓은 밤하늘
무작위 위치에 빛나는 점과 궤도 링, 성운 글로우를 겹겹이 쌓았습니다. 규칙은 똑같은데 우연이 겹치니까 돌릴 때마다 다른 우주가 나옵니다.

Ribbons — 30겹의 파동
진폭이랑 주파수, 위상이 다른 사인 파동을 반투명 리본으로 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번
이 작업에서 제일 재밌는 지점은 '내가 다 그리지 않았다'는 겁니다. 규칙만 정하면 나머지는 우연과 코드가 채워요. 출력 버튼을 누르기 전까지는 저도 뭐가 나올지 모릅니다. 그래서 마음에 드는 게 나올 때까지 시드를 계속 돌리게 되는데, 이게 뽑기 같아서 은근 중독됩니다. 위 세 점도 수십 번 돌려서 건진 애들이에요. 버려진 시드들에게 묵념.
관심 생기신 분은 Canvas랑 난수만 있으면 오늘 시작할 수 있습니다. 위 코드 복붙해서 시드부터 바꿔보세요. 자기 손으로 뽑은 첫 그림이 나오는 순간 이 장르에 빠지실 겁니다.