책임 분산형 구조 제안안
procedural 방식의 패턴 생성(1)에서 새로운 이미지 생성과 렌더링 구조를 제안했다.
구조 요약
| 서버 | 패턴 타입 선택, 랜덤 파라미터 생성, 렌더링 알고리즘(JS 코드 등) 제공 |
| 클라이언트 | 받은 알고리즘과 파라미터로 실시간 이미지 렌더링 수행 |
| 아티스트 | 패턴 템플릿(노드 기반)을 설계하거나, 결과물을 조정해 피드백 제공 |
p5.js 도입 예제
p5.js 도입
새로운 구조를 검증하기 위해서, 빠르게 시각화 가능한 프레임워크로 p5.js를 도입했다. p5.js는 노드 에디터를 직접 지원하지 않고 코드를 수동으로 작성하여 만들어야 하지만, 아래의 이유로 채택하게 되었다.
- 노드 파싱 과정이 필요없어 pattern function 작성하여 바로 테스트 가능
- WebGL 기반이며 웹 브라우저에서 바로 렌더링 테스트 가능
- 예술공학부 1학년 과정에 포함되어 팀 내 아티스트 친화적
- .draw() 함수 하나로 구조 통일 가능 → 렌더 로직을 단일 인터페이스로 묶을 수 있음
- configUI 등과 연계 시, 노드 기반 구조도 가능할 것이라 예상
정리하자면, 당장 p5.js를 채용하진 않더라도 새로 제안한 렌더 구조를 테스트하고 설명하기 위한 프로젝트라고 생각하면 될 것 같다.
사용 시나리오


1. 아티스트가 노드 기반 편집기(예: UE5, Substance Designer 등) 를 통해 패턴 생성 템플릿을 만든다.
생성 알고리즘은 JSON 형태로 추출 가능해야 하며, 파라미터만 달리해도 다양한 결과물을 생성할 수 있도록 설계되었다. 이때, 테스트 프로젝트에서는 노드 기반 편집 과정을 생략하고 p5.js 로 패턴 생성 함수를 작성하여 템플릿으로 사용했다. 예제로 아래와 같은 JSON 패턴 템플릿을 사용했다.
"meltingScanline": {
"function": "function(x, y, p, ctx) {\n function hsvToRgb(h, s, v) {\n ... return [r, g, b];\n}",
"parameters": {
"spots": {
"type": "array",
"minItems": 3,
"maxItems": 4,
"items": {
"type": "object",
"properties": {
"x": { "type": "integer", "minimum": 100, "maximum": 500 },
"y": { "type": "integer", "minimum": 100, "maximum": 500 },
"radius": { "type": "number", "minimum": 100, "maximum": 150 },
"intensity": { "type": "number", "minimum": 0.8, "maximum": 1.2 },
"stretchY": { "type": "number", "minimum": 0.6, "maximum": 0.85 }
},
"required": ["x","y","radius","intensity","stretchY"]
}
},
"scanlineGap": { "type": "integer", "minimum": 4, "maximum": 6 },
"scanlineHue": { "type": "integer", "minimum": 120, "maximum": 260 },
"scale": { "type": "number", "default": 1.0 }
}
},
예제 템플릿에 참고한 소스
- Meta-Mounds Pattern
- Lycee Le Corbusier Patterns
- Dribbble - Generista Posters
- Dribbble - PLAIN Generator
2. 서버는 /v1/patterns/create 요청을 받으면, 템플릿 + 파라미터 + 렌더링 코드(JS 문자열)를 내려준다.
이때, 테스트 프로젝트에서는 템플릿과 파라미터를 랜덤으로 지정하여 내려주었다.
패턴 이름 "meltingScanline"을 요청하면:
→ 패턴 정의 템플릿 JSON 파일 로드
→ pattern["parameters"]로부터 parameters 스펙 읽기
→ 각 파라미터별 랜덤 값 생성
→ JSON과 함께 리턴
==> 랜더링 테스트에 사용될 렌더 정보 JSON 데이터 제공
3. 클라이언트는 받은 렌더링 정보의 Function(...) 을 이용해 실시간으로 이미지를 생성한다.
서버에서 전달받은 JSON에는 code 필드에 JavaScript 형식의 렌더링 함수 코드가 포함되어 있다.
클라이언트에서는 이 문자열을 기반으로 new Function(...)을 사용해 실행 가능한 draw 함수로 동적으로 변환한다.
예를 들어 서버로부터 아래와 같은 JSON을 받았다고 치자.
"meltingScanline": {
"function": "function(x, y, p, ctx) {\n function hsvToRgb(h, s, v) {\n ... return [r, g, b];\n}",
"parameters": {
"spots": {
"type": "array",
"minItems": 3,
"maxItems": 4,
},
},
클라이언트에서는 위의 JSON 정보를 들고 아래와 같은 흐름으로 이미지를 렌더한다.
- 렌더 정보 JSON에서 function code를 추출하고, function("x", "y", "p", code) 형태의 실행 가능한 함수로 변환한다.
- p5.js의 .draw() 루프 내에서 x, y 위치와 파라미터 p를 이 함수에 전달한다.
- 함수 반환값을 이용해 픽셀 색상을 설정, 캔버스에 실시간으로 렌더링한다.
즉, 서버는 생성 알고리즘(function code)과 파라미터를 포함한 렌더 정보만 전달하고, 클라이언트에서는 .draw() 루프하나로 모든 렌더를 처리하여 기능별로 분기하거나 여러 개의 draw 함수를 만들지 않고 이미지를 렌더할 수 있다.
쉽게 설명하자면, 삼각형, 사각형, 오각형, 원을 각각 그리기 위해 아래와 같이 조건문을 이용해 분기처리하도록 접근할 수 있다. 아마도 이게 떠올리기 가장 쉬운 방법 일 것.
if (shape === 'triangle') triangleDraw();
else if (shape === 'rectangle') rectangleDraw();
// ...
지금 사용하고 있는 렌더 구조에서는 팩토리 패턴 + 다형성 구조를 기반으로 조건문을 분기하는 대신 모든 그리기 로직을 object.draw(x, y, p) 함수 하나로 통일하고, object만 바꿔 끼우면 다른 결과물이 나오도록 구성했다. 아주 간단하게 축약하자면 아래와 같은 코드 형태로 표현할 수 있겠다.
let pattern;
function setup() {
createCanvas(400, 400);
pixelDensity(1);
noLoop();
// 서버에서 받은 설정 예시
const name = "circle";
const params = { cx: 200, cy: 200, r: 100 };
const patterns = {
circle: (x, y, p) => {
const dx = x - p.cx;
const dy = y - p.cy;
return Math.sqrt(dx * dx + dy * dy) < p.r ? 255 : 0;
},
wave: (x, y, p) => {
return Math.sin(x * p.freq) * p.amp * 128 + 128;
}
};
pattern = {
draw: (x, y) => patterns[name](x, y, params)
};
}
function draw() {
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const col = pattern.draw(x, y); // 핵심 draw 한 줄
const i = (y * width + x) * 4;
pixels[i] = pixels[i + 1] = pixels[i + 2] = col;
pixels[i + 3] = 255;
}
}
updatePixels();
}
단, 이때 function 코드를 그대로 실행하기 때문에 보안상 문제가 될 수 있다. 요 부분은 차차 고민해서 보완해나갈 예정...
실제 소스에 사용된 렌더 페이지를 의사코드로 간단히 옮기자면 아래와 같다.
<template>
[컨테이너]
[렌더링 카드]
[캔버스 영역]
[JSON 출력 카드]
[JSON 출력 TextArea]
[Create 버튼]
</template>
<script setup>
// 기본 참조 및 상태 정의
ref canvasParent // p5.js 캔버스를 부착할 DOM 요소
ref patternJson // 현재 패턴의 JSON 문자열
reactive params // 현재 패턴에 전달할 파라미터들
// 컴포넌트가 마운트될 때 p5 초기화
onMounted: initP5()
function initP5():
// 이미 초기화된 경우 무시
createCanvas(400x400) // 400x400 크기의 캔버스 생성
attachTo(canvasParent) // 캔버스를 지정된 DOM 요소에 붙임
draw():
getPattern from params // 현재 선택된 패턴 가져오기
loop x/y over canvas: // 픽셀 단위로 전체 캔버스 반복
result = pattern.draw(x, y, params) // x, y 좌표에 대해 패턴 그리기
drawPixel(result) // 결과값에 따라 픽셀 컬러 설정
function fetchRandomPattern():
result = await API.get("/patterns/create") // 서버에서 랜덤 패턴 가져오기
store JSON result in patternJson // 패턴 정보를 JSON으로 저장
compile draw function from result.code // 문자열로 받은 JS 코드를 함수로 변환(*보안이슈 있음)
set current patternType + params // 현재 패턴 및 파라미터 설정
redraw canvas // p5.js로 그리기
</script>
패턴 생성 예제 렌더 결과

'DevLog' 카테고리의 다른 글
| LLM 스트리밍 UX를 위한 아키텍처 설계(1) (3) | 2025.08.01 |
|---|---|
| 프로젝트 관리 (2) | 2025.07.19 |
| LLM 인터페이스 설계 (1) | 2025.06.28 |
| LLM 정량평가하기(1) (0) | 2025.06.21 |
| procedural 방식의 패턴 생성(1) (1) | 2025.06.06 |