Perfect Pixel 엔진 딥다이브 — 문장 하나가 100개의 모션 스프라이트가 되기까지
“파란 갑옷의 기사”라는 한 문장이 8방향 스프라이트와 100개 이상의 모션으로 완성되는 과정. 생성 파이프라인, 셀프 교정 품질 루프, 리얼 픽셀라이즈까지 — Perfect Pixel 엔진의 내부를 처음으로 공개합니다.
작성 — NEXUS AI Labs
게임 프로토타입에서 가장 먼저 발목을 잡는 건 코드가 아니라 아트 에셋입니다. 캐릭터 하나가 걷고, 달리고, 공격하고, 피격당하는 애니메이션을 8방향으로 갖추려면 수백 장의 프레임이 필요합니다. 숙련된 픽셀 아티스트가 캐릭터 하나에 붙어 몇 주를 작업해야 하는 분량이죠. Perfect Pixel은 이 문제를 정면으로 겨냥합니다 — 텍스트 한 문장을 입력하면, 게임 엔진에 바로 임포트할 수 있는 8방향 · 100+ 모션 스프라이트 세트가 나오는 데스크톱 스튜디오입니다.
이 글에서는 Perfect Pixel의 심장인 생성 엔진을 해부합니다. 왜 범용 이미지 생성 모델을 그대로 쓰면 픽셀아트가 망가지는지, 그래서 우리가 어떤 파이프라인을 설계했는지, 그리고 셀프 교정 품질 루프와 리얼 픽셀라이즈라는 두 개의 핵심 장치가 결과물을 어떻게 프로덕션 품질로 끌어올리는지를 순서대로 다룹니다.
왜 “그냥 이미지 생성”으로는 안 되는가
최신 이미지 생성 모델에게 “pixel art knight”를 요청하면 그럴듯한 그림이 나옵니다. 하지만 그 결과물을 게임에 넣어보면 곧바로 세 가지 문제가 드러납니다.
- 가짜 픽셀 그리드 — 모델이 그리는 것은 ‘픽셀아트처럼 보이는 그림’입니다. 픽셀 하나의 크기가 위치마다 다르고, 격자에 정렬되어 있지 않으며, 경계에는 안티앨리어싱이 뭉개져 있습니다. 확대하는 순간 정체가 드러납니다.
- 팔레트 폭발 — 진짜 픽셀아트는 16~64색의 통제된 팔레트로 그려집니다. 생성 모델의 출력은 수만 가지 색을 씁니다. 프레임마다 색이 미묘하게 달라져 애니메이션으로 이으면 캐릭터가 ‘일렁’입니다.
- 프레임 간 일관성 붕괴 — 걷기 6프레임을 각각 생성하면 매 프레임 갑옷 디테일과 비율이 달라집니다. 애니메이션의 생명은 일관성인데, 독립 생성으로는 이것이 원천적으로 확보되지 않습니다.
결론은 명확했습니다. 생성 모델은 파이프라인의 한 단계일 뿐, 파이프라인 그 자체가 될 수 없다는 것. Perfect Pixel 엔진은 생성 모델의 앞뒤에 프롬프트 컴파일러, 품질 판정 루프, 결정론적 후처리를 배치해 이 세 문제를 각각 격파합니다.
아키텍처 — Go 코어와 React 스튜디오
Perfect Pixel은 Go 1.25 + Wails v2 기반의 macOS 데스크톱 앱입니다. 프론트엔드는 React 18 + TypeScript + Vite, UI는 Radix UI와 Tailwind 위에 HSL 커스텀 프로퍼티 토큰 시스템으로 구성했습니다. 무거운 작업 — 프로바이더 API 오케스트레이션, 이미지 디코딩, 픽셀 후처리, 스프라이트 시트 패킹 — 은 전부 Go 코어가 담당하고, React는 순수하게 스튜디오 UI에 집중합니다.

Wails를 선택한 이유는 단순합니다. Electron 대비 바이너리가 10분의 1 수준으로 가볍고, Go의 고루틴이 모션 배치 생성 같은 대량 병렬 I/O에 이상적이기 때문입니다. 100개 모션을 생성할 때 엔진은 프로바이더의 rate limit에 맞춰 워커 풀을 조절하며 동시에 수십 개의 생성 요청을 흘려보냅니다. 이것을 Node 런타임에서 했다면 백프레셔 관리부터 다시 만들어야 했을 겁니다.
생성 파이프라인 — 문장에서 스프라이트 세트까지 6단계
사용자가 “파란 갑옷의 기사”라고 입력하면, 엔진 내부에서는 다음 여섯 단계가 순차·병렬로 실행됩니다.
- 프롬프트 컴파일 — 한 문장을 캐릭터 시트 명세로 확장합니다. 체형 비율, 팔레트 규모, 아웃라인 스타일, 조명 방향 같은 픽셀아트 규약을 시스템 프롬프트 레이어로 주입해, 어떤 프로바이더로 보내도 동일한 스타일 계약이 유지되게 합니다.
- 베이스 캐릭터 생성 — 컴파일된 프롬프트로 정면 기준 캐릭터를 생성합니다. 이 단계의 산출물이 이후 모든 방향·모션의 시각적 앵커가 됩니다.
- 8방향 파생 — 베이스 캐릭터를 레퍼런스로 남은 7개 방향을 생성합니다. 매 요청에 베이스 이미지를 함께 전달해 프레임 간 일관성 문제를 구조적으로 차단합니다.
- 모션 확장 — 걷기 · 달리기 · 공격 · 피격 · 사망 등 카테고리별 모션 템플릿에 따라 100개 이상의 애니메이션 프레임을 병렬 생성합니다. Go 워커 풀이 프로바이더별 rate limit에 맞춰 동시성을 조절합니다.
- 품질 루프 — 생성된 모든 프레임이 셀프 교정 루프를 통과합니다. 기준 미달 프레임은 실패 사유가 프롬프트에 반영되어 재생성됩니다 (다음 섹션에서 상술).
- 리얼 픽셀라이즈 & 내보내기 — 그리드 정렬, 팔레트 양자화, 시트 패킹을 거쳐 게임 엔진 친화 포맷으로 저장합니다.

셀프 교정 품질 루프 — 생성을 믿지 않는 엔진
100개 모션을 병렬 생성하면 반드시 일부는 실패합니다. 손가락이 뭉개지거나, 방향이 뒤집히거나, 팔레트를 벗어난 프레임이 섞여 나옵니다. 초기 버전에서는 이것을 사람이 눈으로 골라냈는데, 100개 중 15개꼴로 재작업이 필요했습니다. 그래서 엔진에 검수자 역할의 AI를 하나 더 넣었습니다.
품질 루프는 생성 직후 각 프레임을 비전 모델에게 보내 구조화된 판정을 받습니다. 판정 기준은 실루엣 무결성, 방향 정확성, 팔레트 준수, 모션 카테고리 부합 — 네 축입니다. 판정이 불합격이면 실패 사유가 다음 생성 프롬프트에 교정 지시로 삽입되어 재생성됩니다. 같은 프레임이 3회 연속 불합격하면 루프를 끊고 사용자 검수 큐로 보냅니다. 무한 루프 방지와 비용 통제를 위한 회로 차단기입니다.
// 프레임 하나의 품질 루프 — 판정 실패 사유를 프롬프트에 되먹임
func (e *Engine) generateWithQualityLoop(ctx context.Context, spec FrameSpec) (*Frame, error) {
prompt := e.compiler.Compile(spec)
for attempt := 1; attempt <= maxAttempts; attempt++ {
frame, err := e.provider.Generate(ctx, prompt, spec.BaseAnchor)
if err != nil {
return nil, err
}
verdict := e.inspector.Judge(ctx, frame, spec) // 비전 모델 판정
if verdict.Pass {
return frame, nil
}
// 실패 사유를 교정 지시로 주입해 재생성
prompt = prompt.WithCorrection(verdict.Reasons)
}
return nil, ErrNeedsHumanReview // 회로 차단 — 사용자 검수 큐로
}이 루프를 넣은 뒤 사람 재작업 비율은 15%에서 2% 아래로 떨어졌습니다. 흥미로운 부수 효과도 있었습니다 — 실패 사유 로그가 쌓이면서 프롬프트 컴파일러의 약점이 데이터로 드러났고, 시스템 프롬프트를 개선하는 근거가 됐습니다. 엔진이 스스로의 교사 데이터를 만드는 셈입니다.
리얼 픽셀라이즈 — ‘픽셀아트처럼 보이는 그림’을 진짜 픽셀아트로
품질 루프를 통과한 프레임도 아직은 ‘픽셀아트처럼 보이는 고해상도 이미지’입니다. 리얼 픽셀라이즈는 이것을 결정론적 후처리로 진짜 픽셀아트로 변환하는 단계입니다. AI가 아니라 순수 알고리즘이라는 점이 중요합니다 — 같은 입력이면 언제나 같은 출력이 나와야 게임 에셋으로 신뢰할 수 있기 때문입니다.
- 그리드 감지 & 스냅 — 이미지에서 지배적인 ‘의도된 픽셀 크기’를 주파수 분석으로 추정하고, 모든 픽셀을 균일 격자에 재정렬합니다. 가짜 픽셀 그리드 문제가 여기서 해소됩니다.
- 팔레트 양자화 — 수만 색의 출력을 캐릭터 시트에서 추출한 마스터 팔레트(기본 32색)로 수렴시킵니다. 프레임이 달라도 같은 팔레트를 공유하므로 애니메이션의 ‘일렁임’이 사라집니다.
- 엣지 클린업 — 안티앨리어싱으로 뭉개진 경계를 제거하고 1px 아웃라인 규약을 복원합니다.
- 투명 배경 추출 — 배경을 알파 채널로 분리해 어떤 씬에도 얹을 수 있는 상태로 만듭니다.
모션 스튜디오 — 이미 있는 그림도 움직인다
텍스트에서 시작하지 않는 경로도 있습니다. 레퍼런스 이미지 한 장을 드롭하면, 엔진이 그 그림의 스타일과 실루엣을 분석해 동일한 파이프라인 — 8방향 파생과 모션 확장 — 을 태웁니다. 오랫동안 갖고 있던 도트 캐릭터 한 장이 걷고 뛰는 애니메이션 세트가 되는 경험은 이 기능을 써본 사용자들이 가장 자주 언급하는 순간이기도 합니다.

멀티 프로바이더 — 특정 모델에 인질 잡히지 않기
이미지 생성 모델의 지형은 분기마다 바뀝니다. Perfect Pixel 엔진은 Gemini · OpenRouter · fal.ai · BytePlus 네 백엔드를 단일 인터페이스 뒤에 추상화했습니다. 프로바이더별 차이 — 레퍼런스 이미지 전달 방식, rate limit, 지원 해상도 — 는 어댑터 레이어가 흡수하고, 파이프라인 코드는 프로바이더를 모릅니다.
// 파이프라인이 아는 것은 이 인터페이스뿐 — 백엔드는 설정으로 교체된다
type ImageProvider interface {
// anchor는 프레임 간 일관성을 위한 베이스 캐릭터 레퍼런스
Generate(ctx context.Context, p CompiledPrompt, anchor *Image) (*Image, error)
Capabilities() ProviderCaps // 해상도, 레퍼런스 지원, 동시성 한도
}실전에서 이 구조는 두 번 우리를 구했습니다. 한 프로바이더의 정책 변경으로 특정 스타일 생성 품질이 급락했을 때, 설정 한 줄로 백엔드를 전환해 서비스 중단 없이 넘어갔습니다. 품질 루프의 판정 데이터가 프로바이더별 성능 벤치마크 역할을 해준 덕에, 어느 백엔드가 어떤 모션 카테고리에 강한지도 데이터로 알고 있습니다.
게임 엔진으로 — 내보내기 파이프라인
마지막 단계는 실제 게임 개발 워크플로에 꽂히는 일입니다. 완성된 프레임들은 모션 카테고리별로 정리된 스프라이트 시트로 패킹되고, 프레임 타이밍 메타데이터와 함께 내보내집니다. Unity · Godot · Phaser에서 별도 가공 없이 임포트할 수 있는 구조입니다. 아래는 Perfect Pixel로 만든 에셋이 실제 게임 씬에서 구동되는 모습입니다.


성능 벤치마크 — 숫자로 검증하다
엔진 초기 버전(단순 생성 → 사람 검수 방식)과 현재 버전(파이프라인 완전 자동화)을 직접 비교했습니다. 테스트 기준: 동일 캐릭터 명세(파란 갑옷 기사), 동일 프로바이더(Gemini 1.5 Flash), 100개 모션 풀 세트 생성.
재작업 비율 15% → 2%는 생각보다 큰 차이입니다. 100개 모션 세트에서 15%면 15개를 손으로 다시 생성해야 했는데, 각 모션 재생성이 평균 3~5분이면 총 45~75분의 추가 작업입니다. 2%로 줄면 2개, 즉 6~10분으로 수렴합니다. 게임 프로토타입에서 캐릭터를 자주 바꿀 때 이 차이는 복리로 쌓입니다.
프롬프트 컴파일러 내부 — 한 문장이 명세가 되는 과정
사용자가 입력한 "파란 갑옷의 기사"는 컴파일러를 거쳐 아래와 같은 구조화된 명세로 확장됩니다. 이 명세가 모든 이후 생성의 계약서가 됩니다.
{
"input": "파란 갑옷의 기사",
"compiled": {
"subject": "판타지 기사 캐릭터",
"visual_spec": {
"armor_color": "코발트 블루 (#1a4fd6), 하이라이트 실버",
"armor_style": "판금 갑옷, 어깨 보호대 돌출, 가슴 문장",
"body_ratio": "5등신 픽셀아트 표준, 머리 1단위",
"outline": "1픽셀 블랙 아웃라인, 안티앨리어싱 없음"
},
"palette_constraint": {
"max_colors": 32,
"required": ["#1a4fd6", "#0d2d7a", "#c0c0c0", "#000000"]
},
"pixel_grid": "32x48px 기준 격자",
"lighting": "상단 좌측 45° 단일 광원"
}
}컴파일 과정에서 가장 중요한 결정은 팔레트 constraint를 먼저 확정하는 것입니다. 주요 색상 4개를 고정하면, 나머지 28색은 이 4색의 밝기/채도 변형으로 제한됩니다. 이 제약이 프레임마다 색이 달라지는 팔레트 폭발을 막는 핵심 장치입니다.
지원 게임 엔진 & 내보내기 포맷
내보내기는 세 가지 포맷을 지원합니다. 각 포맷은 특정 엔진의 임포트 파이프라인에 최적화되어 있으며, 메타데이터 파일을 함께 내보내 프레임 타이밍과 히트박스 정보를 담습니다.
- PNG 스프라이트 시트 + JSON 메타 — Unity(SpriteEditor), Godot(SpriteFrames) 범용. 프레임 좌표와 FPS가 JSON으로 제공되어 추가 설정 없이 임포트.
- Aseprite 호환 `.aseprite` — 픽셀아트 전문가가 Aseprite에서 열어 레이어 단위로 수정 가능. 컷 단위 레이어와 태그가 자동 생성됨.
- Phaser 3 JSON Atlas — wasd(당사 텍스트-투-게임 엔진)와 직접 연동.
scene.anims.createFromAseprite()한 줄로 애니메이션 등록.
초기 설계의 실수들 — 그것이 지금 아키텍처를 만들었다
지금의 파이프라인이 처음부터 이 모습이었던 건 아닙니다. 초기 설계에서 틀렸던 것들을 기록해 둡니다. 같은 실수를 반복하지 않기 위해서이기도 하고, 이 기록 자체가 설계 결정의 근거가 되기 때문입니다.
- 고정 팔레트를 후처리에서 붙였다 (→ 실패) — 초기에는 생성 후 팔레트 양자화를 적용했습니다. 결과: 색 매핑 충돌로 캐릭터 색이 뭉개졌습니다. 팔레트는 생성 전에 프롬프트로 강제해야 한다는 결론.
- 모든 방향을 독립 생성했다 (→ 실패) — 8방향을 각각 별도 프롬프트로 생성하면 방향마다 장비 디테일이 달라집니다. 베이스 앵커 패턴으로 전환한 이후 일관성 문제의 80%가 해소됐습니다.
- 픽셀 크기를 고정했다 (→ 실패) — 32x48, 64x64 등 고정 캔버스를 강제했더니 복잡한 캐릭터는 선이 뭉치고 단순한 캐릭터는 여백이 과다했습니다. 지금은 캐릭터 복잡도에 따라 48x48~96x96 범위에서 컴파일러가 자동 결정합니다.
- 품질 판정을 사람이 했다 (→ 병목) — 100개 모션을 생성하고 사람이 골라내는 방식은 15%의 재작업이 발생했고, 배치 생성 후 하루 지나 결과를 확인하는 흐름이 반복됐습니다. AI 검수자를 파이프라인에 넣은 뒤 비동기 배치 생성이 완전히 자동화됐습니다.
마치며 — 생성이 아니라 파이프라인이 품질을 만든다
Perfect Pixel을 만들며 얻은 가장 큰 교훈은 이것입니다. 모델의 출력을 신뢰하지 말고, 검증하고 교정하는 구조를 신뢰하라. 프롬프트 컴파일러가 스타일 계약을 강제하고, 베이스 앵커가 일관성을 담보하고, 품질 루프가 불량을 걸러내고, 결정론적 후처리가 마지막 규약을 복원합니다. 각 단계는 단순하지만, 이 사슬 전체가 ‘문장 하나 → 프로덕션 에셋’이라는 경험을 만듭니다.
아트 팀 없이도 게임의 비주얼이 완성된다 — 이 문장이 마케팅 카피가 아니라 엔지니어링 명세가 되도록 만드는 것이 Perfect Pixel 엔진의 일입니다.
— NEXUS AI Labs
Perfect Pixel은 현재 macOS 데스크톱 앱으로 제공되며, 오픈소스 버전도 공개되어 있습니다. 엔진의 다음 과제는 타일셋 · 배경 생성으로의 파이프라인 확장, 그리고 품질 루프 판정 데이터를 활용한 스타일별 자동 프로바이더 라우팅입니다.

