3D 웹게임 개발 가이드: WebGPU와 Three.js vs Babylon.js
3D 웹게임, 지금 시작한다면 뭘로 만들어야 하나
"3D 웹게임을 만들고 싶은데 뭐부터 시작해야 하나"라는 질문에는 사실 두 개의 결정이 숨어 있다. 하나는 렌더링 API를 WebGL로 갈지 WebGPU로 갈지, 다른 하나는 엔진을 Three.js로 갈지 Babylon.js로 갈지다. 이 두 결정은 서로 얽혀 있어서 순서대로 정리하는 게 낫다.
WebGL vs WebGPU, 지금 현실
WebGL은 2011년부터 브라우저 3D 렌더링의 사실상 표준이었다. OpenGL ES를 브라우저에 이식한 API라 성숙하고, 모든 주요 브라우저에서 거의 100%에 가깝게 지원된다. 지금 이 순간 "확실히 동작하는 3D 웹게임"을 만들어야 한다면 여전히 WebGL이 정답이다.
WebGPU는 그 다음 세대 API다. Direct3D 12, Metal, Vulkan 같은 최신 저수준 그래픽 API의 설계를 계승해서 만들어졌고, WebGL 대비 CPU 오버헤드가 훨씬 적다. 드로우콜을 미리 기록해뒀다가 재사용하는 커맨드 버퍼 모델을 쓰기 때문에, 매 프레임 상태를 새로 설정하고 검증하는 WebGL의 구조적 병목이 크게 줄어든다. 오브젝트 수가 많은 씬에서 특히 체감 차이가 크다.
WebGPU가 진짜로 새로 가져다주는 건 컴퓨트 셰이더(compute shader)다. WebGL에는 범용 GPU 연산을 위한 표준 경로가 없어서 파티클 시뮬레이션이나 물리 연산 같은 걸 GPU에서 돌리려면 프래그먼트 셰이더를 억지로 우회해서 쓰는 트릭이 필요했다. WebGPU는 컴퓨트 파이프라인을 1급 시민으로 지원해서, 수만~수십만 개 파티클을 GPU에서 직접 시뮬레이션하고 그 결과를 바로 렌더링에 연결하는 게 훨씬 자연스러워졌다.
브라우저 지원 현황은 이렇다. Chrome과 Edge는 버전 113부터 WebGPU를 기본 활성화했고 현재 최신 버전까지 안정적으로 지원한다. Safari는 macOS와 iOS 26 계열부터 정식 지원에 들어갔다. 반면 Firefox는 아직 기본적으로 비활성화된 상태라, WebGPU 전용 기능에 의존하는 사이트는 Firefox 사용자에게 폴백을 제공해야 한다. 정리하면 데스크톱 크로미움 계열과 최신 사파리에서는 실사용 가능한 수준이지만, Firefox와 구형 브라우저까지 커버해야 하는 서비스라면 아직은 WebGL을 기본으로 하고 WebGPU를 점진적 향상(progressive enhancement)으로 얹는 전략이 안전하다.
// 브라우저가 WebGPU를 지원하는지 런타임에 확인하는 가장 기본적인 방법
if (navigator.gpu) {
console.log('WebGPU 사용 가능');
} else {
console.log('WebGPU 미지원 — WebGL 폴백 필요');
}
최소한의 WebGPU 초기화 코드
엔진 없이 순수 WebGPU API로 초기화하는 과정을 한 번 보면 왜 다들 엔진을 쓰는지 이해가 된다.
async function initWebGPU(canvas) {
if (!navigator.gpu) {
throw new Error('이 브라우저는 WebGPU를 지원하지 않습니다.');
}
// GPU 어댑터(물리적 GPU에 대한 핸들) 요청
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw new Error('적합한 GPU 어댑터를 찾을 수 없습니다.');
}
// 실제 커맨드를 제출할 논리적 디바이스
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format,
alphaMode: 'opaque',
});
return { device, context, format };
}
async function renderClearScreen(canvas) {
const { device, context } = await initWebGPU(canvas);
const commandEncoder = device.createCommandEncoder();
const textureView = context.getCurrentTexture().createView();
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [
{
view: textureView,
clearValue: { r: 0.05, g: 0.05, b: 0.1, a: 1 },
loadOp: 'clear',
storeOp: 'store',
},
],
});
renderPass.end();
device.queue.submit([commandEncoder.finish()]);
}
이 코드는 화면을 특정 색으로 지우는 것 말고는 아무것도 하지 않는다. 실제로 삼각형 하나를 그리려면 WGSL(WebGPU Shading Language)로 셰이더를 작성하고, 버텍스 버퍼를 만들고, 렌더 파이프라인을 정의하는 코드가 추가로 필요하다. 로우레벨 API인 만큼 자유도는 높지만 게임 하나 만드는 데 필요한 보일러플레이트가 상당하다. 그래서 실전에서는 이 위에 엔진을 얹어 쓰는 게 합리적이다.
Three.js vs Babylon.js: 뭘 선택해야 하나
여기서부터가 실전 질문이다. 둘 다 WebGL/WebGPU를 감싼 3D 엔진이지만 설계 철학이 다르다.
Three.js는 렌더링 라이브러리에 가깝다. 씬 그래프, 카메라, 조명, 머티리얼, 지오메트리 같은 렌더링 핵심 기능은 강력하지만, 물리 엔진이나 오디오 시스템, 애니메이션 스테이트 머신 같은 "게임에 필요한 것들"은 기본 내장이 아니라 별도 라이브러리(cannon-es, Rapier, Howler.js 등)를 조합해서 써야 한다. 대신 그만큼 가볍고, 필요한 것만 골라 쓸 수 있는 유연함이 있다. 커뮤니티가 압도적으로 크고, 예제와 레퍼런스가 인터넷에 가장 많이 쌓여 있는 것도 Three.js의 강점이다. 순수 시각화, 제품 쇼케이스, 인터랙티브 아트워크처럼 "게임까지는 아니지만 3D가 필요한" 프로젝트에는 Three.js가 과할 정도로 잘 맞는다.
Babylon.js는 처음부터 게임 엔진을 표방하며 만들어졌다. 물리 엔진 통합(Havok, Cannon, Ammo 선택 가능), 스켈레탈 애니메이션, 파티클 시스템, 오디오 엔진, GUI 시스템, 심지어 씬을 시각적으로 편집할 수 있는 Node Material Editor 같은 툴까지 프레임워크 자체에 포함돼 있다. "게임에 필요한 걸 다 갖춘 배터리 포함형(batteries-included)" 엔진이라 초기 셋업 속도가 빠르다. 대신 그만큼 번들 크기가 크고, 프레임워크가 정한 구조를 따라가야 하는 부분이 Three.js보다 많다.
학습 곡선으로 보면 둘 다 3D 그래픽 기본 개념(좌표계, 카메라, 조명 모델)을 알아야 하는 건 동일하지만, "물리와 충돌까지 포함한 완결된 미니 게임"을 가장 빨리 완성하고 싶다면 Babylon.js 쪽이 초반 진입 장벽이 낮다. 반면 렌더링 파이프라인을 세밀하게 커스터마이징하고 싶거나, 셰이더 레벨에서 직접 제어하고 싶은 프로젝트라면 Three.js가 더 손에 붙는다.
WebGPU 지원 측면에서는 둘 다 적극적으로 대응하고 있다. Three.js는 기존 WebGLRenderer와 별도로 WebGPURenderer를 제공하며, TSL(Three.js Shading Language)이라는 노드 기반 셰이더 추상화 레이어를 통해 같은 셰이더 코드를 GLSL(WebGL용)과 WGSL(WebGPU용) 양쪽으로 컴파일할 수 있게 만들어뒀다. 즉 셰이더를 한 번 작성해두면 렌더러 종류에 관계없이 재사용할 수 있는 구조를 지향한다. Babylon.js도 별도의 WebGPU 엔진 구현을 제공하고, 기존 API 표면을 최대한 유지하면서 내부적으로 WebGPU 백엔드로 전환할 수 있게 설계돼 있다. 어느 쪽이든 아직은 WebGPU 전용 최신 기능(레이트레이싱 확장 등)까지 완전히 성숙했다고 보기는 이르고, WebGL 백엔드가 여전히 기본값이자 가장 안정적인 선택지라는 점은 공통이다.
정리하면: 물리·오디오·GUI까지 프레임워크 하나로 빠르게 게임을 완성하고 싶다 → Babylon.js. 렌더링을 세밀하게 통제하고 싶고, 커뮤니티 자료와 레퍼런스를 많이 참고하고 싶다 → Three.js. 팀에 3D 경험이 없고 최대한 빨리 프로토타입을 뽑아야 한다 → Babylon.js 쪽이 초반 속도가 빠른 편이다.
Three.js로 만드는 최소 3D 씬
WebGL 기반 WebGLRenderer로 회전하는 큐브를 띄우는 가장 기본적인 예제는 이렇다.
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x4fc3f7 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(3, 3, 5);
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
MeshStandardMaterial은 물리 기반 렌더링(PBR) 머티리얼이라 조명을 받아야 형태가 드러난다. 그래서 DirectionalLight(태양광처럼 한 방향에서 오는 빛)와 AmbientLight(전역적으로 은은하게 깔리는 빛)를 같이 넣었다. 조명 없이 큐브만 씬에 넣으면 새까맣게 보이는 게 PBR 머티리얼을 처음 쓸 때 가장 흔히 겪는 함정이다.
WebGPURenderer로 바꾸고 싶다면 렌더러 생성부만 교체하면 된다.
import * as THREE from 'three';
import WebGPU from 'three/addons/capabilities/WebGPU.js';
import { WebGPURenderer } from 'three/webgpu';
if (WebGPU.isAvailable() === false) {
console.warn('WebGPU를 지원하지 않아 WebGL로 폴백합니다.');
}
const renderer = new WebGPURenderer({ antialias: true });
await renderer.init(); // WebGPURenderer는 비동기 초기화가 필요하다
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
WebGPURenderer는 내부적으로 디바이스와 컨텍스트를 비동기로 준비하기 때문에 init()을 await 해줘야 한다는 점이 WebGLRenderer와의 실질적인 코드 차이다. 씬 구성 코드(지오메트리, 머티리얼, 조명, 애니메이션 루프)는 거의 그대로 재사용된다.
three js 전망, 솔직하게
"three js 전망"을 검색하는 사람들이 궁금한 건 대체로 두 가지다. 지금 배워도 되는가, 그리고 WebGPU 시대에도 살아남을 것인가. 둘 다 답은 긍정적이다.
Three.js는 10년 넘게 웹 3D 생태계의 사실상 표준 역할을 해왔고, 그 위에 React Three Fiber 같은 프레임워크 통합, 수많은 튜토리얼과 예제, npm 생태계 전반의 호환성이 쌓여 있다. WebGPU로의 전환도 기존 API를 버리는 방식이 아니라 WebGLRenderer와 WebGPURenderer를 공존시키고 TSL로 셰이더 코드를 공유하는 점진적 전략을 택했기 때문에, 지금 Three.js를 배워둔 지식과 코드 자산이 WebGPU 시대에도 대부분 그대로 이어진다. 즉 "WebGPU가 대세가 되면 Three.js가 쓸모없어지는" 구조가 아니라, Three.js 안에서 렌더러만 바뀌는 구조다.
다만 냉정하게 볼 지점도 있다. 브라우저 3D는 여전히 네이티브 게임 엔진(Unity, Unreal, Godot) 대비 툴체인이 빈약하고, 대규모 3D 게임을 웹에서 완성도 있게 서비스하는 사례는 아직 적다. Three.js/Babylon.js의 강점은 "가볍고 접근성 높은 3D 인터랙션"이지, AAA급 3D 게임 엔진을 대체하는 포지션이 아니다. 제품 쇼케이스, 캐주얼 3D 미니게임, 데이터 시각화, 웹 기반 에디터처럼 "브라우저에서 바로 실행되고 설치 없이 공유 가능하다"는 웹의 강점을 살리는 영역에서는 앞으로도 수요가 꾸준히 늘어날 걸로 보는 게 합리적이다.
결론: 뭐부터 시작할까
지금 3D 웹게임을 만들기 시작한다면 이렇게 접근하는 걸 추천한다. 첫째, 렌더러는 WebGL을 기본값으로 잡고 시작한다. 브라우저 호환성 문제로 막히는 시간을 아낄 수 있다. 둘째, 물리와 오디오까지 빠르게 갖춘 결과물이 필요하면 Babylon.js, 렌더링을 세밀하게 통제하고 싶거나 커뮤니티 자료를 많이 참고하고 싶으면 Three.js를 고른다. 셋째, WebGPU는 "성능이 실제로 병목인 게 확인된 다음"에 렌더러를 교체하는 방향으로 접근한다. 둘 다 WebGL과 WebGPU 렌더러를 API 차원에서 공존시키도록 설계돼 있어서, 나중에 옮기는 비용이 생각보다 크지 않다.
성능이 실제로 문제가 될 때 확인할 것들
3D 웹게임에서 프레임이 떨어지는 원인은 대개 GPU가 아니라 CPU 쪽 드로우콜 오버헤드인 경우가 많다. 오브젝트마다 개별 드로우콜을 발생시키는 구조라면 오브젝트 수가 몇백 개만 넘어가도 병목이 생긴다. 이럴 때 가장 먼저 시도할 최적화는 렌더러 교체가 아니라 인스턴싱이다. Three.js라면 InstancedMesh로 동일한 지오메트리/머티리얼을 가진 오브젝트 여러 개를 드로우콜 하나로 묶을 수 있다.
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshStandardMaterial({ color: 0xffaa00 });
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(
(Math.random() - 0.5) * 50,
Math.random() * 10,
(Math.random() - 0.5) * 50
);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedMesh);
이렇게 인스턴싱만 적용해도 오브젝트 1000개를 드로우콜 하나로 그릴 수 있어서, WebGL 상태에서도 상당한 오브젝트 수를 감당할 수 있게 된다. WebGPU로 넘어가는 건 여기서 더 나아가 파티클 수만 개를 GPU 컴퓨트 셰이더로 직접 시뮬레이션해야 하는 시점, 즉 CPU에서 위치를 계산해 GPU로 매 프레임 업로드하는 것 자체가 병목이 되는 시점에 의미가 커진다.
렌더러 선택과 별개로 챙겨야 할 것들
엔진과 렌더러를 정했다고 끝나는 게 아니다. 3D 웹게임에서 실제로 사용자 경험을 좌우하는 요소는 따로 있다.
에셋 로딩 전략. 3D 모델(glTF), 텍스처, 사운드는 용량이 커서 첫 로딩에 시간이 걸린다. 로딩 화면 없이 바로 큰 씬을 불러오면 사용자가 이탈하기 쉽다. Three.js의 LoadingManager나 Babylon.js의 AssetsManager로 진행률을 표시하고, 필요하다면 저해상도 텍스처를 먼저 보여준 뒤 고해상도로 교체하는 점진적 로딩을 고려하는 게 좋다.
입력 디바이스 다양성. 데스크톱은 키보드/마우스, 모바일은 터치, 콘솔형 컨트롤러까지 고려해야 할 수 있다. 웹 표준 Gamepad API로 컨트롤러 입력을 받을 수 있고, 두 엔진 모두 포인터락(pointer lock) API를 감싼 1인칭 카메라 컨트롤을 기본 제공한다.
모바일 GPU 성능 편차. 데스크톱에서 60fps로 잘 돌아가던 씬이 저사양 모바일 기기에서는 그림자, 후처리 이펙트 때문에 크게 떨어질 수 있다. 두 엔진 모두 디바이스 성능에 따라 그림자 해상도나 안티앨리어싱을 동적으로 낮추는 품질 조정 로직을 직접 짜거나 내장 유틸리티를 활용하는 걸 권장한다.
이런 요소들은 WebGL이냐 WebGPU냐, Three.js냐 Babylon.js냐를 정하는 것보다 실제 완성도에 미치는 영향이 훨씬 크다. 렌더링 API 선택은 시작점일 뿐이고, 진짜 승부는 그 위에서 에셋과 입력, 성능 튜닝을 얼마나 꼼꼼히 챙기느냐에서 갈린다.