Skip to content

PlayCanvas를 포크하지 않고 직접 WebGPU 엔진을 만든 이유

Oleg Sidorkin, Cinevva CTO 겸 공동창업자

A stylized low-poly world rendered by Cinevva's WebGPU engine: rolling hills, a cave carved into a cliff, a winding river, and an avatar mid-stride on a path at golden hour

Cinevva World를 본 기술자라면 누구나 5분 안에 같은 질문을 합니다. 당신들은 작은 팀이다. 성숙한 웹 3D 엔진이 이미 존재한다. 그런데 왜 PlayCanvas나 Babylon 위에 올라타 더 빨리 출시하지 않고, 직접 렌더러를, 직접 캐릭터 물리를, 직접 애니메이션 시스템을 만들었나?

타당한 질문이고, "여기서 만든 게 아니라서(not invented here)"는 틀린 답입니다. 우리는 숙제를 했습니다. 기존 엔진들을 돌려봤고, 소스를 읽었고, 결정을 내리기 전에 그중 둘 위에 스파이크를 올려서 출시까지 해봤습니다. 이 글은 그 결정의 솔직한 버전입니다. 기성 옵션들이 진짜로 잘하는 것, 우리 요구가 직접 만드는 게 정당화될 만큼 크게 갈라진 네 곳, 그리고 우리를 따라 하기보다 그중 하나를 골라야 하는 상황을 다룹니다. 빌드 자체는 장기 연재인 브라우저 안의 오픈월드 엔지니어링 시리즈에 기록해 두었으니, 아래 주장에 작업 로그가 있는 경우에는 링크를 걸겠습니다.

우리가 실제로 평가한 옵션들

"웹 게임 엔진"이라고 불리는 네 가지가 있는데, 서로 같은 종류의 물건이 아닙니다.

Three.js는 게임 엔진이 아니라 렌더링 라이브러리입니다. 씬 그래프, 머티리얼, 로더, 렌더러를 주고 나서 길을 비켜줍니다. 에디터도, 물리도, 엔티티 시스템도, 게임을 어떻게 구성해야 하는지에 대한 의견도 없습니다. 그게 매력이자 비용입니다. 렌더러 위의 모든 것을 직접 만들어야 하지만, 그 무엇도 당신과 싸우지 않습니다. MIT 라이선스고, 이 분야에서 가장 큰 생태계를 갖고 있으며, 새벽 2시에 난해한 셰이더 버그를 만나도 답을 주는 포럼 글이 기다리고 있습니다.

Babylon.js는 씬 그래프, 물리 통합(Havok), 에셋 파이프라인, 웹 에디터를 갖춘 완전한 엔진입니다. MIT 라이선스고, Microsoft의 팀이 뒷받침하며, WebGPU 작업이 빠르게 진행돼 왔습니다. 배터리가 포함된 걸 원하고 엔진의 구조 안에서 사는 게 편하다면, 강력한 기본 선택지입니다.

PlayCanvas는 "웹용 Unity"에 가장 가까운 물건입니다. 이 글을 쓰는 시점에 엔진은 2026년 6월 5일에 출시된 v2.19.6이고, 엔진 자체는 MIT 라이선스의 오픈소스입니다. 다만 대부분의 사람이 실제로 쓰는 건 호스팅 비주얼 에디터이고, 그 에디터는 오픈소스가 아니라 상용 제품입니다. PlayCanvas는 엔티티-컴포넌트 시스템을 쓰고, TypeScript나 JavaScript로 스크립팅하며, 에셋은 서버 사이드 GLB 파이프라인을 거치고, 실제 상용 게임들이 이 위에서 출시됐습니다(Snap이 프로덕션 작업을 PlayCanvas로 돌리는 게 한 예입니다). 렌더러는 WebGL2로 돌아가고 WebGPU 경로는 기본값이라기보다 아직 성숙해 가는 중입니다. 그 마지막 디테일이 들리는 것보다 더 중요한데, 뒤에서 다시 다루겠습니다.

Unity WebGL은 애초에 웹 엔진이 아닙니다. 내보내기 대상입니다. Unity 데스크톱 에디터에서 빌드한 다음 WebGL 번들로 컴파일합니다. 이미 Unity 게임이 있고 그걸 브라우저에 넣고 싶을 때는 맞는 도구고, "중급 휴대폰의 탭에서 즉시 로드된다"가 강한 요구사항일 때는 틀린 도구입니다. 런타임과 다운로드 무게가 따라오기 때문입니다.

이 중 무엇이든 평범한 웹 게임의 합리적인 토대가 됩니다. 우리는 평범한 웹 게임을 갖고 있지 않았습니다.

결정 하나: WebGPU는 우리의 바닥이지 결승선이 아니다

우리를 모든 기성품에서 밀어내는 갈림은 이것입니다. 우리에게 WebGPU는 언젠가 도달할 기능이 아니라 요구사항입니다.

우리 지형은 정적 하이트맵이 아닙니다. 스트리밍되는 하이트맵 필드와, 부호 거리 필드(signed-distance field)로 뒷받침되는 마칭 큐브 청크의 하이브리드라서, 세계에 진짜 동굴과 오버행이 있을 수 있고 창작자가 실시간으로 조각할 수 있습니다. 조각 브러시, 식생 스캐터, 지형 메싱은 모두 컴퓨트 셰이더로 돌아갑니다. 컴퓨트를 빼면 세계는 품질이 떨어지는 게 아니라 아예 돌지 않습니다.

이건 오늘날 범용 엔진들이 서 있는 자리와 정반대입니다. 그들의 WebGPU 지원은 WebGL2 우선 렌더러 위에 얹은 점진적 향상으로 설계되어 있고, WebGPU가 없는 브라우저를 위한 폴백 경로를 둡니다. 특히 PlayCanvas는 WebGL2 우선이고 WebGPU는 아직 베타입니다. 그들에게는 그게 올바른 판단입니다. 그들의 일은 가능한 가장 넓은 게임 매트릭스를 가능한 가장 넓은 기기 매트릭스에서 돌리는 것이니까요. 우리 일은 더 좁고 깊어서, 우리는 반대로 판단했습니다. 스파이크 13에서 WebGPU 전용으로 갔고 다시는 뒤돌아보지 않았습니다. WebGPU가 없는 브라우저는 다운그레이드되는 게 아니라 미지원이고, 우리는 그걸 WebGL2 폴백이 설정 플래그 하나 거리에 있는 척하는 대신 도달률 수치로 추적합니다. 그건 설정 플래그 거리에 있지 않습니다. 우리 지형과 식생 단계의 부분 재작성이 될 겁니다.

WebGL2 우선 엔진 위에 짓는다는 건 모든 컴퓨트 기능마다 그 엔진의 폴백 가정과 싸우거나, 아니면 두 개의 렌더링 경로를 영원히 유지하거나 둘 중 하나를 뜻했을 겁니다. 렌더러를 소유하니 컴퓨트를 기준선으로 다룰 수 있었습니다.

결정 둘: 물리 엔진이 아니라 캐릭터 솔버

교과서적인 수는 물리 엔진을 끼워 넣는 것입니다. 우리도 해봤습니다. 초기 스파이크에서 워커 안의 Rapier를 검증했고, 괜찮게 느껴졌습니다.

그래도 우리는 직접 만들었고, 출시 빌드 어디에도 Rapier, Cannon, Ammo가 없습니다. 이유는 범위입니다. 우리는 강체, 조인트, 래그돌, 제약 솔버가 필요 없습니다. 캡슐 하나가 지형에 대해 올바르게 움직이면 되고, 걷기, 미끄러지기, 활공, 등반, 수영이 "내가 지면에 있는가, 어떤 표면 위에서, 어떤 각도로"에 대한 하나의 공유된 답에 동의하기만 하면 됩니다. 범용 물리 엔진은 이걸 더 쉽게가 아니라 더 어렵게 만듭니다. 그 모드들이 결국 엔진 내부의 스프링과 댐퍼와 싸우게 되기 때문입니다.

그래서 우리 캐릭터 컨트롤러는 플러그형 멀티채널 상태 머신입니다. 각 모드는 이번 프레임에 제어권을 원하는지 말하고, 이기면 속도와 방향을 쓰는 작은 유닛입니다. 이들은 자원, 자세, 이동이라는 세 채널에 걸쳐 우선순위로 중재하고, 모두 같은 지형 쿼리를 읽습니다. 충돌은 청크에 따라 하이트맵이나 부호 거리 필드에 대한 캡슐 프로브입니다.

제가 가장 자랑스러워하는 디테일은 화려하지 않습니다. 지면 검출은 SDF 기울기를 믿는 대신, 발밑 수직 기둥의 모든 표면을 스캔해 캡슐 이하에서 가장 높은 것을 고릅니다. 오버행 가장자리에서는 옆으로 가장 가까운 표면이 절벽 면이라, 기울기 기반 노멀은 "바닥"과 "벽" 사이를 깜빡거리고 유령 미끄러짐이 생깁니다. 기둥 쿼리는 턱 위에 서 있는 걸 지루하게 만드는데, 그게 바로 당신이 원하는 것입니다. 기성 하이트필드 콜라이더는 이걸 공짜로 주지 않았을 겁니다. 애초에 우리 지형을 볼 수 없기 때문입니다. 지형은 우리 포맷으로 GPU 위에 삽니다. 외부 엔진은 매 편집마다 우리가 필드 전체를 그 콜라이더 포맷으로 복사해 주지 않으면 거기에 충돌시킬 수 없습니다.

결정 셋: 리그를 가로질러 이동하는 애니메이션

아바타는 Synty POLYGON 리그를 쓰고, 우리 모션 클립은 큰 오픈 애니메이션 라이브러리에서 옵니다. 리그와 클립이 본 이름을 공유하기 때문에, 일반적인 경우엔 런타임 리타게팅이 필요 없고 트랙이 이름으로 그냥 바인딩됩니다. 전체 캐릭터 파이프라인은 범용 캐릭터에 정리해 두었습니다.

흥미로운 작업은 둘이 맞지 않는 경우입니다. 우리는 한 스켈레톤을 다른 스켈레톤에 매핑하는 리타게팅 패스를 만들었고, 이걸 제대로 하려면 세 가지 구체적인 문제를 풀어야 했습니다. 먼저 바인드 포즈를 정렬합니다. A-포즈를 T-포즈에 맞추면 조인트마다 약 30도가 조용히 더해지기 때문입니다. 루트 모션과 보폭은 두 개의 별도 비율로 스케일합니다. 수직은 엉덩이-바닥(hip-to-floor), 수평은 전체 비율입니다. 그리고 리타게팅된 클립을 고정 샘플 레이트로 원본과 비교 테스트해서, 회귀가 플레이어보다 먼저 드러나게 합니다. 그 파이프라인이 새 애니메이션 소스를, 그리고 언젠가는 창작자가 제공한 캐릭터를, 모든 클립을 손으로 고치지 않고 추가할 수 있게 해줍니다.

그 위에는 모션 상태에서 매 프레임 올바른 클립 패밀리를 고르는 애니메이션 상태 머신이 있습니다. 속도에 따른 점프 변형, 충격에 따른 착지 강도, 속도와 지형 기울기의 내적에서 나오는 경사 방향, 멈출 때 문워크하지 않도록 하는 발 위상 정확 정지, 그리고 벽에 부딪혀도 걷기와 떨어지기 사이에서 깜빡이지 않게 하는 100밀리초 디바운스. 이 중 무엇도 이국적이지 않지만, 그 레이어를 소유해야만 얻을 수 있는 종류의 것입니다.

결정 넷: 어떤 엔진도 출시하지 않는 부분

나머지 세 결정은 세계가 어떻게 돌아가는지에 관한 것입니다. 이 결정은 제품이 무엇인지에 관한 것이고, PlayCanvas와의 비교가 결국 범주 오류인 이유입니다.

PlayCanvas, Babylon, Unity, 그리고 직접 짠 Three.js 앱은 모두 같은 형태를 가정합니다. 당신은 책상에서, 2D 에디터 안에서, 세계를 바깥에서 들여다보며 게임을 만들고, 그다음 재생을 눌러 별도의 런타임을 출시합니다. 그들의 엔진 내 편집조차 워크스테이션 앞에 앉은 당신이 들여다보는 씬을 조작하는 것입니다.

Cinevva World는 그걸 뒤집습니다. 당신은 세계 안에서, 당신의 플레이어가 설 바로 그 공간에 선 아바타로서, 원하는 걸 묘사하고 그것이 나타나는 걸 지켜보며 만듭니다. 창작과 플레이는 런타임에 다리를 놓은 빌드 단계가 아니라 하나의 연속된 세션입니다. 가장 가까운 참조점은 웹 3D 엔진이 아니라 Roblox, Rec Room, Horizon Worlds이고, 그것들조차 창작 대부분을 책상에 둡니다. 마우스로 모는 에디터 없이 이걸 다룰 수 있게 만드는 건 AI 빌더인데, "여기에 등불 켜진 부두를 놓아"를 지오메트리와 배치로 바꿔줍니다.

이건 범용 엔진에 볼트로 조여 붙일 수 없습니다. 렌더링 기능이 아니기 때문입니다. 이건 스택 전체의 전제입니다. 지형이 런타임에 어떻게 편집 가능한지부터, 네트워크가 모든 오브젝트를 근처에 선 사람이 방금 만든 무언가로 어떻게 다루는지까지요.

우리가 한 일을 따라 하면 안 되는 경우

여기가 "우리가 직접 엔진을 만들었다" 장르가 보통 건너뛰는 부분입니다. 이번 분기에 브라우저 게임을 출시하고 싶다면, 우리를 따라 하지 마세요. Unity 스타일 에디터와 관리형 파이프라인을 원하면 PlayCanvas를 쓰세요. 물리가 포함된 완전한 엔진을 원하고 그 구조 안에서 편하면 Babylon을 쓰세요. 최대한의 제어와 최소한의 의견을 원하고 그 위에 쌓아 올릴 팀이 있으면 Three.js를 쓰세요. 이미 Unity 게임이 있으면 Unity에서 포팅하세요.

직접 렌더러, 물리, 애니메이션을 작성하는 건 제품의 전제가 기성품과 양립할 수 없을 때, 만들고 있는 게 엔진 위의 게임이 아니라 마침 엔진으로 만들어진 장소일 때에만 올바른 판단입니다. 우리에겐 그게 사실이었습니다. 당신에겐 아마 사실이 아닐 거고, 그래도 괜찮습니다. 평가를 솔직하게 하는 핵심은 첫 줄을 쓰기 전에 자신이 어느 경우에 속하는지 아는 것입니다.