3D 브러시 기법과 게임 내 월드 조형
우리는 플레이어가 월드를 직접 조형할 수 있기를 바랍니다. 그리드에 프리팹을 배치하거나 블록을 켜고 끄는 정도가 아닙니다. 강을 파고, 산을 솟아오르게 하고, 절벽면을 매끄럽게 다듬고, 동굴을 뚫는 등 지형 자체를 실제로 변형하는 것입니다. ZBrush와 Blender의 조형 모드가 아티스트에게 제공하는 작업을 멀티플레이어 브라우저 게임 안에서 60fps로 실행하는 셈입니다.
이는 데이터 표현, 메시 추출, GPU 연산, 브러시 수학, 네트워크 동기화를 모두 아우르는 어려운 엔지니어링 문제입니다. 이 가이드에는 저희가 알아낸 모든 내용을 정리했습니다.
지형 표현의 두 가지 방식
모든 조형 시스템은 지형 데이터를 저장하는 방식을 선택하는 것에서 시작합니다. 이 선택에 따라 가능한 편집의 종류, 실행 속도, 메모리 비용이 결정됩니다.
하이트맵
하이트맵은 각 그리드 지점마다 하나의 높이 값을 저장합니다. 밝기가 고도를 나타내는 회색조 이미지라고 생각할 수 있습니다. 현재 저희의 world/client 지형도 정확히 이 방식으로 작동합니다. noise.ts는 FBM 값 노이즈로 높이를 생성하고, 각 Chunk는 PlaneGeometry에 투영되는 Float32Array 하이트맵을 저장합니다.
하이트맵은 빠릅니다. 샘플링에는 이중 선형 보간을 포함한 단 한 번의 배열 조회만 필요합니다. 그리드 해상도만 낮추면 되므로 LOD도 매우 간단합니다. 스플랫 기반 텍스처 블렌딩은 UV 그리드에 직접 대응합니다. 물리 충돌도 높이 조회 문제로 단순화됩니다.
한계는 토폴로지입니다. 하이트맵은 각 (x, z) 좌표마다 하나의 높이만 표현할 수 있습니다. 동굴, 돌출부, 아치, 터널은 표현할 수 없습니다. 플레이어가 뒤쪽으로 휘어 들어가는 절벽을 조형해도 하이트맵에는 이를 저장할 수 없습니다. 완만한 구릉과 산이 대부분인 지형이라면 괜찮습니다. 하지만 플레이어가 땅속을 파고들 수 있는 자유형 조형에는 막다른 길입니다.
볼류메트릭 표현(3D 스칼라 필드)
대안은 3D 공간의 모든 지점에 값을 저장하는 것입니다. 단단한 물질 내부의 값이 음수이고 외부가 양수라면(또는 그 반대라면) 부호 거리장(Signed Distance Field, SDF)입니다. 값이 단순한 밀도이고 특정 임곗값보다 높으면 고체, 낮으면 빈 공간으로 취급한다면 밀도 필드입니다.
볼류메트릭 표현은 어떤 토폴로지도 처리할 수 있습니다. 동굴, 돌출부, 공중에 떠 있는 섬, 산을 관통하는 터널도 가능합니다. 대신 메모리와 복잡성 측면에서 비용이 큽니다. 32비트 부동소수점으로 구성된 256^3 그리드는 64MB, 512^3 그리드는 512MB를 차지합니다. 이것도 청크 하나에 필요한 용량입니다. 이를 실용적으로 구현하려면 옥트리나 브릭 맵 같은 희소 데이터 구조가 필요합니다.
메시 추출 단계 역시 간단하지 않습니다. 버텍스의 Y 위치만 설정하고 끝낼 수 없습니다. 스칼라 필드를 읽고 필드가 0을 가로지르는 표면을 근사하는 삼각형 메시를 생성하는 알고리즘이 필요합니다.
메시 추출: 마칭 큐브, 서피스 넷, 듀얼 컨투어링
마칭 큐브
마칭 큐브(Marching Cubes)는 가장 오래되고 가장 널리 구현된 등가면 추출 알고리즘입니다. 1987년 Lorensen과 Cline이 발표했으며, 각 모서리에 스칼라 값이 있는 복셀 그리드의 모든 큐브를 검사하는 방식으로 작동합니다. 일부 모서리가 표면 내부(음수)에 있고 나머지는 외부(양수)에 있으면 해당 큐브 안에 삼각형 메시 패치를 배치합니다.
각 큐브에는 8개의 모서리가 있고 각각은 내부 또는 외부이므로 256가지 구성(2^8)이 가능합니다. 대칭성을 적용하면 15가지 고유 사례로 줄어듭니다. 조회 테이블은 각 사례를 삼각형 집합에 매핑합니다. 부호가 바뀌는 에지를 따라 선형 보간하여 에지 교차점을 찾습니다.
최근 GPU 구현 덕분에 마칭 큐브는 실시간 조형에 충분히 빠르게 실행됩니다. 2025년에 공개된 UE5 구현은 각 GPU 스레드에 큐브 하나를 할당해 수천 개를 동시에 처리합니다. 핵심은 각 큐브의 삼각분할이 이웃 큐브와 독립적이어서 알고리즘을 매우 손쉽게 병렬화할 수 있다는 점입니다.
MCHex(arxiv 2511.02064, 2025)는 마칭 큐브를 양의 야코비안 값이 보장되는 적응형 육면체 메시 생성으로 확장하여 시뮬레이션 메시의 경계 근사를 개선합니다.
rupMC는 CPU/GPU 이기종 아키텍처를 사용해 직렬 구현보다 수십 배, 병렬 DMC 변형보다 4배 빠른 성능을 달성합니다.
주요 한계는 마칭 큐브가 날카로운 형상을 처리하는 데 약하다는 점입니다. 90도 에지가 부드러운 곡선으로 둥글게 변합니다. 자연 지형은 대부분 매끄러우므로 지형 조형에서는 대체로 허용할 수 있지만, 건축 구조물을 만들 때는 문제가 됩니다.
서피스 넷
서피스 넷(Surface Nets)은 이산 스칼라 필드에서 더 매끄러운 메시를 생성하는 최신 알고리즘 계열입니다. 마칭 큐브처럼 큐브의 에지에 버텍스를 배치하는 대신, 표면이 포함된 큐브마다 버텍스 하나를 배치한 다음 인접한 버텍스를 연결해 사각형을 만듭니다.
결과물은 본질적으로 더 매끄럽습니다. 2024년 논문(arxiv 2401.14906)에서는 순차 알고리즘보다 한두 자릿수 배 빠르게 실행되는 고성능 병렬 서피스 넷 구현을 선보였습니다. Rust 크레이트 fast-surface-nets는 작은 조회 테이블과 SIMD 가속을 사용해 단일 2.5GHz 코어에서 초당 약 2천만 개의 삼각형을 생성합니다.
bevy-sculpter(v0.18.0, 2026년 1월)는 서피스 넷을 기본 메시 생성 전략으로 사용합니다. 이 크레이트는 SDF 기반 볼류메트릭 조형과 네 가지 브러시 유형을 제공합니다. 즉시 추가하거나 제거하는 하드 CSG, 입력을 누르고 있는 동안 사용하는 매끄러운 연속 브러시, 표면을 매끄럽게 만드는 블러, 목표 높이로 설정하는 평탄화 브러시입니다. 또한 편집 후 올바른 부호 거리장 특성을 복원하기 위해 Fast Sweeping Method를 통한 SDF 거리 재초기화도 포함합니다.
서피스 넷은 단순하고 빠르지만 이진 데이터에서 계단 현상이 있는 메시를 생성하는 마칭 큐브와, 형상을 보존하지만 복잡한 듀얼 컨투어링 사이에서 좋은 절충안입니다.
듀얼 컨투어링
듀얼 컨투어링(Dual Contouring)은 마칭 큐브와 서피스 넷으로는 표현하기 어려운 날카로운 형상을 보존합니다. 각 모서리에서 필드의 부호뿐 아니라 에지 교차점의 그래디언트(노멀)도 사용합니다. QEF(Quadratic Error Function, 이차 오차 함수) 최소화를 통해 모든 에지 교차 제약 조건을 가장 잘 충족하는 셀 내부 위치에 버텍스를 배치합니다.
그 결과 추출된 메시에서 날카로운 에지와 모서리가 보존됩니다. 수직으로 맞닿는 면을 가진 큐브는 계속 큐브 형태를 유지합니다.
대신 복잡성이 높습니다. QEF 계산에는 셀 사이의 종속성이 있어 GPU 병렬화가 어렵습니다. 두 개가 넘는 폴리곤이 하나의 에지를 공유하는 비다양체 메시가 생성될 수도 있습니다. 구현도 마칭 큐브보다 까다롭습니다. 다만 Johannes Jendersie에 따르면 작동 가능한 듀얼 컨투어링 구현은 약 200줄인 반면, 견고한 마칭 큐브 구현은 500줄 이상입니다.
**Cubical Marching Squares(CMS)**는 중간 지점의 대안으로 제안되었습니다. 셀 간 독립적이어서 GPU에 적합하면서도 어느 정도 형상을 보존합니다.
무엇을 선택해야 하는가
브라우저에서 플레이어가 사용하는 지형 조형 기능을 만든다면 다음과 같이 선택할 수 있습니다.
서피스 넷이 가장 유력한 후보입니다. 이진 데이터에서 마칭 큐브가 만드는 계단 현상 없이 매끄럽고 자연스러운 지형 메시를 생성합니다. 실시간 메시 재생성에도 충분히 빠르며, 듀얼 컨투어링보다 구현이 간단합니다.
마칭 큐브는 GPU 병렬 처리가 최우선이거나(모든 큐브가 독립적임) 가장 광범위한 라이브러리 지원이 필요할 때 여전히 탄탄한 선택입니다. Reinder Nijhoff가 2026년 1월 공개한 WebGPU SDF Editor는 추출 파이프라인에 마칭 큐브와 서피스 넷을 모두 구현했으며, 전체 작업을 GPU에서 실행합니다.
듀얼 컨투어링은 성능보다 건축적 정밀도가 더 중요한 경우에 사용하는 것이 가장 좋습니다. 브라우저 환경의 실시간 지형 조형에는 적합하지 않습니다.
트랜스복셀 알고리즘: LOD 연결 문제 해결
복셀 지형을 서로 다른 해상도로 메시화하면(플레이어 근처는 LOD0, 멀리 떨어진 곳은 LOD2) 경계에 틈이 생깁니다. 하이트맵에서는 이 문제가 간단합니다. 에지 버텍스를 보간해 해상도가 더 낮은 이웃과 맞추면 됩니다. 현재 저희의 chunk.ts도 stitchEdge()에서 정확히 이 작업을 수행합니다.
볼류메트릭 지형에서는 문제가 훨씬 어렵습니다. LOD0의 동굴 입구 경계에는 삼각형 30개가 생성될 수 있습니다. LOD1에서 같은 영역은 완전히 다른 토폴로지의 삼각형 8개를 생성할 수 있습니다. 두 결과를 선형으로 보간하는 간단한 방법은 없습니다.
Eric Lengyel의 트랜스복셀 알고리즘(2009)은 이 문제를 "전환 셀"로 해결합니다. 두 LOD 레벨의 경계에서 알고리즘은 큐브 모서리 8개 대신 고해상도 샘플 9개를 고려합니다. 그 결과 가능한 구성 512개가 생성되며, 이는 73개의 동치 클래스로 나뉩니다. 각 클래스는 두 해상도 사이의 틈을 완벽하게 채우는 사전 정의된 삼각형 패턴에 매핑됩니다.
이 알고리즘은 로컬 복셀 데이터에서 작동하므로 수정된 영역의 삼각분할을 빠르게 다시 수행할 수 있습니다. 이는 실시간 조형에 매우 중요합니다. 플레이어가 LOD 경계 근처의 지형을 편집할 때 전환 셀만 다시 빌드하면 됩니다.
Rust 구현은 transvoxel 크레이트로 제공됩니다. 원본 조회 테이블은 transvoxel.org에서 확인할 수 있습니다.
브러시 수학
조형 브러시는 대상 지점 주변의 일정 반경 안에서 스칼라 필드 값을 수정하는 함수입니다. Blender와 Unreal Engine부터 런타임 게임 시스템까지, 모든 구현에서 사용되는 수학은 놀라울 정도로 비슷합니다.
감쇠 함수
브러시 감쇠는 편집 강도가 중심에서 가장자리로 갈수록 얼마나 감소하는지를 결정합니다. Blender 5.1은 다음과 같은 표준 프로필을 정의합니다.
부드러움: f(d) = 3d^2 - 2d^3(에르미트 보간, 저희의 noise.ts와 동일한 smoothstep)
구형: 중심에서는 강하고 경계 근처에서 급격히 감쇠합니다. f(d) = sqrt(1 - d^2)로 근사합니다.
날카로움: f(d) = (1 - d)^n, 여기서 n > 2입니다. 뾰족한 끝을 만듭니다.
선형: f(d) = 1 - d, 여기서 d는 중심으로부터 정규화된 거리입니다. 중심에서는 0, 가장자리에서는 1입니다.
상수: d < 1일 때 f(d) = 1이며, 브러시 경계에서 값이 즉시 끊깁니다.
역제곱: 자연스러운 "점토 같은" 느낌을 내기 위해 부드러움과 구형을 결합한 방식입니다.
모든 경우에 d = distance_to_center / brush_radius이며 [0, 1] 범위로 제한됩니다. 감쇠 값에 브러시 강도를 곱하면 각 지점에 적용할 실제 필드 수정량이 나옵니다.
감쇠 공간
Blender는 구형 감쇠(3D 월드 공간에서 계산한 거리)와 투영 감쇠(2D 화면 공간에서 계산한 거리)를 구분합니다. 투영 감쇠에서는 월드 공간상 깊이가 크게 다르더라도 화면에서 서로 가까워 보이는 두 지점이 동일한 영향을 받습니다. 지형 조형에는 일반적으로 3D 월드 공간 감쇠가 더 직관적입니다.
핵심 브러시 연산
올리기/내리기(변위): 브러시 반경 내의 스칼라 필드에 감쇠로 가중한 값을 더하거나 뺍니다. 하이트맵에서는 height[i] += strength * falloff(d)입니다. SDF에서는 sdf[i] -= strength * falloff(d)입니다. 값을 빼면 물질이 더 단단해져 표면이 올라옵니다.
매끄럽게 하기(라플라시안): 감쇠로 가중하여 각 값을 이웃 값의 평균으로 대체합니다. 이를 통해 디테일이 지워지고 노이즈가 줄어듭니다. 라플라시안 필터는 작은 커널(하이트맵은 3x3, 볼륨은 3x3x3)을 샘플링하고 평균값 쪽으로 블렌딩합니다. HC(Humphrey's Classes) 스무딩은 기본 라플라시안보다 부피를 더 잘 보존하는 변형입니다.
평탄화: 감쇠를 적용해 필드 값을 목표 높이 또는 SDF 공간의 거리로 설정합니다. 일반적으로 스트로크를 시작할 때 브러시 중심에서 목표값을 샘플링한 뒤 일정하게 유지합니다. 이를 통해 평평한 고원을 만들 수 있습니다.
집기/부풀리기: 버텍스를 표면 노멀 방향으로 이동시키거나 반대 방향으로 이동시킵니다. SDF 공간에서는 그래디언트 방향을 따라 변위를 적용하는 것과 같습니다.
당기기: 점토를 잡아당기듯 필드의 한 영역을 이동합니다. 마우스 이동량을 월드 공간에 투영한 변위 벡터를 반경 내 필드 값에 적용합니다.
노이즈: 브러시 반경 내의 필드에 절차적 노이즈를 추가합니다. 매끄러운 표면을 거칠게 만드는 데 유용합니다.
스탬프: 커서 아래의 표면에 2D 회색조 이미지를 투영하여 변위로 적용합니다. Unreal Engine의 Landscape 도구는 지형 브러시에 이 기능을 지원합니다.
적응형 테셀레이션
sculpt-3D(React + Three.js 브라우저 조형)는 적응형 테셀레이션을 구현합니다. 브러시가 메시 위를 이동하면 브러시 중심 근처의 삼각형을 세분화하여 변형에 사용할 버텍스를 더 많이 확보합니다. 이를 통해 성긴 메시가 조형 과정에서 늘어지는 "로우 폴리 스트레칭" 문제를 방지합니다. 세분화에는 균일한 삼각형 품질을 위한 대칭 분할이 사용됩니다.
볼류메트릭 시스템에서는 필드에서 메시를 다시 생성하므로 같은 방식의 적응형 테셀레이션이 필요하지 않습니다. 대신 편집 지점 근처의 복셀 해상도를 국소적으로 높이는 적응형 옥트리를 사용해 같은 효과를 얻을 수 있습니다.
SDF 조형: Dreams의 접근 방식
Media Molecule의 Dreams(PS4, 2020)는 지금까지 출시된 게임 내 조형 시스템 중 가장 야심 찬 사례입니다. Alex Evans는 SIGGRAPH 2015에서 그 기술적 접근 방식을 발표했습니다.
표현 방식
Dreams는 83^3 fp16 볼륨 텍스처 블록에 복합 SDF 함수 형태로 지오메트리를 저장합니다. 각 조형물은 1개에서 100,000개의 "편집" 목록으로 구성됩니다. 각 편집은 기본 도형(구, 큐브, 원기둥, 원뿔, 타원체, 토러스 등) 및 블렌드 모드가 지정된 CSG 연산(추가, 빼기, 색상 적용)입니다. 블렌드 모드는 소프트 최대 및 소프트 최소 함수를 사용합니다. "소프트" 블렌드는 프리미티브 사이에 둥근 전환을 만듭니다(찰흙을 서로 밀어 붙인 듯한 형태). "하드" 블렌드는 선명한 불리언 절단면을 만듭니다. 블렌드 반경은 사용자가 조절할 수 있습니다.
렌더링
Dreams는 삼각형 메시를 추출하지 않습니다. 대신 커스텀 포인트 클라우드 렌더러("flecks")를 사용해 SDF에서 직접 렌더링합니다. 각 fleck은 표면 노멀 방향으로 정렬된 작은 원판입니다. SDF를 샘플링해 표면을 찾고, 그 위에 fleck을 분포시킵니다. 이 방식은 메시 추출 병목을 완전히 피하지만 커스텀 렌더러가 필요합니다.
Three.js/WebGL 월드에는 이 방식을 직접 적용할 수 없습니다. 메시를 추출해야 하기 때문입니다. 하지만 CSG 편집 목록 개념은 실행 취소/다시 실행과 네트워크 동기화에 매우 유용합니다.
Mike Turitzin의 동적 SDF 엔진(2026년)
현재 Mike Turitzin이 개발 중인 게임 엔진은 동적 SDF를 핵심 표현 방식으로 사용합니다. 이 엔진은 다음 기능을 지원합니다.
게임플레이 중 세밀한 수정: 물질을 부드럽게 또는 날카로운 모서리로 추가하고 제거할 수 있습니다. 구멍을 옮기거나 플레이어가 지나간 뒤 사라지는 임시 터널을 만드는 것과 같은 비파괴 변경도 가능합니다.
희소 캐싱을 위한 브릭 맵과 브릭 아틀라스. 전체 SDF 필드를 조밀한 3D 그리드에 저장하는 대신, 필드를 "브릭"(작은 3D 타일)으로 나눕니다. 표면 경계가 포함된 브릭만 할당합니다. 대부분이 빈 공간이거나 단단한 공간인 장면에서 메모리 사용량을 크게 줄일 수 있습니다.
LOD를 위한 지오메트리 클립맵(Losasso & Hoppe, SIGGRAPH 2004). 해상도가 단계적으로 달라지는 중첩된 정규 그리드가 카메라 위치를 둘러쌉니다. 가장 안쪽 그리드의 해상도가 가장 높고, 바깥쪽 그리드로 갈수록 점차 낮아집니다. 이를 통해 광대한 공간을 지원하면서 메모리 사용량을 크게 줄일 수 있습니다. 클립맵은 카메라가 움직일 때 점진적으로 업데이트되므로 오픈 월드 스트리밍에 효율적입니다.
물리와 충돌은 SDF를 대상으로 직접 처리됩니다. 구체 추적(SDF 거리를 스텝 크기로 사용하는 레이 마칭)은 효율적인 레이캐스팅을 제공합니다. 충돌 감지는 SDF 그래디언트를 표면 노멀로, 거리 값을 침투 깊이로 사용합니다.
Teardown: 대규모 복셀 파괴
Teardown(Voxagon)은 스펙트럼의 반대편을 보여 줍니다. 월드의 모든 오브젝트가 조각 단위로 파괴할 수 있는 복셀 볼륨으로 표현됩니다.
아키텍처
오브젝트는 일정한 간격의 복셀 그리드로 저장됩니다. 이 엔진은 렌더링에 Marching Cubes나 SDF를 사용하지 않습니다. 대신 OpenGL 3.3을 기반으로 프래그먼트 셰이더에서 수정된 DDA(Digital Differential Analyzer) 알고리즘을 사용해 복셀을 직접 레이 트레이싱합니다. 밉맵은 레이 교차 검사 중 빈 공간을 빠르게 통과하기 위한 조밀한 옥트리 구조를 형성합니다.
엔진은 각 오브젝트의 방향성 경계 상자(OBB)를 래스터화하고, 그 안으로 레이를 추적해 복셀 교차점을 찾습니다. OBB의 뒷면만 렌더링하므로 카메라가 경계 볼륨 안으로 들어갈 수 있습니다.
파괴 동기화(멀티플레이어)
Teardown의 2026년 3월 멀티플레이어 업데이트는 준결정론적 방식을 사용합니다. 구조적 파괴(구멍 뚫기, 소유권 변경, 조인트 재연결)는 신뢰성 있는 네트워크 스트림에서 고정소수점 정수 연산으로 처리됩니다. 모든 클라이언트가 동일한 결정론적 명령을 실행하여 같은 월드 상태에 도달합니다. 비구조적 변경(잔해, 파티클)은 비신뢰성 상태 동기화를 사용합니다.
이는 멀티플레이어 월드에 중요한 시사점을 줍니다. 지형 편집은 결정론적이어야 합니다. 플레이어 A가 산을 조각하면 모든 클라이언트가 동일한 필드 데이터로부터 동일한 메시를 생성해야 합니다. 최종 메시가 아니라 편집 명령(브러시 위치, 반경, 강도, 연산 유형)이 권위 데이터가 되어야 합니다.
ALICE-SDF: 압축과 CSG 트리
ALICE-SDF(Adaptive Lightweight Implicit Compression Engine, v1.3.0 2026년 3월)는 폴리곤 메시보다 10~1,000배 높은 압축률을 제공하는 SDF 기반 공간 데이터의 Rust 구현체입니다. 다음 기능을 지원합니다.
126개의 구성 요소: 프리미티브 72개, 연산 24개, 변환 7개, 모디파이어 23개입니다. 부드러운 블렌딩 연산(합집합, 차집합, 교집합)과 하드 에지 베벨 및 계단식 CSG 전환을 위한 모따기 및 계단 블렌드를 제공합니다.
실행 취소/다시 실행과 네트워크 동기화를 위한 CSG 트리 diff/patch. 이는 멀티플레이어 조각의 핵심 기능입니다. 전체 필드 상태를 전송하는 대신 두 CSG 트리 사이의 구조적 diff를 전송합니다. 클라이언트는 패치를 적용해 새로운 상태를 재구성합니다. 원시 복셀 데이터를 델타 압축하는 것보다 훨씬 효율적으로 대역폭을 사용할 수 있습니다.
항등 변환 제거, 중첩 변환 병합, 모디파이어 강등을 포함한 CSG 트리 최적화. 편집이 누적되어도 트리를 간결하게 유지합니다.
Marching Cubes와 Dual Contouring을 모두 활용한 메시 생성. 물리 충돌 감지는 SDF를 대상으로 직접 작동합니다.
WebAssembly 지원을 통해 브라우저에 통합할 수 있습니다. 엔진은 WASM 바인딩을 제공하는 Rust로 작성되어 Three.js 애플리케이션에 적용할 수 있는 현실적인 선택지입니다.
지형을 위한 WebGPU 컴퓨트
2025년 말 기준으로 WebGPU는 모든 주요 브라우저에서 사용할 수 있습니다. Chrome 113+, Edge 113+, Firefox 141+, Safari 26+에서 모두 기본 활성화된 상태로 제공됩니다. 이에 따라 이전에는 GPU 전용이었던 컴퓨트 셰이더 파이프라인을 사용할 수 있게 되었습니다.
성능
WebGPU 컴퓨트 셰이더는 대규모 병렬화를 통해 CPU 방식보다 약 100배 빠르게 지형을 생성합니다. GPU는 수천 개의 계산을 동시에 실행하며, 지형 생성은 거의 전적으로 병렬 처리할 수 있습니다(각 버텍스/복셀이 독립적임).
작업은 세 단계로 구성됩니다. 디스패치 수준(GPU 전체의 워크로드 분배), 워크그룹 수준(처리 장치 내의 공유 메모리), 스레드 수준(개별 계산)입니다. 셰이더 언어로는 WGSL(WebGPU Shading Language)을 사용합니다.
실시간 지형 조각 파이프라인
WebGPU 조각 파이프라인은 다음과 같이 구성할 수 있습니다.
브러시 적용(컴퓨트 셰이더): 브러시 반경 내의 스칼라 필드 값을 업데이트합니다. 각 스레드가 복셀 하나를 처리합니다. 유니폼 버퍼에서 브러시 매개변수(위치, 반경, 강도, 폴오프 유형, 연산)를 읽어 수정 사항을 적용합니다.
메시 추출(컴퓨트 셰이더): 수정된 영역에서 Surface Nets 또는 Marching Cubes를 실행합니다. Nijhoff의 WebGPU SDF Editor는 이를 다단계 파이프라인으로 구현합니다. 16,384개 셀에 걸친 공간 분할, 옥트리 기반 셀 분할, 표면 추출 순으로 진행됩니다.
버텍스 버퍼 업데이트(GPU 측): CPU 메모리를 왕복하지 않고 추출된 버텍스를 렌더 버퍼에 직접 씁니다.
노멀 계산(컴퓨트 셰이더): 메시 또는 SDF 그래디언트로부터 버텍스 노멀을 계산합니다.
렌더링(표준 파이프라인): 표준 PBR 머티리얼로 메시를 그립니다.
1~4단계는 데이터를 JavaScript로 반환하지 않고 모두 GPU에서 실행할 수 있습니다. CPU는 매 프레임 브러시 매개변수만 전송하면 됩니다.
WebGPU SDF Editor
Reinder Nijhoff의 WebGPU SDF Editor(2026년 1월)는 Chrome에서 실행되는 이 접근 방식을 보여 줍니다. 프리미티브 6개(원뿔, 원기둥, 캡슐, 토러스, 상자, 구), 블렌드 연산 3개(합집합, 차집합, 교집합), 설정 가능한 부드러운 블렌딩, 계층형 씬 그래프를 지원합니다. 각 프리미티브는 단일 GPU 버퍼에서 112바이트를 차지합니다.
렌더링 파이프라인은 앰비언트 오클루전과 시간적 안티앨리어싱을 위해 1,024개의 섀도 맵을 사용합니다. 고성능 GPU에서 인터랙티브 프레임 속도로 실행됩니다.
높이맵 조각: 더 간단한 경로
동굴과 오버행을 지원할 필요가 없다면 높이맵 조각을 사용해 전체 볼류메트릭 파이프라인을 피할 수 있습니다. 출시된 대부분의 게임이 지형 편집을 처리하는 방식입니다.
런타임 구현 패턴
Unity Runtime Terrain(JohannHotzel, 2026년 1월)은 표준 패턴을 보여 줍니다.
- 마우스 위치를 통과하도록 카메라에서 레이캐스트하여 지형과 충돌한 지점을 찾습니다.
- 충돌 지점을 높이맵 좌표로 매핑합니다.
- 폴오프에 따라 가중치를 적용하여 인접한 높이맵 값에 브러시를 적용합니다.
- 수정된 높이맵으로부터 버텍스의 Y 위치를 설정해 메시를 업데이트합니다.
- 새 메시와 일치하도록 물리 콜라이더를 다시 빌드합니다.
Three.js 지형에서는 1~4단계를 기존 아키텍처에 그대로 대응시킬 수 있습니다. Chunk 클래스는 이미 높이맵을 저장하고 이를 바탕으로 메시를 빌드합니다. 조각 기능을 추가하려면 다음이 필요합니다.
- 청크 메시를 대상으로 하는 레이캐스팅 시스템(Three.js
Raycaster) chunk.heightmap값을 수정하는 브러시 적용 함수- 메시 버텍스 업데이트(Y 위치 설정, 노멀 재계산)
- 영향을 받은 영역의 스플랫 맵 재계산(텍스처 블렌딩에 새로운 경사/높이를 반영)
- 기존 WebSocket 프로토콜을 통해 다른 클라이언트에 편집 정보(브러시 위치, 반경, 강도, 연산)를 네트워크로 브로드캐스트
클립맵 접근 방식
Landow.dev는 높이맵 지형을 위한 "이동형 클립맵"을 설명합니다. 플레이어를 따라다니며 세분화 밀도가 달라지는 단일 메시입니다. 높이맵을 LOD 수준이 서로 다른 별도 메시로 청크화하는 대신(현재 사용하는 방식), 클립맵은 카메라 근처에서 조밀하고 가장자리로 갈수록 성긴 연속 메시입니다.
이 방식은 LOD 스티칭을 완전히 제거합니다. 필요한 곳에는 메시의 삼각형이 더 많고, 필요하지 않은 곳에는 더 적습니다. 단점은 조각할 때 개별 청크가 아니라 하나의 대형 메시를 업데이트해야 하므로 대규모 편집에서는 비용이 많이 들 수 있다는 것입니다.
비파괴 SDF 높이맵
Landow.dev는 높이맵 자체를 SDF 합성으로 생성하는 기법도 설명합니다. 셰이프 인스턴스(구, 상자, 노이즈 함수)를 컴퓨트 셰이더에서 CSG 연산으로 합성하고, 그 출력을 높이맵으로 샘플링합니다. 높이맵 렌더링의 단순함을 유지하면서도 비파괴 편집을 제공합니다. 언제든 원하는 셰이프 인스턴스를 이동하거나 삭제할 수 있습니다.
이는 매력적인 하이브리드 방식입니다. 데이터 표현은 볼류메트릭(SDF CSG 트리)이지만 렌더링 경로는 표준 높이맵 메시입니다. SDF 측에서는 실행 취소/다시 실행과 네트워크 친화적인 편집 연산을 얻고, 높이맵 측에서는 단순한 렌더링과 물리를 얻습니다. 단, 동굴이나 오버행을 지원할 수 없다는 한계는 그대로입니다.
대규모 월드를 위한 희소 복셀 옥트리
조밀한 3D 그리드는 확장성이 떨어집니다. 각 변이 1km인 월드를 0.5m 해상도로 표현하려면 80억 개의 복셀이 필요합니다. 희소 복셀 옥트리(SVO)는 공간을 재귀적으로 세분화하고 표면 경계를 포함하는 옥턴트에만 저장 공간을 할당하여 이 문제를 해결합니다.
SVO는 본질적으로 계층적 LOD를 제공합니다. 임의의 지점에서 트리 깊이가 유효 해상도를 결정합니다. 플레이어 근처에서는 트리가 완전히 확장되어 최대 디테일을 제공하고, 멀리서는 더 거친 수준에서 잘립니다.
렌더링 시 SVO를 직접 레이 트레이싱할 수 있으므로 메시를 추출할 필요가 없습니다. GPU 레이 마처는 각 트리 수준에서 축 정렬 상자와 레이의 교차를 검사하며, 빈 하위 트리는 전부 건너뜁니다. 이를 통해 청크 기반 렌더링의 오버드로 문제를 없애고 그리디 메시 생성의 아티팩트를 방지합니다.
AdamYuan의 Vulkan 기반 SVO 빌더는 상당한 성능을 보여 줍니다. GTX 1660 Ti에서 Crytek Sponza를 2^10 해상도로 빌드하는 데 19ms가 걸립니다.
조각 시에도 SVO 수정은 효율적입니다. 브러시 반경 안에 있는 리프 노드만 업데이트하면 되며, 트리 구조가 가변 해상도를 자연스럽게 처리합니다. 플레이어가 조각하는 곳에서는 노드를 분할해 해상도를 높이고, 다듬는 곳에서는 노드를 병합해 해상도를 낮추는 작업이 데이터 구조의 특성상 자연스럽게 이루어집니다.
브라우저 배포의 과제는 WebGL이 컴퓨트 셰이더를 지원하지 않는다는 점입니다. WebGPU는 지원하지만 SVO 구축 및 순회 알고리즘을 WGSL로 구현하기는 복잡합니다.
멀티플레이어 조각을 위한 네트워크 동기화
월드에는 이미 Cloudflare Durable Objects(world-chunk-do.ts)를 통한 멀티플레이어가 구현되어 있습니다. 조각 기능을 추가하려면 연결된 모든 클라이언트에서 지형 수정 사항을 동기화해야 합니다.
델타 압축
원시 복셀 데이터 전송에는 많은 비용이 듭니다. Oulu University의 2024년 연구에서는 델타 인코딩과 DEFLATE 압축을 결합해 페이로드를 2~8배 개선했으며, 복셀 업데이트를 복셀당 1바이트 미만으로 패킹했습니다. SDEC 코덱은 비트 패킹 델타 인코딩을 통해 일반 직렬화의 평균 패킷 크기인 1,114바이트를 259바이트로 줄였습니다.
연산 기반 동기화(권장)
필드 상태 대신 연산을 동기화합니다. 각 조각 작업은 다음과 같은 메시지가 됩니다.
interface TerrainEditMsg {
t: MsgType.TerrainEdit
brush: {
position: [number, number, number]
radius: number
strength: number
falloff: 'smooth' | 'linear' | 'sharp' | 'constant'
operation: 'raise' | 'lower' | 'smooth' | 'flatten' | 'noise'
targetHeight?: number
}
}서버는 이 메시지를 모든 클라이언트에 브로드캐스트하고, 각 클라이언트는 로컬 지형 데이터에 동일한 결정론적 브러시 연산을 적용합니다. 이는 Teardown이 구조적 파괴에 사용하는 방식과 같습니다. 신뢰성 있는 스트림으로 결정론적 명령을 전송합니다.
ALICE-SDF의 CSG 트리 diff/patch는 이를 한 단계 더 발전시킵니다. 개별 브러시 스트로크 대신 전체 CSG 트리의 구조적 변경을 diff로 표현합니다. 이를 통해 네트워크 전체에서 효율적으로 실행 취소/다시 실행할 수 있으며(역방향 패치 전송), 나중에 참가한 클라이언트도 연산 로그를 재생해 전체 월드 상태를 재구성할 수 있습니다.
우선순위와 스로틀링
연결된 플레이어 근처의 지형 편집은 우선순위를 높여 즉시 브로드캐스트해야 합니다. 플레이어로부터 멀리 떨어진 편집은 배치로 묶어 더 낮은 빈도로 전송할 수 있습니다. Enshrouded의 복셀 네트워킹은 이 패턴을 사용합니다. 플레이어와 가까운 지형은 60Hz로, 배경 영역은 10Hz로 업데이트합니다.
전송 과정에서 ZSTD 압축을 사용하면 지형 업데이트 메시지의 패킷 크기를 최대 60% 줄일 수 있습니다.
협업 조각: 동시 편집
여러 플레이어가 같은 영역을 동시에 조각할 때는 충돌 해결이 필요합니다. cSculpt(CNR Visual Computing Lab, 2016)는 다중 해상도 병합 알고리즘으로 이 문제를 해결했습니다. 각 편집은 여러 스케일로 표현되며, 동시에 발생해 서로 겹치는 편집은 각각의 다중 해상도 표현을 블렌딩하여 병합합니다. 우리 목적에는 더 단순한 접근 방식이면 충분합니다. 바로 서버 순서를 따르는 최종 쓰기 우선(last-write-wins) 방식입니다. Durable Object는 각 편집에 타임스탬프를 지정하고 순서대로 브로드캐스트합니다. 모든 클라이언트는 동일한 순서로 편집을 적용합니다. 브러시 스트로크는 작고 국소적이며 가산/감산 방식이므로, 동시에 수행된 편집의 순서가 약간 바뀌더라도 시각적 결과는 일반적으로 "올바른" 순서의 결과와 구분하기 어렵습니다.
INST-Sculpt: 신경망 SDF 편집(연구 최전선)
INST-Sculpt(arxiv 2502.02891, 2025년 2월)는 신경망 SDF를 스트로크 방식으로 편집할 수 있게 해줍니다. 사용자가 표면에 스트로크를 그리면 시스템이 스트로크 경로 주변의 관형 영역을 따라 기반 신경 필드를 변형합니다. 사용자 정의 브러시 프로필(설정 가능한 단면)이 변형 형태를 제어합니다.
이는 AI 생성 지형에 흥미로운 방식입니다. 기본 월드가 신경망 SDF(3D 좌표를 부호 있는 거리로 매핑하는 소형 신경망)로 표현된다면, 조형 작업은 명시적인 복셀 데이터가 아니라 네트워크 가중치를 수정합니다. 표현 크기는 월드 전체가 몇 MB에 불과할 정도로 매우 작지만, 평가 비용은 룩업 테이블보다 더 높습니다.
이는 아직 연구 단계의 기술입니다. 현재 소비자용 하드웨어에서 신경망 SDF의 추론 비용은 실시간 게임에 사용하기에는 너무 높습니다. 하지만 특히 WebGPU 셰이더 기능이 향상되고 모델 추론이 빨라짐에 따라 계속 주시할 가치가 있습니다.
World Creator 2026.3: 상용 지형 도구의 최첨단
World Creator(BiteTheBytes, 2026년 3월)는 상용 지형 제작 도구의 최첨단 수준을 보여줍니다. 버전 2026.3에는 자동 지형 적응(배치된 오브젝트에 맞춰 지형이 변형됨)을 지원하는 GPU 기반 지형 생성, LOD 최적화를 위한 카메라 중심 오브젝트 분포, 실제 고도 데이터(GeoTIFF, HGT, DTED) 가져오기 기능이 추가되었습니다.
이후 World Creator 2026.4(2026년 4월 28일)에는 숫자 필드의 수학 표현식, 주요 오브젝트가 표면에 자연스럽게 안착하도록 하는 지형 노멀 블렌딩, 완전한 데칼 지원, 사용 가능한 GPU 메모리에 맞춰 최대 오브젝트 수를 조정하는 VRAM 스케일링이 추가되었습니다. BiteTheBytes는 모든 기능을 제공하되 내보내기만 비활성화한 무료 Community Edition도 출시했습니다. 사실상 사용 기간 제한이 없는 체험판입니다.
이 도구는 침식 시뮬레이션, 하천 굴착, 텍스처 페인팅을 포함한 모든 지형 작업에 GPU 컴퓨트를 사용합니다. 브러시 도구는 GPU 가속을 활용하며 뷰포트에서 실시간 피드백을 제공합니다. 이는 위에서 설명한 WebGPU 컴퓨트 파이프라인을 데스크톱 GPU에서 실행하는 방식과 일치합니다.
이미 구축한 것: 24개의 스파이크와 프로덕션 월드
world/spikes/ 디렉터리에는 각각 독립적으로 실행되는 프로토타입 24개가 들어 있습니다. 단순한 장난감 데모가 아닙니다. 각 스파이크가 특정 문제를 해결하고, 목표 대비 벤치마크를 수행하며, 다음 스파이크의 방향을 제시하는 점진적인 R&D 파이프라인입니다. 조형 시스템은 후기의 볼류메트릭 스파이크뿐만 아니라 이 모든 결과를 기반으로 합니다.
프로덕션 높이맵 지형(world/client/)
현재 서비스 중인 월드는 Three.js WebGL의 청크 기반 높이맵 시스템을 사용합니다.
noise.ts는 결정론적terrainHeight(wx, wz)함수와 함께 FBM 값 노이즈(언덕에 5옥타브, 능선에 4옥타브, 미세 디테일에 3옥타브)를 사용하여 지형 높이를 생성합니다chunk.ts는Float32Array높이맵으로부터 3단계 LOD 수준(64유닛 청크당 32/8/4 세그먼트)의PlaneGeometry메시를 만들고, 시드 기반 무작위 배치와 오브젝트별 콜라이더를 사용해 인스턴싱된 나무/빌보드를 배치합니다chunk-manager.ts는 플레이어 주변에 고리 형태로 청크를 스트리밍하고(LOD0에서 반경 1, LOD1에서 반경 3, LOD2에서 반경 6),stitchEdge()의 선형 보간으로 가장자리를 연결하며, 물리 레이어에getHeight(),getNormal(),resolveCollisions()를 제공합니다terrain-material.ts는 경사도 및 높이 기반 가중치와 레이어별 노멀 맵 블렌딩을 사용하여MeshStandardMaterial.onBeforeCompile을 통해 4레이어 스플랫 기반 텍스처 블렌딩(잔디/바위/모래/흙)을 수행합니다character-controller.ts는 중력, 지면 접촉, 경사 거부(최대 경사 코사인 50도)를 위해 매 프레임 지형 높이를 샘플링합니다. 조형 작업은 수정된 높이를 이 시스템에 즉시 반영해야 하며, 그렇지 않으면 플레이어가 편집된 지형을 뚫고 떨어집니다placement.ts에는 이미 오브젝트 배치 도구를 위해 청크 메시와 교차하는Raycaster가 있습니다. 브러시 도구는 레이캐스팅을 처음부터 새로 구현하는 대신 이 패턴을 그대로 따라야 합니다protocol.ts는world-chunk-do.tsDurable Object를 통한 멀티플레이어 동기화용 MessagePack 인코딩 메시지를 정의하며, 현재PlayerState,PlaceObject,RemoveObject,Snapshot메시지를 처리합니다. 지형 편집을 위해서는 새로운 메시지 유형이 필요합니다world-chunk-do.ts(Cloudflare Worker)는 배치된 오브젝트를 Durable Object 스토리지에 영속화하고 50ms 간격으로 연결된 플레이어에게 브로드캐스트합니다. 아직 지형 수정이라는 개념은 없습니다
스파이크 01-11: 기반 레이어
이 스파이크들은 조형 기능이 의존할 핵심 시스템을 검증했습니다. 이를 건너뛰면 조형 시스템이 준수해야 할 제약 조건을 놓치게 됩니다.
스파이크 01(지형 + 인스턴싱): Three.js로 만든 최초의 지형 프로토타입입니다. chunk.ts에서 지금도 사용하는 PlaneGeometry + 높이맵 패턴과 인스턴싱된 오브젝트 배치 방식을 확립했습니다.
스파이크 02(Rapier 물리 워커): ColliderDesc.heightfield() 콜라이더를 사용하는 Rapier 3D를 Web Worker에서 실행했습니다. 자동 단차 오르기, 경사 제한, 지면 스냅을 지원하는 키네마틱 캐릭터 컨트롤러를 구축했습니다. 이 스파이크는 메인 스레드 외부에서 높이 필드를 대상으로 물리 연산을 실행할 수 있음을 입증했습니다. 지형을 조형한다면 물리 높이 필드를 다시 만들거나 MC 청크용 트라이메시 콜라이더로 교체해야 합니다.
스파이크 05(LLM 행동): 지형과 직접적인 관련은 없지만 게임 오브젝트용 JSON 행동 스키마를 확립했습니다. 조형된 지형 요소가 행동을 트리거할 수 있기 때문에 중요합니다(예: 깎아 만든 강이 물 효과를 생성).
스파이크 06(청크 스트리밍): 플레이어 이동에 따라 동적으로 로드되는 최초의 청크 로드/교체 시스템입니다. chunk-manager.ts가 사용하는 패턴, 즉 로드 및 언로드되는 색상 영역을 확립했습니다. 조형 시스템은 청크를 언로드했다가 다시 로드해도 편집 상태를 보존해야 합니다.
스파이크 07(밀도 맵 기반 GPU 식생): 지형 높이와 경사도를 샘플링하는 밀도 맵을 통해 인스턴싱된 풀과 나무를 배치했습니다. 조형 작업은 식생 배치를 무효화합니다. 지형 높이가 변경되면 나무가 공중에 뜨거나 땅에 묻힐 수 있습니다. 편집된 청크의 밀도 맵을 다시 생성해야 합니다.
스파이크 08(지형 머티리얼 셰이더 비용): 트라이플래너 투영, 노멀 맵, 4레이어 블렌딩의 성능을 벤치마크했습니다. 각 기능의 정확한 밀리초 비용을 측정했습니다. 트라이플래너 + 노멀 + 4레이어 조합이 45FPS 이상에서 예산 내에 유지됨을 확인했습니다. 이 예산은 조형된 지형에도 중요합니다. "편집된 흙"을 위한 다섯 번째 레이어를 추가하거나 깎인 표면의 블렌딩을 변경할 경우, 사용 가능한 여유 성능이 정확히 얼마나 되는지 알 수 있습니다.
스파이크 09(CSM 그림자 예산): 1024^2 해상도의 캐스케이드 3개를 사용하는 캐스케이디드 섀도 맵입니다. 그림자 비용을 약 1.5ms로 측정했습니다. 조형된 지형은 섀도 맵을 변경하지만 비용은 지형 형태와 관계없이 일정합니다.
스파이크 10(지오메트리 클립맵 + 지오모핑): LOD 수준 사이에 지오모핑을 적용한 중첩 클립맵 고리를 사용해 팝인 현상을 제거했습니다. 삼각형 수가 일정하므로 GPU 비용을 예측할 수 있습니다. 지오모핑은 조형에 중요합니다. 플레이어가 LOD 경계 근처를 조형할 때 LOD 수준 사이의 모프가 편집 내용을 반영해야 합니다. 편집 내용이 고해상도 고리에만 존재하면 지오모프 대상이 잘못됩니다.
스파이크 11(높이맵 청크 스트리밍): LOD 수준별 로드됨/로드 중/언로드됨 상태를 표시하는 시각적 그리드를 갖춘 고급 청크 스트리밍입니다. 스트리밍 예산, 즉 프레임당 최대 로드 청크 수와 LOD 업그레이드가 필요한 청크의 우선순위를 확립했습니다. 조형 작업은 새로운 우선순위 신호를 추가합니다. 플레이어가 현재 편집 중인 청크는 절대 언로드하면 안 됩니다.
스파이크 12-14: WebGPU + Three.js 통합
스파이크 12(WebGPU 마칭 큐브): 최초의 볼류메트릭 스파이크입니다. 애니메이션되는 구형 동굴이 있는 4개의 64^3 SDF 청크를 GPU에서 완전히 실행했습니다. 원시 WebGPU를 사용하며, SDF 평가용 컴퓨트 파이프라인, Twinklebear 케이스 테이블(256개 구성, 각 16개 항목)을 사용하는 MC 추출, 원자적 버텍스 카운터, 간접 드로우로 구성됩니다. 성능 목표는 청크당 <4ms, 4개 전체에서 <12ms였습니다. 이를 통해 GPU MC가 브라우저에서 실시간 리메싱을 수행하기에 충분히 빠르다는 것을 검증했습니다. 이후의 모든 볼류메트릭 스파이크는 여기서 정의한 MC 케이스 테이블과 WGSL 셰이더를 재사용합니다.
스파이크 13(스파이크 12 기준선 재설정): 스파이크 12의 원시 WebGPU 드로우 경로를 Three.js의 WebGPURenderer 내부에서 실행하도록 포팅하고 백엔드의 device에 직접 접근했습니다. 렌더 파이프라인은 여전히 원시 WebGPU(drawIndirect와 Vertex vec4+vec4 구조체)를 사용합니다. 이를 통해 사용자 정의 컴퓨트와 Three.js 씬 렌더링이 동일한 GPU 장치에서 공존할 수 있음을 입증했습니다.
스파이크 14(Three.js WebGPU 점진적 안정화): 원시 렌더 파이프라인을 위치 및 노멀용 Three.js StorageBufferAttribute로 교체했습니다. MC 컴퓨트는 GPU에 상주하는 이 버퍼에 직접 씁니다. drawIndirect 버퍼는 Three.js가 그릴 버텍스 수를 제어합니다. 이후 모든 스파이크가 이 패턴을 사용합니다. 컴퓨트는 원시 WebGPU로 유지하고 렌더링은 Three.js 씬 그래프를 통합니다. WebGPU 백엔드가 안정화되면서 이 스파이크들에서 사용한 Three.js 버전은 0.170.0에서 0.172.0으로 발전했습니다.
스파이크 15-17: Transvoxel LOD 연결
스파이크 15(Transvoxel 심 골격): 3영역 아키텍처를 추가했습니다. MC 청크(볼류메트릭 중심부), 전환 스트립(MC 경계와 높이맵 사이의 심), 지형 고리(주변 높이맵)입니다. 세 영역 모두 하나의 머티리얼 패스를 공유합니다. 이 단계의 전환 스트립은 실제 Transvoxel 셀이 아닌 임시 메시입니다.
스파이크 16(공유 높이맵을 사용하는 Transvoxel +X 면): 하나의 스파이크에서 두 가지 중요한 돌파구를 마련했습니다. 첫째, SDF의 평평한 지형 평면을 공유 Perlin 높이맵으로 교체했습니다. 257x257 Float32Array를 스토리지 버퍼로 GPU에 업로드하고 SDF 컴퓨트 셰이더에서 이중 선형 보간으로 샘플링했습니다. 이제 MC 표면과 높이맵 메시가 동일한 기준 데이터를 공유합니다. 둘째, GitHub에서 Eric Lengyel의 참조 데이터 테이블(transitionCellClass, transitionVertexData, transitionCellData)과 npm transvoxel-data 패키지를 가져와 +X 면에 실제 Transvoxel 전환 셀을 구현했습니다. CPU는 9샘플 전환 셀(512개 구성, 73개 동치 클래스)을 평가하고, 그리드 지점의 SDF 값을 보간해 버텍스를 배치하며, 미러링된 케이스의 와인딩 반전을 처리합니다.
스파이크 17(이중 MC 1x/2x LOD): 서로 다른 해상도의 MC 청크 2개를 나란히 배치했습니다. 고해상도는 cell_scale=1.0인 62셀, 저해상도는 cell_scale=2.0인 31셀입니다. MC 셰이더에 cell_scale 및 grid_points 유니폼이 추가되었습니다. transition_shrink를 도입해 저해상도 청크의 face-0 경계 버텍스를 cell_scale의 15%만큼 안쪽으로 당겼으며, 이를 통해 Transvoxel 전환 셀이 Z 파이팅 없이 채울 수 있는 얇은 틈을 만들었습니다. 프로덕션 시스템에 필요한 LOD 모델은 바로 이것입니다. 가까운 청크는 전체 해상도, 먼 청크는 절반 해상도를 사용하고 모든 경계에 Transvoxel을 적용합니다.
스파이크 18-21: Transvoxel 코너 케이스와 GPU 가속
이 네 스파이크는 각각 Transvoxel 구현의 구체적인 실패 사례 하나를 해결했습니다. 하나로 묶어 설명하면 서로 다른 문제가 가려집니다.
스파이크 18(높이맵 2:1 심): 한쪽 해상도가 다른 쪽의 두 배인 순수 높이맵 경계에 Transvoxel을 적용했습니다. MC는 사용하지 않았습니다. 62셀 및 31셀 높이맵 청크 사이의 심은 15% 저해상도 면 축소와 Transvoxel 전환 테이블을 사용해 생성됩니다. 이를 통해 Transvoxel이 MC뿐만 아니라 높이맵 전용 사례에서도 작동함을 검증했습니다.
스파이크 19(64/32/32/16 코너 그리드): 가장 까다로운 연결 사례입니다. 서로 다른 해상도를 가진 4개 청크(64, 32, 32, 16셀)가 한 모서리 지점에서 만납니다. 심 시스템은 네 가장자리(A-B, A-C, B-D, C-D)를 따라 각 방향에 올바른 와인딩을 적용한 전환 셀을 생성해야 합니다. 이 스파이크는 Transvoxel 테이블이 사용자 정의 예외 처리 로직 없이도 다중 해상도 코너를 처리할 수 있음을 입증했습니다.
스파이크 20(GPU Transvoxel 코너): 64/32/32/16 코너 레이아웃의 Transvoxel 전환 셀 생성을 GPU로 옮겼습니다. 애니메이션 지형에서 매 프레임 전환 셀을 다시 생성할 때 CPU가 병목이 되었습니다. GPU 컴퓨트는 MC 추출과 동일한 패스에서 심 버텍스를 생성합니다.
스파이크 21(GPU MC + Transvoxel 코너): 완전한 GPU MC 추출과 GPU Transvoxel 심 생성을 하나의 컴퓨트 디스패치 시퀀스로 결합했습니다. MC 청크와 네 개의 심 모두 GPU에서 생성되고, 버텍스 수는 원자적 카운터로 관리되며 drawIndirect로 그려집니다. 이는 매끄러운 LOD 전환을 갖춘 다중 해상도 볼류메트릭 지형의 완전한 GPU 파이프라인입니다.
스파이크 22-24: 하이브리드 아키텍처
스파이크 22(하이브리드 MC/높이맵 정책): 핵심 아키텍처 스파이크입니다. 청크는 기본적으로 높이맵입니다. 애니메이션되는 변형 구체가 청크의 AABB와 교차하면 해당 청크가 MC 모드로 전환됩니다. 나머지는 정적 높이맵 메시로 유지됩니다. 레이아웃은 서로 다른 해상도의 64, 32/32, 16셀 청크로 구성됩니다. Transvoxel 심은 MC-높이맵 전환을 포함한 모든 경계를 처리합니다. 이 스파이크는 프레임마다 MC 청크 수와 HM 청크 수, 버텍스 오버플로를 추적합니다.
스파이크 23(정책 기반 청크 모드): 스파이크 22 위에 패치 형태로 로드됩니다. 카메라 거리 히스테리시스(카메라가 임계값 근처에 있을 때 청크가 모드 사이에서 깜박이지 않음)와 편집 마스크(변형된 청크는 변형 소스가 멀어져도 MC 모드에 유지됨)를 추가했습니다. 이는 조형에 필요한 "고정 편집" 동작입니다. 플레이어가 동굴을 한 번 파면 해당 청크는 영구적으로 볼류메트릭 상태를 유지합니다.
스파이크 24(정책 + 클립맵 고리): 가장 발전된 스파이크입니다. 스파이크 23의 근거리 정책 시스템과 스파이크 10의 원거리 지오메트리 클립맵 고리를 결합했습니다. Three.js 0.183.1로 업그레이드했습니다. 근거리는 64/32/16 해상도에서 Transvoxel 심을 사용하는 HM/MC 하이브리드를 사용합니다. 원거리는 카메라를 따라가는 정적 중심 클립맵 고리를 사용합니다. 이것이 완성된 지형 렌더링 아키텍처입니다. 필요한 곳에는 청크 기반 볼류메트릭 조형을 사용하고, 나머지 모든 곳에는 저비용 클립맵 지형을 사용합니다.
Surface Nets가 아닌 Marching Cubes를 사용하는 이유
이 가이드의 외부 연구 섹션에서는 브라우저 기반 지형 조형에 가장 유력한 후보로 Surface Nets를 제안합니다. 하지만 파이프라인의 모든 스파이크에서는 Marching Cubes를 사용합니다. 이는 우연이 아닙니다.
MC의 결정적인 장점은 단순할 정도로 뛰어난 병렬성입니다. 각 큐브가 완전히 독립적입니다. 스파이크 12~24의 WGSL 컴퓨트 셰이더는 셀 간 통신 없이 큐브마다 하나의 스레드를 디스패치합니다. 버텍스 할당은 원자적 카운터로 처리합니다. 이 방식은 GPU 워크그룹에 완벽하게 맞습니다.
Surface Nets는 표면이 포함된 셀마다 하나의 버텍스를 배치한 다음 이웃 셀과 연결합니다. 이러한 이웃 연결에는 셀 간 의존성이 있습니다. fast-surface-nets 크레이트는 신중하게 설계된 반복 순서를 통해 CPU에서 이를 처리합니다. GPU에서는 2패스 방식(버텍스를 찾은 뒤 연결)이나 워크그룹 내 공유 메모리가 필요합니다. WebGPU에서는 둘 다 가능하지만 복잡성이 증가합니다.
실용적인 권장 사항은 조형 파이프라인에서 Marching Cubes를 계속 사용하는 것입니다. 이미 코드베이스에서 검증되었고, WGSL 셰이더가 존재하며 벤치마크까지 완료되었고, Transvoxel 심 시스템도 MC의 에지 기반 버텍스 배치를 중심으로 구축되어 있습니다. 이진 데이터에서 MC의 앨리어싱이 눈에 띄는 문제가 된다면 Surface Nets를 다시 검토할 가치가 있지만, 값이 부드러운 그라디언트인 SDF 지형에서는 MC가 깔끔한 결과를 생성합니다.
조형을 위한 실용적인 아키텍처
스파이크 시퀀스를 통해 렌더링 파이프라인은 해결되었습니다. 이제 남은 것은 브러시 시스템, 게임 시스템 전반으로 연쇄되는 부수 효과, 그리고 멀티플레이어 동기화입니다. 다음은 모든 스파이크를 토대로 한 계획입니다.
1단계: 높이맵 조형(변경은 최소화하고 적용 범위는 극대화)
프로덕션 world/client/ 코드에 청크 높이맵을 수정하는 브러시 도구를 추가합니다. 이 방식은 기존 WebGL 렌더러 내에서 작동하며 WebGPU가 필요하지 않습니다.
브러시 입력: placement.ts의 PlacementTool 패턴을 따릅니다. 이 도구에는 이미 chunkManager.getChunkMeshes()에 적중하는 Raycaster가 있으며, 적중 지점에서 고스트 메시를 추적합니다. TerrainBrushTool도 동일하게 레이캐스트하되 오브젝트를 배치하는 대신 청크 높이맵을 수정합니다. World.onMouseDown 핸들러는 이미 도구 상태에 따라 작업을 디스패치합니다.
청크 수정(Chunk.applyBrush): 브러시의 월드 위치를 높이맵 그리드 좌표로 변환합니다. 브러시 반경 안에 있는 각 그리드 지점에서 감쇠 가중 변위를 계산해 높이맵 값에 더하거나 뺍니다. 그런 다음 메시를 업데이트합니다. 수정된 높이맵을 바탕으로 버텍스의 Y 위치를 설정하고, 중앙 차분을 통해 노멀을 다시 계산하며(chunk.ts 155~158행에서 이미 사용하는 동일한 terrainHeight(wx +/- eps, wz) 패턴), 영향을 받은 영역에 대해 terrain-material.ts의 createSplatMap()으로 스플랫 맵을 다시 생성하여 경사 기반 텍스처 블렌딩도 업데이트합니다.
캐릭터 컨트롤러: CharacterController.update()는 접지 처리를 위해 매 프레임 getHeight()를 호출합니다. ChunkManager.getHeight()는 청크의 heightmap Float32Array를 읽는 Chunk.sampleHeight()에 처리를 위임합니다. 이 배열을 직접 수정하므로 별도의 연결 작업 없이 다음 프레임에 캐릭터 컨트롤러가 변경 사항을 반영합니다.
오브젝트 무효화: chunk.ts의 나무 인스턴스는 생성 시 terrainHeight()를 샘플링하여 배치됩니다. 조형 후에는 영향을 받은 영역의 나무 높이가 잘못될 수 있습니다. 1단계에서는 이를 미뤄도 됩니다. 작은 편집에서는 나무가 약간 떠 보이는 정도입니다. 2단계에서는 높이를 다시 샘플링하고 인스턴스 행렬을 재구축하는 chunk.invalidateObjects()가 필요합니다. resolveCollisions()에서 사용하는 콜라이더에도 같은 처리가 필요합니다.
Rapier 물리 엔진(통합된 경우): 스파이크 02에서 높이 필드 콜라이더가 작동함을 입증했습니다. Rapier가 활성화되어 있다면 수정된 청크의 높이 필드 콜라이더를 다시 구축하거나 패치해야 합니다. Rapier의 ColliderDesc.heightfield()는 평면 Float32Array를 받으므로 그대로 교체하면 됩니다.
네트워크 동기화: protocol.ts에 MsgType.TerrainEdit = 10을 추가합니다.
interface TerrainEditMsg {
t: MsgType.TerrainEdit
cx: number
cz: number
brush: {
wx: number
wz: number
radius: number
strength: number
falloff: number
operation: number
}
}WorldChunkDO는 이를 모든 클라이언트에 브로드캐스트하고 Durable Object 스토리지에 저장된 청크별 편집 로그에 추가합니다. 나중에 참여한 클라이언트는 Snapshot 메시지로 편집 로그를 받아 재생함으로써 지형 상태를 재구성합니다. 모든 클라이언트가 동일한 결정론적 브러시 함수를 적용하므로 같은 높이맵 상태로 수렴합니다.
청크 언로드/재로드: 스파이크 06과 스파이크 11에서 스트리밍 패턴을 확립했습니다. 청크를 언로드했다가 나중에 다시 로드할 때는 해당 청크의 편집 로그를 기본 절차적 높이맵에 다시 적용해야 합니다. 편집 로그는 서버 측 Durable Object에 저장되며 Snapshot 메시지에 포함됩니다.
2단계: 스파이크 22~24 아키텍처를 활용한 볼류메트릭 조형
스파이크 24 파이프라인을 프로덕션 월드로 이식합니다. 플레이어가 표면 아래를 조형하면(동굴을 파거나 터널을 만드는 경우) 영향을 받은 청크가 높이맵 모드에서 MC 모드로 전환됩니다.
WebGPU 렌더러 마이그레이션: 스파이크 13~14를 통해 Three.js의 WebGPURenderer가 씬 그래프와 함께 커스텀 컴퓨트를 호스팅할 수 있음을 입증했습니다. 프로덕션 월드는 MC 청크에 StorageBufferAttribute를 사용하는 방식으로 WebGLRenderer에서 WebGPURenderer로 이전합니다. WebGPU를 사용할 수 없을 때는 1단계의 높이맵 전용 경로로 대체합니다.
청크별 SDF 할당: 스파이크 22의 하이브리드 패턴을 따릅니다. 각 청크는 높이맵으로 시작합니다. 첫 번째 볼류메트릭 브러시 스트로크가 적용되면 64^3 Float32Array를 할당하고 높이맵을 샘플링해 초기화한 뒤(각 지점의 SDF 값은 world.y - heightmap_value) MC 렌더링으로 전환합니다. 스파이크 23의 정책 시스템은 해당 청크가 영구적으로 MC 모드를 유지하도록 보장합니다(편집 마스크의 "고정 편집" 동작).
SDF 셰이더의 공유 높이맵: 스파이크 16의 height_at() 함수입니다. 청크의 높이맵을 GPU 스토리지 버퍼에 업로드합니다. SDF 컴퓨트 셰이더는 max(height_sdf, edit_sdf)를 평가하며, 여기서 height_sdf = world.y - height_at(world.xz)이고 edit_sdf에는 브러시 수정 사항이 포함됩니다. MC 청크와 높이맵 청크는 경계에서 동일한 기준 데이터를 공유합니다.
Transvoxel 심: 스파이크 15~21의 전체 스택을 사용합니다. MC와 높이맵 사이의 경계에는 축소 간격이 적용된 전환 셀을 사용합니다. 해상도가 서로 다른 MC 청크 사이의 경계에는 스파이크 17의 듀얼 LOD 패턴을 사용합니다. 스파이크 19의 코너 케이스는 4방향 교차점을 처리합니다. 스파이크 21의 GPU 컴퓨트는 동일한 디스패치에서 모든 심 지오메트리를 생성합니다.
클립맵 원거리 영역: 조형 범위 밖의 지형에는 스파이크 24의 클립맵 링을 사용합니다. 조형은 이 링에 영향을 주지 않으며, 링은 기본 절차적 높이맵을 샘플링합니다.
지오모핑: 스파이크 10의 지오모핑은 LOD 전환 시 팝인 현상을 제거합니다. 편집된 청크의 경우 지오모핑 대상에도 편집 내용이 포함되어야 합니다. 한 청크는 LOD0의 MC이고 이웃 LOD1 청크는 높이맵이라면 지오모핑은 두 표현 사이를 블렌딩합니다. 이를 위해서는 낮은 LOD에서도 편집 로그를 샘플링해야 합니다.
머티리얼 예산: 스파이크 08에서 노멀을 포함한 4레이어 트라이플래너가 45 FPS 이상을 달성함을 벤치마크했습니다. MC 청크에도 동일한 머티리얼이 필요합니다. 스플랫 맵은 높이맵 경사 대신 SDF 그라디언트로 생성할 수 있습니다(가파른 곳은 바위, 평평한 곳은 풀). 이 방식은 4레이어 예산 안에 들어옵니다.
식생 무효화: 스파이크 07의 밀도 맵 기반 식생은 지형 높이와 경사에 의존합니다. 청크가 MC 모드로 전환되면 SDF 표면을 샘플링하여 나무 인스턴스를 다시 생성해야 합니다. 돌출부에 있거나 동굴 내부에 있는 나무는 컬링해야 합니다. chunk.ts의 인스턴스 메시 행렬은 새 표면을 기준으로 재구축합니다.
3단계: 실행 취소/다시 실행 및 네트워크 동기화를 위한 CSG 편집 트리
원시 SDF 변경을 CSG 연산 트리로 대체합니다. 각 브러시 스트로크는 연산(더하기, 빼기, 부드러운 블렌딩)과 함께 프리미티브(구, 캡슐, 상자)를 추가합니다. SDF는 트리에서 다시 계산됩니다.
장점:
- 비파괴적: 트리에서 편집을 제거하여 실행 취소 가능
- 네트워크 효율성: 원시 필드 값이 아닌 CSG 연산을 브로드캐스트
- 결정론적: 모든 클라이언트가 동일한 연산 시퀀스로 같은 SDF를 구축
- ALICE-SDF의 CSG 트리 diff/patch를 활용해 대역폭 효율적인 동기화와 네트워크 전반의 실행 취소/다시 실행 지원
Durable Object 스토리지: 청크별 편집 트리가 1단계의 단순 편집 로그를 대체합니다. WorldChunkDO는 원시 높이맵 델타가 아닌 CSG 트리 구조를 저장합니다. Snapshot 메시지에는 트리가 포함되며, 나중에 참여한 클라이언트는 이를 평가하여 로컬 SDF를 생성합니다.
4단계: 협업 조형
서버에서 순서를 지정한 연산 재생 방식으로 동시 편집을 지원합니다. Durable Object는 각 편집에 타임스탬프를 지정하고 순서대로 브로드캐스트합니다. 나중에 참여한 클라이언트는 연산 로그를 받아 월드 상태를 재구성합니다. 기존 Snapshot 메시지 타입은 청크별 지형 편집 기록을 포함하도록 확장합니다.
브러시 스트로크는 작고 국소적이며 더하기/빼기 방식이므로, 동시에 수행된 편집의 순서가 약간 바뀌더라도 시각적 결과는 일반적으로 "올바른" 순서와 구별하기 어렵습니다. 서버 순서를 따르는 최종 쓰기 우선 방식이면 충분합니다. Durable Object의 tick() 함수는 현재 플레이어 상태를 위해 50ms 간격으로 실행되며, 동일한 루프에 지형 편집 브로드캐스트를 추가합니다.
주요 참고 자료
알고리즘:
- Lorensen & Cline, "Marching Cubes" (1987)
- Eric Lengyel, "Transvoxel Algorithm" (2009), transvoxel.org
- Losasso & Hoppe, "Geometry Clipmaps" (SIGGRAPH 2004)
- "A High-Performance SurfaceNets Discrete Isocontouring Algorithm" (arxiv 2401.14906, 2024)
- MCHex (arxiv 2511.02064, 2025)
구현체:
- bevy-sculpter v0.18.0 (Rust, Surface Nets + SDF 브러시)
- fast-surface-nets (Rust, 초당 2천만 트라이앵글)
- ALICE-SDF v1.3.0 (Rust + WASM, CSG 트리 diff/patch)
- WebGPU SDF Editor (Nijhoff, 2026년 1월)
- SculptingPro (Unity 런타임 조형 API)
- TerraBrush (Godot 지형 조형 GDExtension)
게임:
- Dreams (Media Molecule, SDF + 포인트 클라우드 렌더링, SIGGRAPH 2015)
- Teardown (Voxagon, 복셀 DDA 레이 트레이싱, 결정론적 멀티플레이어 파괴)
- Mike Turitzin의 SDF 엔진(브릭 맵 + 지오메트리 클립맵, 2026년 1월)
네트워크:
- "Optimizing payload size for voxel state synchronization" (Oulu, 2024)
- Teardown 멀티플레이어(준결정론적 파괴 동기화, 2026년 3월)
- cSculpt(다중 해상도 병합을 활용한 협업 메시 조형)