브라우저에서 오픈 월드 만들기, 5부: 예쁜 것들에 예산 배정하기
글 Oleg Sidorkin, Cinevva CTO 겸 공동 창업자
처음 오셨나요? 시리즈 가이드를 보세요. spike가 무엇인지 설명하고 모든 파트를 링크해 둡니다.
이번 장은 시각적 야심이 산수와 부딪힌 장이다.
우리는 렌더링 비용을 별도의 spike로 쪼개서 측정했다. 묶어서 나온 결과는 진단하기 어렵기 때문이다. 한 번에 전부 켜면, 알게 되는 건 딱 하나다. 프레임이 느리다는 것. 어떤 기능이 예산을 먹었는지는 알 수 없다.
Spike 7은 식생 밀도와 애니메이션 비용을 겨냥했다. 방식은 지형 chunk마다 32x32 밀도 맵에서 런타임으로 흩뿌리고, 큰 InstancedMesh 세트에 넣어주는 것이었다. 풀잎 하나하나와 관목 덩어리마다 스크롤하는 노이즈 텍스처로 구동되는 버텍스 셰이더 바람을 입혔다. 우리가 지켜본 핵심 수치는 삼각형 개수가 아니라 중급 GPU에서의 draw call 오버헤드와 버텍스 처리량이었다. 인스턴스를 더 적은 mesh로 batch하는 게 잎 하나당 폴리곤 수를 줄이는 것보다 더 중요하다는 걸 알게 됐다.
Spike 8은 지형 머티리얼의 복잡도를 밀어붙였다. 경사 각도와 고도로 가중치를 준 다층 블렌딩, 절벽 면에 선택적으로 적용하는 triplanar 투영, 그리고 레이어별 노멀 맵. 셰이더는 네 개의 텍스처 레이어로 경사 기반 splatting을 하고 있었고, 각 레이어마다 diffuse와 normal 샘플이 필요했다. 그러니까 조명을 더하기 전에 이미 프래그먼트당
Spike 9는 현실적인 지형과 오브젝트 부하 아래에서의 캐스케이드 섀도우 맵 비용에 집중했다. 세 개의 캐스케이드를 쓰는 CSM이 기준선이었다. 일부러 낮은 각도의 태양 위치로 테스트했는데, 거기가 캐스케이드 압박이 가장 심해지는 지점이기 때문이다. 가장 먼 캐스케이드는 거대한 절두체 조각을 덮고, 섀도우 디테일은 텍셀 밀도
이 단계에서 어려웠던 건 제품 규율이었다. 어떤 효과들은 아주 훌륭해 보였지만, 시각적 영향에 비해 프레임 예산을 너무 많이 먹어서 결국 제약을 걸어야 했다.
우리의 규칙은 단순해졌다. 어떤 기능은 측정된 프레임 시간 데이터로 자기 비용을 설명할 수 있을 때만 앞으로 나아간다.
당연한 얘기처럼 들린다. 하지만 다음 시각적 성과에 다들 들떠 있는 빠른 프로토타입 사이클에서는 흔한 일이 아니다. 이 규칙을 일찍부터 지킨 덕분에 나중에 clipmap과 볼류메트릭 존을 둘러싼 아키텍처 결정이 훨씬 깔끔해졌다. 같은 16ms를 두고 경쟁하는 모든 것의 기능별 비용을 우리가 이미 알고 있었으니까.
6부에서는 geometry clipmap으로 첫 번째 주요 지형 아키텍처 전환을 맞는다.
이 장에서 언급한 기술
InstancedMesh와 GPU 식생. Three.js의 InstancedMesh는 같은 지오메트리의 N개 복사본을 draw call 한 번으로 렌더링한다. 식생의 경우, 밀도 맵 (chunk당 32x32)이 풀잎과 관목 덩어리를 인스턴스 버퍼로 런타임에 흩뿌리는 작업을 구동한다. 바람 애니메이션은 스크롤하는 노이즈 텍스처를 써서 버텍스 셰이더에서 돌아간다. 규모가 커지면, WebGPU의 ComputeInstanceCulling이 래스터화 전에 화면 밖과 먼 거리의 인스턴스를 제거하고, IndirectBatchedMesh가 여러 식생 타입을 하나의 버퍼에 담아 multi-draw indirect로 그린다. GPU 식생 컬링에 관한 지형 가이드를 보라.
Triplanar mapping. 표준 UV 매핑 텍스처는 가파른 경사에서 UV 좌표가 압축되기 때문에 늘어난다. Triplanar mapping은 세 축(X, Y, Z) 모두를 따라 텍스처를 투영하고 표면 노멀을 기준으로 블렌딩한다. 절벽 면은 X 또는 Z 투영(늘어남 없음)을 받고, 평평한 지면은 Y 투영을 받는다. 블렌딩은 부드럽고 자동이며 UV 언랩이 필요 없다. PBR 지형의 경우, 같은 블렌딩 가중치가 albedo, normal, roughness, ambient occlusion 채널에 그대로 적용된다. triplanar mapping 세부 사항을 보라.
경사와 고도 기반 머티리얼 splatting. 손으로 그린 splat 맵 대신, 머티리얼은 지형 속성을 기준으로 프래그먼트 셰이더에서 절차적으로 할당된다. 낮은 고도의 평지는 풀을 받고, 가파른 경사는 바위를, 높은 고도는 눈을(쌓일 만큼 충분히 평평한 표면에서만), 해수면 근처는 모래를 받는다. 전환은 부드러운 블렌딩을 위해 smoothstep을 쓴다. 우리 구현에서는 각 지형 chunk가 네 개의 텍스처 레이어를 레이어당 diffuse와 normal 샘플로 평가하므로, 조명 전에 프래그먼트당 여덟 번의 텍스처 페치가 일어난다. 경사와 고도 기반 머티리얼 할당을 보라.
캐스케이드 섀도우 맵 (CSM). CSM은 카메라의 뷰 절두체를 3~4개의 거리 구간(캐스케이드)으로 쪼갠다. 각 캐스케이드는 그 거리에 맞춘 해상도로 태양 시점에서 섀도우 맵을 렌더링한다. 가까운 캐스케이드는 고해상도 섀도우(나무와 건물 아래의 정밀한 접촉 섀도우)를 받고, 먼 캐스케이드는 낮은 해상도(넓은 산 그림자)를 받는다. 지형 셰이더는 모든 캐스케이드를 샘플링하고 프래그먼트마다 적절한 것을 고른다. 성능 비용: 1024x1024로 3~4개 캐스케이드는 섀도우 맵 렌더링에 약 0.5~1 ms, 샘플링에 약 0.2~0.3 ms를 더한다. 지형 섀도우를 보라.
12부 중 5부.
이전: 4부 - 화려한 지형보다 먼저 스트리밍
다음: 6부 - Clipmap이 줄거리를 바꿨다
시리즈 가이드: /ko/blog/2026-02-25-open-world-browser-series-guide