
WebGL 렌더러로 갈아타자 바로 막힌 문제가 있었다. 사용자 그림은 SVG인데, GPU는 SVG를 모른다.
GPU는 삼각형만 안다
SVG는 "이 경로를 따라 곡선을 그려라"라는 서술이고, GPU가 그릴 수 있는 건 결국 삼각형뿐이다. Canvas 2D 시절에는 라이브러리가 경로를 CPU로 래스터화해 줬지만, WebGL 위에서는 경로를 GPU가 소비할 수 있는 형태(정점과 삼각형 인덱스)로 미리 바꿔둬야 한다. 우리는 이 변환 과정을 베이크라고 불렀다.
경로를 정점으로, 다각형을 삼각형으로
베이크는 세 단계로 돌았다. 먼저 SVG 경로를 파싱해 다각형으로 평탄화한다. 곡선을 짧은 선분들로 근사하는 단계다. 다음으로 earcut으로 다각형을 삼각형으로 분해한다. 마지막으로 캐릭터 영역을 덮는 그리드 메시에 스키닝 정보(정점별 영향 관절과 가중치)를 직렬화한다.
형상과 골격을 파일로 분리했다
산출물은 두 파일로 나눴다. rig.svg는 형상(어떻게 생겼나), rig.json은 골격과 가중치(어떻게 움직이나)다. 분리한 이유는 변경 주기가 다르기 때문이다. 그림을 다시 그리면 rig.svg만 바뀌고, 리깅을 조정하면 rig.json만 바뀐다. 하나로 합쳐두면 어느 쪽이 바뀌어도 전체를 다시 만들어야 한다.
베이크는 비싸니 캐시했다
베이크를 매번 다시 수행하기엔 비용이 컸다. 그래서 베이크된 스키닝 지오메트리를 localStorage에 보존해 같은 자산은 다시 굽지 않았다.
캐시에는 대가가 있다. 베이크 로직이 바뀌면 캐시가 낡은 결과를 들고 있는다. 그리고 베이크 산출물은 손으로 만든 게 아니라 기계가 구운 것이라, 잘못 구워져도 런타임에야 드러난다. 그래서 자산 검증을 CI에 걸어 삼각분해 실패와 가중치 누락을 빌드 단계에서 잡았다.
정리
렌더러가 GPU로 가면 자산도 GPU의 언어로 번역돼야 한다. 그 번역은 런타임 계산에서 사전 베이크로, 다시 캐시로 옮겨 갔고, 깨지기 쉬운 산출물은 CI가 지킨다. 자산 파이프라인은 코드만큼 쉽게 깨진다. 런타임에서 발견하는 순간 디버깅 비용은 몇 배가 된다.
다음에 읽을 글

예외가 나지 않는 버그가 더 무섭다
마이그레이션에서 가장 기억에 남는 버그는 화면을 깨뜨린 버그가 아니다. 에러 레벨로 쏟아지는 무해한 로그가 진짜 오류를 가렸고, 복제 로직이 새 필드를 몰라 보정값이 조용히 사라졌다. 시끄러운 버그는 스스로를 고발하지만, 조용한 버그는 발견 자체를 늦춘다.

357개 정점을 매 프레임 CPU로 움직이기
본편에서 걷어낸 수치와 공식의 기록. 합성 관절은 랜드마크 평균으로, 가중치는 역거리제곱으로, 회전은 atan2 참조 벡터로 구했다. 자료구조 다섯 개에 지오메트리 전체가 18KB. 규모가 작을 때 단순함은 성능보다 값지다.

잘 돌아가던 렌더러를 갈아엎은 이유
손그림 캐릭터를 실시간으로 움직이는 렌더러는 잘 돌아갔다. 문제는 캔버스 2D 벡터 엔진이라 GPU를 적극 활용하기 어렵다는 점이었다. 코어를 테스트로 고정하고 새 WebGL 엔진을 옆에서 키운 뒤 구 라이브러리를 걷어냈다. 잘 돌아가는 코드를 버린 이유는 고장이 아니라 구조의 한계 때문이었다.