4주차 · 2026-02-02 ~ 08
이주
이틀 만에 전체 애플리케이션을 네이티브 Python 데스크톱 프로그램으로 다시 만들었다. Three.js의 모든 편의를 잃었고, GPU에 대한 직접 접근을 얻었다. 더 안전한 선택이 늘 옳은 선택은 아니다.
지난 주는 세 개의 벽과 하나의 결론으로 끝났다. 브라우저는 이 프로젝트를 감당할 수 없다. 나는 주말 동안 대안을 생각했고, 월요일이 되기 전에 결정을 내렸다. 주 로직은 Python. 렌더링은 OpenGL 4.3을 통한 ModernGL. MediaPipe는 기기에서 직접 실행. 공간 오디오는 OpenAL. 스택 전체가 웹에서 내려와 네이티브 데스크톱 애플리케이션 위로 옮겨간다.
이주가 한 주 걸릴 거라고 예상했다. 이틀 걸렸다. 쉬웠기 때문이 아니라, 옮겨야 하는 범위가 두려워했던 것보다 작았기 때문이다.
스택
새 스택은 새로움을 위해 고른 것이 아니다. 각 조각은 지난주에 드러난 특정 문제를 해결한다.
기술 스택 · 아래로 흐르는 데이터 파이프라인
- 방문자의 손, 마우스, 키보드, 웹캠 피드에서 들어오는 원시 입력.
- Google의 손 추적 모델이 CPU에서 직접 실행된다. 손 하나당 21개의 랜드마크를 초당 약 30프레임으로 반환하며 브라우저 오버헤드가 없다.
- 최상위 애플리케이션 코드. 장면 상태, 오디오 재생, 그리고 MediaPipe·ModernGL·OpenAL 사이의 조율을 담당한다.
- OpenGL 4.3을 감싼 얇은 파이썬 래퍼. 저수준 C 보일러플레이트 없이 컴퓨트 셰이더, SSBO, 고급 텍스처 포맷을 노출한다.
- 렌더링 API. 컴퓨트 셰이더와 Shader Storage Buffer Object, 이 두 기능이 GPU 위의 대규모 파티클 시뮬레이션을 가능하게 했다.
- NVIDIA 노트북 GPU, 8 GB VRAM. 프로젝트 성능 예산의 하드웨어 타깃.
- 출력 창. 수십만 개의 오디오 반응형 파티클이 초당 60프레임으로 돌아간다.
Python은 내가 가장 빠르게 쓸 수 있는 언어였다. 실행 속도가 가장 빠른 언어는 아니지만, 애플리케이션 로직에서는 그것이 병목이 아니다. 병목은 GPU이며, ModernGL이 Python을 OpenGL 4.3과 직접 대화시켜 준다. OpenGL 4.3은 컴퓨트 셰이더와 Shader Storage Buffer Object를 지원하는 최소 버전이고, 이 두 기능이 대규모 파티클 시뮬레이션에 필요했다. MediaPipe에는 브라우저 계층 없이 CPU에서 실행되는 네이티브 Python 바인딩이 있어, 지연시간 문제를 원점에서 제거해 준다. OpenAL은 이후 주차들에서 계획한 1인칭 내부 장면과 잘 맞는 방식으로 3D 위치 기반 오디오를 처리한다.
이틀, 세 가지 고통
이주는 매끄럽지 않았다. 세 가지 별개의 어려움이 나타났고, 그 어느 것도 시간이 모자라서 생긴 것이 아니었다.
-
Three.js의 편의를 잃다
Three.js는 많은 작업을 감춰 준다. 장면 그래프, 카메라 행렬, 기본 렌더 루프, 기본 도형들. Three.js에서는
new THREE.Points(geometry, material)을 쓰면 파티클 시스템이 존재했다. ModernGL에서는 정점 버퍼, 정점 배열 객체, 정점·프래그먼트 셰이더, 유니폼 업로드, 드로 콜까지 전부 손으로 써야 했다. 각각은 작은 양의 코드다. 합쳐지면 화면에 파티클 한 개가 찍히기 전까지 하루 분량의 재구현이다. -
그래픽 프리미티브부터 다시 쌓기
두 번째 어려움은 더 깊었다. 웹 버전에서 나는 기능을 조합하고 있었다. 네이티브 버전에서는 프리미티브를 조합하고 있다. Three.js의 파티클은 위치와 머티리얼을 가진 객체다. ModernGL의 파티클은 버퍼 안의 부동소수점 값이고, 셰이더가 그것을 내가 쓴 방식대로 해석할 뿐이다. 무엇이 파티클인지에 대한 내장된 개념이 없다. 그것을 내가 정의해야 하고, 그린다는 것이 무엇인지도 내가 정의해야 한다.
단기적으로는 일이 더 많다. 그리고 이것이 네이티브 버전이 결국 웹 버전이 갈 수 없는 곳까지 갈 수 있었던 이유이기도 하다. Three.js가 제공하는 추상은 나를 제한하던 추상이기도 했다.
-
문서는 얇고 AI 도움은 적다
Three.js는 커뮤니티가 크고 인터넷에 방대한 예제 코드가 있다. AI 코딩 어시스턴트들도 그것을 잘 안다. ModernGL은 더 작다. Python + OpenGL은 그보다도 더 작다. 컴퓨트 셰이더 문제를 AI에게 디버깅 도움을 요청하면, 돌아오는 답은 그럴듯해 보이지만 틀린 것이 잦았다. 이 구석의 학습 데이터는 얇고, AI가 만들어 낸 것을 더 많이 검증해야 했기에 피드백 루프가 느려졌다.
나는 프로젝트 전반에서 vibe coding을 주된 방법으로 써 왔다. 이번 주는 그 방법이 처음으로 반발해 온 주였다.
Git이 이 세 고비를 지나가도록 해 줬다. 작은 성공이 있을 때마다 커밋을 남겼다. 변경이 빌드를 깨뜨리면 마지막으로 작동하던 상태로 되돌리고 다른 각도에서 다시 시도했다. 이 교훈이 이번 주에 완전히 자리를 잡은 것은 아니었다. 습관이 진짜로 몸에 배기까지는 학기 후반에 작업을 잃어버리는 일이 한 번 필요했지만, 그 기반은 이미 이번 주에 놓이고 있었다.
결과
둘째 날이 끝날 무렵 네이티브 애플리케이션은 창을 열고, 오디오 파일을 불러오고, 오디오에 반응하는 파티클을 화면에 그리고 있었다. 비주얼은 Three.js가 뽑아내던 것보다 거칠었다. 기본으로 오는 블룸도 없고, 기본 알파 블렌딩도 부드럽지 않고, 파티클은 단단한 점으로 찍혔다. 하지만 지연시간은 사라졌고, 프레임 레이트는 웹 버전이 감당할 수 있었던 것보다 훨씬 많은 파티클에서도 60에서 안정적으로 유지됐다. 그리고 이후에 만들고 싶은 모든 것을 위한 컴퓨트 셰이더에 직접 접근할 수 있었다.
브라우저 안의 Three.js. 세 가지 축이 기술적으로는 돌고 있었지만, 지연시간과 성능, 시각적 천장이 경험을 제한했다.
Python과 ModernGL. 비주얼은 더 거칠지만 반응은 빠르다. 브라우저에 있던 천장이 사라졌다.
애플리케이션은 이제 프로젝트 남은 기간 동안 그대로 유지될 스택 위에 놓여 있었다. 이후의 일은 플랫폼 선택에 관한 것이 아니라, 그 위에 실제로 무엇을 쌓을 수 있는가에 관한 것이 될 것이었다.
최종 결과 · 주차 마감 상태