Skip to content

3D 브러시 기법과 게임 내 월드 스컬프팅

우리는 플레이어가 월드를 빚기를 바랍니다. 그리드 위에 프리팹을 놓는 게 아니고, 블록을 켜고 끄는 것도 아닙니다. 실제로 지형을 다시 만드는 것입니다. 강을 깎고, 산을 솟아오르게 하고, 절벽면을 매끄럽게 다듬고, 동굴을 파는 것이죠. ZBrush와 Blender의 스컬프트 모드가 아티스트를 위해 해주는 그런 작업을, 멀티플레이어 브라우저 게임 안에서 60fps로 돌리는 것입니다.

이건 데이터 표현, 메시 추출, GPU 컴퓨트, 브러시 수학, 네트워크 동기화에 걸친 어려운 엔지니어링 문제입니다. 이 가이드는 우리가 찾아낸 모든 것을 정리합니다.

지형 표현의 두 세계

모든 스컬프팅 시스템은 지형 데이터를 어떻게 저장할지에 대한 선택에서 시작합니다. 그 선택이 어떤 종류의 편집이 가능한지, 얼마나 빠르게 실행되는지, 메모리를 얼마나 쓰는지를 결정합니다.

하이트맵

하이트맵은 그리드 점마다 높이 값 하나를 저장합니다. 밝기가 곧 고도인 그레이스케일 이미지라고 생각하면 됩니다. 우리의 현재 world/client 지형이 바로 이렇게 작동합니다. noise.ts가 FBM 값 노이즈로 높이를 생성하고, 각 ChunkFloat32Array 하이트맵을 저장해 PlaneGeometry에 투영합니다.

하이트맵은 빠릅니다. 샘플링은 양선형 보간을 곁들인 배열 조회 한 번입니다. LOD는 그리드 해상도를 낮추기만 하면 되니 간단합니다. splat 기반 텍스처 블렌딩은 UV 그리드에 그대로 매핑됩니다. 물리 충돌은 높이 쿼리로 줄어듭니다.

한계는 토폴로지입니다. 하이트맵은 (x, z) 좌표당 높이 하나만 표현할 수 있습니다. 동굴도, 오버행도, 아치도, 터널도 안 됩니다. 플레이어가 안쪽으로 접히는 절벽을 깎아내면 하이트맵은 그걸 저장할 수 없습니다. 대부분 완만한 언덕과 산으로 이루어진 지형에는 괜찮습니다. 하지만 플레이어가 땅을 파고들 수 있는 자유 형식 스컬프팅에는 막다른 길입니다.

볼류메트릭(3D 스칼라 필드)

대안은 3D 공간의 모든 점에 값을 저장하는 것입니다. 그 값이 고체 재질 안에서 음수이고 바깥에서 양수라면(또는 그 반대라면) Signed Distance Field(SDF)를 가진 것입니다. 그 값이 그냥 밀도라면(어떤 임계값 위는 고체, 아래는 비어 있음) 밀도 필드를 가진 것입니다.

볼류메트릭 표현은 어떤 토폴로지든 처리합니다. 동굴, 오버행, 떠 있는 섬, 산을 관통하는 터널. 대가는 메모리와 복잡도입니다. 32비트 부동소수점을 쓰는 256^3 그리드는 64 MB가 듭니다. 512^3 그리드는 512 MB입니다. 그것도 단일 chunk에 대해서요. 이걸 실용적으로 만들려면 희소 데이터 구조(옥트리, brick map)가 필요합니다.

메시 추출 단계도 만만치 않습니다. 정점 Y 위치를 설정하고 끝낼 수는 없습니다. 스칼라 필드를 읽어서 필드가 0을 가로지르는 표면을 근사하는 삼각형 메시를 만드는 알고리즘이 필요합니다.

메시 추출: Marching Cubes, Surface Nets, Dual Contouring

Marching Cubes

Marching Cubes는 가장 오래되고 가장 널리 구현된 등치면 추출 알고리즘입니다. 1987년 Lorensen과 Cline이 발표했고, 각 코너에 스칼라 값을 가진 복셀 그리드의 모든 큐브를 검사하는 식으로 작동합니다. 일부 코너가 표면 안(음수)에 있고 일부가 바깥(양수)에 있으면, 그 큐브 안에 삼각형 메시 패치가 배치됩니다.

각 큐브는 8개의 코너를 가지고, 각각 안 또는 밖이므로 256가지 가능한 구성(2^8)이 나옵니다. 대칭성으로 이것이 15가지 고유 케이스로 줄어듭니다. 룩업 테이블이 각 케이스를 삼각형 집합에 매핑합니다. 부호가 바뀌는 에지를 따라 선형 보간으로 에지 교점을 찾습니다.

최근 GPU 구현들은 Marching Cubes를 실시간 스컬프팅에 충분히 빠르게 만들었습니다. 2025년 UE5에서의 한 구현은 각 GPU 스레드에 큐브 하나를 할당해 수천 개를 동시에 처리합니다. 핵심 통찰은 각 큐브의 삼각화가 이웃과 독립적이라는 것으로, 이 덕분에 알고리즘이 민망할 만큼 병렬적입니다.

MCHex(arxiv 2511.02064, 2025)는 Marching Cubes를 양의 야코비안 값을 보장하는 적응형 육면체 메시 생성으로 확장해, 시뮬레이션 메시의 경계 근사를 개선합니다.

rupMC는 CPU/GPU 이종 아키텍처를 사용해 직렬 구현보다 수십 배 빠르고, 병렬 DMC 변형보다 4배 빠른 성능을 달성합니다.

주된 한계는 Marching Cubes가 날카로운 특징에 약하다는 점입니다. 90도 에지가 매끄러운 곡선으로 둥글려집니다. 지형 스컬프팅에는 보통 받아들일 만하지만(자연 지형은 대부분 매끄러움), 건축적 특징에는 문제입니다.

Surface Nets

Surface Nets는 이산 스칼라 필드에서 더 매끄러운 메시를 만드는 더 새로운 알고리즘 계열입니다. Marching Cubes처럼 정점을 큐브 에지에 놓는 대신, Surface Nets는 표면을 포함하는 큐브마다 정점 하나를 놓고, 인접한 정점들을 연결해 쿼드를 만듭니다.

결과는 자연스럽게 더 매끄럽습니다. 2024년 논문(arxiv 2401.14906)은 순차 알고리즘보다 한두 자릿수 빠르게 돌아가는 고성능 병렬 Surface Nets 구현을 시연했습니다. fast-surface-nets Rust 크레이트는 작은 룩업 테이블과 SIMD 가속을 사용해 단일 2.5 GHz 코어에서 초당 약 2천만 개의 삼각형을 생성합니다.

bevy-sculpter(v0.18.0, 2026년 1월)는 Surface Nets를 주요 메싱 전략으로 사용합니다. 이 크레이트는 SDF 기반 볼류메트릭 스컬프팅을 네 가지 브러시 타입과 함께 제공합니다. 하드 CSG(즉시 추가/제거), 매끄러운 연속(누른 입력용), 블러(표면 매끄럽게 하기), 평탄화(목표 높이로 설정). 또한 편집 후 올바른 부호 거리 필드 속성을 복원하기 위해 Fast Sweeping Method를 통한 SDF 재거리화도 포함합니다.

Surface Nets는 Marching Cubes(단순하고 빠르지만 이진 데이터에서 계단 현상이 있는 메시를 만듦)와 Dual Contouring(특징 보존이 되지만 복잡함) 사이의 좋은 중간 지점입니다.

Dual Contouring

Dual Contouring은 Marching Cubes와 Surface Nets가 할 수 없는 날카로운 특징을 보존합니다. 각 코너에서의 필드 부호뿐 아니라 에지 교점에서의 기울기(법선)까지 사용함으로써 이를 해냅니다. QEF(Quadratic Error Function, 이차 오차 함수) 최소화가 모든 에지 교점 제약을 가장 잘 만족하는 위치에 각 셀 안의 정점을 놓습니다.

결과는 추출된 메시에서 날카로운 에지와 코너가 보존되는 것입니다. 수직면을 가진 큐브는 큐브로 남습니다.

대가는 복잡도입니다. QEF 풀이에는 셀 간 의존성이 있어 GPU 병렬화가 어렵습니다. 비매니폴드 메시(둘 이상의 폴리곤이 공유하는 에지)를 만들 수 있습니다. 구현도 Marching Cubes보다 더 까다롭지만, Johannes Jendersie는 작동하는 Dual Contouring 구현이 약 200줄이고 견고한 Marching Cubes는 500줄 이상이라고 언급합니다.

**Cubical Marching Squares(CMS)**가 중간 지점으로 제안되었습니다. 셀 간 독립적(GPU 친화적)이면서도 어느 정도 특징 보존을 제공합니다.

어느 것을 쓸까

브라우저에서 플레이어를 위한 지형 스컬프팅이라면:

Surface Nets가 가장 강력한 후보입니다. Marching Cubes가 이진 데이터에서 보이는 계단 현상 없이 매끄러운 메시(자연스러워 보이는 지형)를 만듭니다. 실시간 재메싱에도 충분히 빠릅니다. 그리고 Dual Contouring보다 구현이 더 간단합니다.

Marching Cubes는 GPU 병렬성이 우선일 때(모든 큐브가 독립적임)나 가장 넓은 라이브러리 지원이 필요할 때 여전히 견실한 선택입니다. Reinder Nijhoff의 WebGPU SDF Editor(2026년 1월)는 추출 파이프라인에서 Marching Cubes와 Surface Nets를 둘 다 구현하며, 전부 GPU에서 돌아갑니다.

Dual Contouring은 건축적 정밀도가 성능보다 더 중요한 경우에 쓰는 게 가장 좋습니다. 브라우저 환경에서의 실시간 지형 스컬프팅에는 이상적이지 않습니다.

Transvoxel 알고리즘: LOD 이음매 해결

복셀 지형을 서로 다른 해상도(플레이어 가까이는 LOD0, 멀리는 LOD2)로 메싱하면 경계에서 균열이 생깁니다. 하이트맵에는 간단한 문제입니다. 에지 정점을 보간해 더 낮은 해상도의 이웃에 맞추면 됩니다. 우리의 현재 chunk.tsstitchEdge()에서 바로 이렇게 합니다.

볼류메트릭 지형에는 문제가 훨씬 어렵습니다. LOD0의 동굴 입구가 경계에서 삼각형 30개를 만들 수 있습니다. 같은 영역이 LOD1에서는 완전히 다른 토폴로지의 삼각형 8개를 만들 수 있습니다. 둘 사이를 선형 보간할 간단한 방법은 없습니다.

Eric Lengyel의 Transvoxel 알고리즘(2009)은 "전이 셀(transition cell)"로 이를 해결합니다. 두 LOD 레벨 사이의 경계에서, 알고리즘은 (8개의 큐브 코너 대신) 9개의 고해상도 샘플을 고려해 512가지 가능한 구성을 만들고, 이것이 73개의 동치류로 나뉩니다. 각 류는 두 해상도 사이의 간극을 완벽하게 채우는 미리 정의된 삼각형 패턴에 매핑됩니다.

이 알고리즘은 로컬 복셀 데이터에서 작동하므로, 수정된 영역을 다시 삼각화하는 게 빠릅니다. 이것이 실시간 스컬프팅에 결정적입니다. 플레이어가 LOD 경계 근처 지형을 편집할 때 전이 셀만 다시 만들면 됩니다.

Rust 구현이 transvoxel 크레이트로 존재합니다. 원본 룩업 테이블은 transvoxel.org에서 받을 수 있습니다.

브러시 수학

스컬프팅 브러시는 목표 점 주위 반경 안의 스칼라 필드 값을 수정하는 함수입니다. Blender부터 언리얼 엔진, 런타임 게임 시스템까지, 모든 구현에서 수학은 놀랄 만큼 비슷합니다.

감쇠 함수

브러시 감쇠는 편집 강도가 중심에서 가장자리로 가면서 어떻게 줄어드는지를 결정합니다. Blender 5.1은 다음 표준 프로파일을 정의합니다.

Smooth: f(d) = 3d^2 - 2d^3 (Hermite 보간, noise.ts의 것과 같은 smoothstep)

Sphere: 중심에서 강하고 경계 근처에서 가파르게 감쇠. f(d) = sqrt(1 - d^2)로 근사.

Sharp: f(d) = (1 - d)^n에서 n > 2. 미세한 점을 만듭니다.

Linear: f(d) = 1 - d, 여기서 d는 중심으로부터의 정규화된 거리(중심에서 0, 가장자리에서 1).

Constant: d < 1일 때 f(d) = 1, 브러시 경계에서 하드 컷오프.

Inverse Square: smooth와 sphere 사이의 하이브리드로 자연스러운 "점토 같은" 느낌.

모든 경우에 d = distance_to_center / brush_radius이고 [0, 1]로 클램프됩니다. 감쇠 값이 브러시 강도에 곱해져 각 점에서의 실제 필드 수정량을 만듭니다.

감쇠 공간

Blender는 sphere 감쇠(3D 월드 공간에서 거리 계산)와 projected 감쇠(2D 화면 공간에서 거리 계산)를 구분합니다. projected 감쇠는 화면에서 가까워 보이는 두 점이 월드 공간에서 깊이가 매우 다르더라도 서로 똑같이 영향을 준다는 뜻입니다. 지형 스컬프팅에는 보통 3D 월드 공간 감쇠가 더 직관적입니다.

핵심 브러시 연산

Raise/Lower(디스플레이스먼트): 브러시 반경 안에서 감쇠로 가중해 스칼라 필드에 더하거나 뺍니다. 하이트맵의 경우: height[i] += strength * falloff(d). SDF의 경우: sdf[i] -= strength * falloff(d) (빼면 재질이 더 단단해져 표면이 올라감).

Smooth(라플라시안): 각 값을 이웃의 평균으로 대체하되 감쇠로 가중합니다. 디테일을 지우고 노이즈를 줄입니다. 라플라시안 필터는 작은 커널(하이트맵은 3x3, 볼륨은 3x3x3)을 샘플링해 평균 쪽으로 블렌딩합니다. HC(Humphrey's Classes) 스무딩은 원시 라플라시안보다 부피를 더 잘 보존하는 변형입니다.

Flatten: 필드 값을 목표 높이(또는 SDF 공간에서의 거리)로 설정하되 감쇠로 블렌딩합니다. 목표는 보통 스트로크가 시작될 때 브러시 중심에서 샘플링한 다음 고정해 둡니다. 평평한 고원을 만듭니다.

Pinch/Inflate: 표면 법선을 향해 또는 그 반대로 정점을 이동시킵니다. SDF 공간에서는 기울기 방향을 따라 변위시키는 것과 같습니다.

Grab: 점토를 잡아당기듯 필드의 한 영역을 평행이동합니다. 변위 벡터는 월드 공간으로 투영된 마우스 델타이고, 반경 안의 필드 값에 적용됩니다.

Noise: 브러시 반경 안에서 필드에 절차적 노이즈를 더합니다. 매끄러운 표면을 거칠게 만들 때 유용합니다.

Stamp: 2D 그레이스케일 이미지를 디스플레이스먼트로 적용해 커서 아래 표면에 투영합니다. 언리얼 엔진의 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 연산(추가, 빼기, 색칠)입니다.

블렌드 모드는 soft-max와 soft-min 함수를 씁니다. "소프트" 블렌드는 원시 형상 사이에 둥근 전이를 만듭니다(서로 눌러 붙인 점토처럼). "하드" 블렌드는 깔끔한 불리언 절단을 만듭니다. 블렌드 반경은 사용자가 조절할 수 있습니다.

렌더링

Dreams는 삼각형 메시를 추출하지 않습니다. 대신 커스텀 포인트 클라우드 렌더러("flecks")로 SDF에서 직접 렌더링합니다. 각 fleck은 표면 법선을 따라 배향된 작은 원반입니다. SDF를 샘플링해 표면을 찾고, fleck들을 그 위에 분포시킵니다. 이것은 메시 추출 병목을 통째로 피하지만 커스텀 렌더러가 필요합니다.

Three.js/WebGL 월드에는 이 접근을 직접 적용할 수 없습니다. 우리는 메시를 추출해야 합니다. 하지만 CSG 편집 목록 개념은 실행 취소/다시 실행과 네트워크 동기화에 매우 관련이 깊습니다.

Mike Turitzin의 동적 SDF 엔진(2026)

Mike Turitzin이 현재 개발 중인 게임 엔진은 동적 SDF를 핵심 표현으로 사용합니다. 이 엔진은 다음을 지원합니다.

게임플레이 중 세밀한 수정: 물질을 매끄럽게 또는 날카로운 에지로 추가하고 제거합니다. 구멍을 옮기거나 플레이어 뒤에서 사라지는 임시 터널을 만드는 등 비파괴적 변경.

Brick map과 brick atlas로 희소 캐싱. 전체 SDF 필드를 조밀한 3D 그리드에 저장하는 대신, 필드를 "브릭"(작은 3D 타일)으로 나눕니다. 표면 경계를 포함한 브릭만 할당됩니다. 이는 대부분 비어 있거나 채워진 공간으로 된 장면의 메모리를 극적으로 줄입니다.

Geometry clipmap(Losasso & Hoppe, SIGGRAPH 2004)으로 LOD를 처리. 해상도가 점점 높아지는 중첩된 정규 그리드가 카메라 위치를 둘러쌉니다. 가장 안쪽 그리드가 가장 세밀하고, 바깥 그리드는 점점 거칠어집니다. 이것은 광대한 공간을 지원하면서 메모리를 크게 줄여줍니다. Clipmap은 카메라가 움직이면 점진적으로 업데이트되어, 스트리밍 오픈 월드에 효율적입니다.

물리와 충돌은 SDF에 직접 작동합니다. Sphere-tracing(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-1000배 압축을 제공하는 SDF 기반 공간 데이터의 Rust 구현을 제공합니다. 다음을 지원합니다.

126개의 빌딩 블록: 원시 형상 72개, 연산 24개, 변환 7개, 모디파이어 23개. 매끄러운 블렌드 연산(합집합, 차집합, 교집합), 하드 에지 베벨과 계단식 CSG 전이를 위한 chamfer 및 stairs 블렌드.

CSG 트리 diff/patch로 실행 취소/다시 실행과 네트워크 동기화를 처리. 이것이 멀티플레이어 스컬프팅의 핵심 기능입니다. 전체 필드 상태를 보내는 대신 두 CSG 트리 사이의 구조적 diff를 보냅니다. 클라이언트는 patch를 적용해 새 상태를 재구성합니다. 이것은 원시 복셀 데이터를 델타 압축하는 것보다 훨씬 대역폭 효율적입니다.

CSG 트리 최적화에는 항등 변환 제거, 중첩 변환 병합, 모디파이어 강등이 포함됩니다. 편집이 쌓이는 동안 트리를 간결하게 유지합니다.

메시 생성은 Marching Cubes와 Dual Contouring 둘 다로 가능합니다. 물리 충돌 감지는 SDF에 직접 작동합니다.

WebAssembly 지원으로 브라우저 통합이 가능합니다. 엔진은 Rust로 작성되고 WASM 바인딩이 있어, Three.js 애플리케이션의 현실적인 선택지가 됩니다.

지형을 위한 WebGPU Compute

WebGPU는 2025년 말부터 모든 주요 브라우저에서 사용할 수 있습니다. Chrome 113+, Edge 113+, Firefox 141+, Safari 26+가 모두 활성화된 채로 출시됩니다. 이것은 이전에는 GPU 전용이던 컴퓨트 셰이더 파이프라인을 열어줍니다.

성능

WebGPU 컴퓨트 셰이더는 대규모 병렬화를 통해 CPU 방식보다 약 100배 빠르게 지형을 생성합니다. GPU는 수천 개의 계산을 동시에 실행하며, 지형 생성은 거의 완전히 병렬적입니다(각 정점/복셀이 독립적임).

작업은 세 단계로 구성됩니다. Dispatch Level(GPU 전반의 작업량 분배), Workgroup Level(처리 단위 내 공유 메모리), Thread Level(개별 계산). WGSL(WebGPU Shading Language)이 셰이더 언어입니다.

실시간 지형 스컬프팅 파이프라인

WebGPU 스컬프팅 파이프라인은 이렇게 생겼을 것입니다.

  1. 브러시 적용(컴퓨트 셰이더): 브러시 반경 안의 스칼라 필드 값을 업데이트합니다. 각 스레드가 복셀 하나를 처리합니다. uniform buffer에서 브러시 매개변수(위치, 반경, 강도, 감쇠 타입, 연산)를 읽어 수정을 적용합니다.

  2. 메시 추출(컴퓨트 셰이더): 수정된 영역에 Surface Nets나 Marching Cubes를 실행합니다. Nijhoff의 WebGPU SDF Editor는 이것을 다단계 파이프라인으로 구현합니다. 16,384개 셀에 걸친 공간 분할, 옥트리 기반 셀 분할, 그다음 표면 추출.

  3. 정점 버퍼 업데이트(GPU 측): 추출된 정점을 CPU 메모리를 거치지 않고 렌더 버퍼에 직접 씁니다.

  4. 법선 계산(컴퓨트 셰이더): 메시나 SDF 기울기에서 정점 법선을 계산합니다.

  5. 렌더링(표준 파이프라인): 표준 PBR 머티리얼로 메시를 그립니다.

1단계부터 4단계까지는 데이터가 JavaScript로 돌아가지 않고 전부 GPU에서 돌릴 수 있습니다. CPU는 매 프레임 브러시 매개변수만 보내면 됩니다.

WebGPU SDF Editor

Reinder Nijhoff의 WebGPU SDF Editor(2026년 1월)는 이 접근을 Chrome에서 돌아가는 모습으로 시연합니다. 원시 형상 6개(cone, cylinder, capsule, torus, box, sphere), 구성 가능한 매끄러운 블렌딩을 갖춘 블렌드 연산 3개(union, subtraction, intersection), 계층적 장면 그래프를 지원합니다. 각 원시 형상은 단일 GPU 버퍼에서 112바이트를 차지합니다.

렌더링 파이프라인은 앰비언트 오클루전과 시간적 안티에일리어싱에 1,024개의 섀도 맵을 씁니다. 이것이 고급 GPU에서 인터랙티브 프레임레이트로 돌아갑니다.

하이트맵 스컬프팅: 더 단순한 길

동굴과 오버행 지원이 필요 없다면, 하이트맵 스컬프팅은 볼류메트릭 파이프라인 전체를 피합니다. 대부분의 출시된 게임이 지형 편집을 처리하는 방식이 이것입니다.

런타임 구현 패턴

Unity Runtime Terrain(JohannHotzel, 2026년 1월)은 표준 패턴을 보여줍니다.

  1. 마우스 위치를 통해 카메라에서 레이캐스트해 지형 충돌 점을 찾습니다.
  2. 충돌 점을 하이트맵 좌표로 매핑합니다.
  3. 감쇠로 가중해 가까운 하이트맵 값에 브러시를 적용합니다.
  4. 수정된 하이트맵에서 정점 Y 위치를 설정해 메시를 업데이트합니다.
  5. 새 메시에 맞게 물리 콜라이더를 다시 만듭니다.

우리의 Three.js 지형에서는 1단계부터 4단계가 기존 아키텍처에 그대로 매핑됩니다. Chunk 클래스는 이미 하이트맵을 저장하고 그것으로 메시를 만듭니다. 스컬프팅을 추가한다는 것은 다음을 의미합니다.

  • chunk 메시에 대한 레이캐스팅 시스템(Three.js Raycaster)
  • chunk.heightmap 값을 수정하는 브러시 적용 함수
  • 메시 정점 업데이트(Y 위치 설정, 법선 재계산)
  • 영향받은 영역의 splat map 재계산(텍스처 블렌딩이 새 경사/높이를 반영하도록)
  • 기존 WebSocket 프로토콜을 통해 편집(브러시 위치, 반경, 강도, 연산)을 다른 클라이언트로 브로드캐스트

Clipmap 접근

Landow.dev는 하이트맵 지형을 위한 "wandering clipmap"을 설명합니다. 플레이어를 따라다니는, 가변 세분화 밀도를 가진 단일 메시입니다. 하이트맵을 서로 다른 LOD 레벨의 별도 메시로 청크화하는(우리가 지금 하는 것) 대신, clipmap은 카메라 가까이에서 조밀하고 가장자리에서 거친 연속적인 메시입니다.

이것은 LOD 이음매를 완전히 없앱니다. 메시는 필요한 곳에 삼각형이 더 많고 필요 없는 곳엔 더 적을 뿐입니다. 대가는 스컬프팅이 개별 chunk가 아니라 단일 큰 메시를 업데이트해야 한다는 점인데, 큰 편집에는 비용이 클 수 있습니다.

비파괴적 SDF 하이트맵

Landow.dev는 하이트맵 자체가 SDF 구성에서 생성되는 기법도 설명합니다. 형상 인스턴스(구, 박스, 노이즈 함수)가 컴퓨트 셰이더에서 CSG 연산으로 합성되고, 그 출력이 하이트맵으로 샘플링됩니다. 이것은 하이트맵 렌더링의 단순함을 유지하면서 비파괴적 편집(언제든 어떤 형상 인스턴스도 옮기거나 지울 수 있음)을 제공합니다.

이건 매력적인 하이브리드입니다. 데이터 표현은 볼류메트릭(SDF CSG 트리)이지만, 렌더링 경로는 표준 하이트맵 메시입니다. SDF 쪽에서 실행 취소/다시 실행과 네트워크 친화적인 편집 연산을 얻고, 하이트맵 쪽에서 단순한 렌더링과 물리를 얻습니다. 한계는 남습니다. 동굴이나 오버행은 안 됩니다.

대형 월드를 위한 희소 복셀 옥트리

조밀한 3D 그리드는 확장되지 않습니다. 한 변이 1 km이고 해상도가 0.5m인 월드는 80억 개의 복셀이 필요합니다. 희소 복셀 옥트리(SVO)는 공간을 재귀적으로 세분화하고 표면 경계를 포함한 옥탄트에만 스토리지를 할당함으로써 이를 해결합니다.

SVO는 자연스럽게 계층적 LOD를 제공합니다. 어떤 점에서의 트리 깊이가 유효 해상도를 결정합니다. 플레이어 가까이에서는 트리가 완전히 펼쳐집니다(최대 디테일). 멀리서는 더 거친 레벨에서 잘립니다.

렌더링에는 SVO를 직접 레이트레이싱할 수 있습니다(메시 추출이 필요 없음). GPU 레이 마처가 각 트리 레벨에서 축 정렬 박스와 레이를 교차시키며 빈 서브트리를 통째로 건너뜁니다. 이것은 chunk 기반 렌더링의 오버드로 문제를 없애고 greedy meshing 아티팩트를 피합니다.

AdamYuan의 Vulkan 기반 SVO 빌더는 상당한 성능을 보여줍니다. GTX 1660 Ti에서 2^10 해상도의 Crytek Sponza를 19ms 빌드 시간으로 처리합니다.

스컬프팅에는 SVO 수정이 효율적입니다. 브러시 반경 안의 리프 노드만 업데이트하면 되고, 트리 구조가 변하는 해상도를 자연스럽게 처리합니다. 플레이어가 스컬프팅하는 곳에 디테일을 추가하고(노드를 더 높은 해상도로 분할), 매끄럽게 하는 곳에서 디테일을 제거하는 것(노드를 더 낮은 해상도로 병합)이 데이터 구조에서 저절로 나옵니다.

브라우저 배포의 과제는 WebGL이 컴퓨트 셰이더를 지원하지 않는다는 점이고, WebGPU는 지원하지만 SVO 구축과 순회 알고리즘이 WGSL로 구현하기에 복잡하다는 점입니다.

멀티플레이어 스컬프팅을 위한 네트워크 동기화

우리 월드는 이미 Cloudflare Durable Objects(world-chunk-do.ts)를 통해 멀티플레이어를 갖췄습니다. 스컬프팅을 추가한다는 것은 연결된 모든 클라이언트에 걸쳐 지형 수정을 동기화하는 것을 의미합니다.

델타 압축

원시 복셀 데이터를 보내는 건 비쌉니다. Oulu 대학의 2024년 연구는 델타 인코딩과 DEFLATE 압축을 결합해 페이로드를 2-8배 개선했고, 복셀 업데이트를 복셀당 1바이트 미만으로 압축했습니다. SDEC 코덱은 비트 패킹 델타 인코딩으로 일반 직렬화의 1114바이트 대비 평균 259바이트 패킷을 만드는 것을 시연했습니다.

연산 기반 동기화(권장)

필드 상태를 동기화하는 대신 연산을 동기화하세요. 각 스컬프팅 동작이 메시지가 됩니다.

typescript
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는 이를 더 밀고 나갑니다. 개별 브러시 스트로크 대신, diff가 전체 CSG 트리의 구조적 변화를 나타냅니다. 이것은 네트워크에 걸친 효율적인 실행 취소/다시 실행을 가능하게 하고(역 patch를 전송), 늦게 합류하는 클라이언트는 연산 로그를 재생해 전체 월드 상태를 재구성할 수 있습니다.

우선순위와 스로틀링

연결된 플레이어 가까이의 지형 편집은 고우선순위여야 합니다(즉시 브로드캐스트). 어떤 플레이어와도 먼 편집은 배치로 묶어 더 낮은 빈도로 보낼 수 있습니다. 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.tsFloat32Array 하이트맵에서 3개 LOD 레벨(64 단위 chunk당 32/8/4 세그먼트)로 PlaneGeometry 메시를 만들고, 시드 기반 랜덤 배치와 오브젝트별 콜라이더로 인스턴스화된 나무/빌보드를 놓습니다
  • chunk-manager.ts는 플레이어 주위에 링 형태로 chunk를 스트리밍하고(LOD0 반경 1, LOD1 반경 3, LOD2 반경 6), stitchEdge()에서 선형 보간으로 에지 이음매를 처리하며, 물리 레이어에 getHeight(), getNormal(), resolveCollisions()를 제공합니다
  • terrain-material.tsMeshStandardMaterial.onBeforeCompile을 통해 경사 및 높이 기반 가중치로 4레이어 splat 기반 텍스처 블렌딩(잔디/바위/모래/흙)을 하고, 레이어별 노멀 맵 블렌딩도 합니다
  • character-controller.ts는 중력, 접지, 경사 거부(최대 경사 cos 50도)를 위해 매 프레임 지형 높이를 샘플링합니다. 스컬프팅은 수정된 높이를 이 시스템에 즉시 넣어야 하며, 그러지 않으면 플레이어가 편집된 지형을 뚫고 떨어집니다
  • placement.ts는 오브젝트 배치 도구를 위해 이미 chunk 메시를 맞히는 Raycaster를 가지고 있습니다. 브러시 도구는 레이캐스팅을 처음부터 만드는 대신 바로 이 패턴을 따라야 합니다
  • protocol.tsworld-chunk-do.ts Durable 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 물리 Worker): Web Worker에서 ColliderDesc.heightfield() 콜라이더로 돌아가는 Rapier 3D. autostep, 경사 제한, 바닥 스냅을 갖춘 키네마틱 캐릭터 컨트롤러를 만들었습니다. 이 스파이크는 물리가 메인 스레드 바깥에서 하이트필드에 대해 돌 수 있음을 증명했습니다. 지형을 스컬프팅하면, 물리 하이트필드를 다시 만들거나 MC chunk에 대해 trimesh 콜라이더로 교체해야 합니다.

스파이크 05(LLM 동작): 지형과 직접 관련은 없지만 게임 오브젝트의 JSON 동작 스키마를 확립했습니다. 스컬프팅한 지형 특징이 동작을 촉발할 수 있어(예: 깎아낸 강이 물 효과를 생성) 관련이 있습니다.

스파이크 06(Chunk 스트리밍): 플레이어가 움직이면 동적으로 로드하는 첫 chunk 로드/스왑 시스템. chunk-manager.ts가 쓰는 패턴을 확립했습니다. 로드되고 언로드되는 색칠된 영역. 스컬프팅은 chunk가 언로드되고 다시 로드될 때 편집 상태를 보존해야 합니다.

스파이크 07(밀도 맵에서의 GPU 식생): 지형 높이와 경사를 샘플링하는 밀도 맵으로 배치되는 인스턴스화된 잔디와 나무. 스컬프팅은 식생 배치를 무효화합니다. 지형 높이가 바뀌면 나무가 떠 있거나 묻힐 수 있습니다. 편집된 chunk에 대해 밀도 맵을 다시 생성해야 합니다.

스파이크 08(지형 머티리얼 셰이더 비용): 트라이플래너 투영, 노멀 맵, 4레이어 블렌딩을 벤치마크했습니다. 각 기능의 정확한 ms 비용을 측정했습니다. 트라이플래너 + 노멀 + 4레이어가 45+ FPS에서 예산 안에 머문다는 것을 알아냈습니다. 이 예산은 스컬프팅한 지형에 중요합니다. "편집된 토양"을 위해 5번째 레이어를 추가하거나 깎아낸 표면의 블렌딩을 바꾸면, 여유가 정확히 얼마인지 압니다.

스파이크 09(CSM 섀도 예산): 1024^2 해상도 캐스케이드 3개를 가진 캐스케이드 섀도 맵. 섀도 비용을 약 1.5ms로 측정했습니다. 스컬프팅한 지형은 섀도 맵을 바꾸지만, 비용은 지형 형태와 관계없이 일정합니다.

스파이크 10(Geometry Clipmap + Geomorphing): 팝인을 없애기 위해 LOD 레벨 사이를 geomorphing하는 중첩 clipmap 링. 일정한 삼각형 수가 예측 가능한 GPU 비용을 뜻합니다. Geomorphing은 스컬프팅에 중요합니다. 플레이어가 LOD 경계 근처를 스컬프팅하면, LOD 레벨 사이의 모핑이 편집을 반영해야 합니다. 편집이 고해상도 링에만 있으면 geomorph 목표가 틀립니다.

스파이크 11(하이트맵 chunk 스트리밍): LOD 레벨별로 로드됨/로드 중/언로드됨 상태를 보여주는 시각적 그리드를 갖춘 더 고급 chunk 스트리밍. 스트리밍 예산을 확립했습니다. 프레임당 최대 로드 chunk 수, LOD 업그레이드가 필요한 chunk의 우선순위. 스컬프팅은 새 우선순위 신호를 추가합니다. 플레이어가 적극적으로 편집 중인 chunk는 절대 언로드되면 안 됩니다.

스파이크 12-14: WebGPU + Three.js 통합

스파이크 12(WebGPU Marching Cubes): 첫 볼류메트릭 스파이크. 애니메이션 구 동굴을 가진 네 개의 64^3 SDF chunk가 전부 GPU에서 돌아갑니다. 원시 WebGPU를 씁니다. SDF 평가용 컴퓨트 파이프라인, Twinklebare 케이스 테이블(256개 구성, 각각 16개 항목)을 가진 MC 추출, 원자적 정점 카운터, 인다이렉트 드로우. 성능 목표는 chunk당 <4ms, 4개 전부 <12ms였습니다. 이것은 GPU MC가 브라우저에서 실시간 재메싱에 충분히 빠르다는 것을 검증했습니다. 이후의 모든 볼류메트릭 스파이크가 여기 정의된 MC 케이스 테이블과 WGSL 셰이더를 재사용합니다.

스파이크 13(스파이크 12에서 기준선 리셋): 스파이크 12의 원시 WebGPU 드로우 경로를 Three.js의 WebGPURenderer 안에서 돌도록 이식해, 백엔드의 device에 직접 접근합니다. 렌더 파이프라인은 여전히 원시 WebGPU를 씁니다(struct Vertex vec4+vec4를 가진 drawIndirect). 이것은 커스텀 컴퓨트와 Three.js 장면 렌더링이 같은 GPU 디바이스에서 공존할 수 있음을 증명했습니다.

스파이크 14(Three.js WebGPU 점진적 견고화): 원시 렌더 파이프라인을 위치와 법선용 Three.js StorageBufferAttribute로 교체했습니다. MC 컴퓨트가 GPU에 상주하는 이 버퍼에 직접 씁니다. drawIndirect 버퍼가 Three.js가 그리는 정점 수를 제어합니다. 이것이 이후 모든 스파이크가 쓰는 패턴입니다. 컴퓨트는 원시 WebGPU로 두고, 렌더링은 Three.js 장면 그래프를 거칩니다. 이 스파이크들 전반에서 Three.js 버전은 WebGPU 백엔드가 안정화되면서 0.170.0에서 0.172.0으로 발전했습니다.

스파이크 15-17: Transvoxel LOD 이음매

스파이크 15(Transvoxel 이음매 스캐폴드): 세 구역 아키텍처를 추가했습니다. MC chunk(볼류메트릭 중심), 전이 스트립(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(Dual MC 1x/2x LOD): 서로 다른 해상도의 MC chunk 두 개를 나란히. 고해상도: cell_scale=1.0의 62 셀. 저해상도: cell_scale=2.0의 31 셀. MC 셰이더가 cell_scalegrid_points uniform을 얻었습니다. transition_shrink를 도입했습니다. 저해상도 chunk의 face-0 경계 정점이 cell_scale의 15%만큼 안쪽으로 당겨져, Transvoxel 전이 셀이 z-fighting 없이 채우는 얇은 간극을 만듭니다. 이것이 프로덕션 시스템에 필요한 LOD 모델입니다. 가까운 chunk는 전체 해상도, 먼 chunk는 절반 해상도, 모든 경계에 Transvoxel.

스파이크 18-21: Transvoxel 코너 케이스와 GPU 가속

이 네 스파이크는 각각 Transvoxel 구현의 특정 실패 케이스를 해결했습니다. 묶어 버리면 별개의 문제들이 가려집니다.

스파이크 18(하이트맵 2:1 이음매): 한쪽이 다른 쪽의 두 배 해상도인 순수 하이트맵 경계에 Transvoxel을 적용했습니다. MC는 관여하지 않습니다. 62 셀과 31 셀 하이트맵 chunk 사이의 이음매가 15% 저면 수축을 곁들인 Transvoxel 전이 테이블에서 생성됩니다. 이것은 Transvoxel이 MC뿐 아니라 하이트맵만 있는 경우에도 작동함을 검증했습니다.

스파이크 19(64/32/32/16 코너 그리드): 가장 어려운 이음매 케이스. 서로 다른 해상도(64, 32, 32, 16 셀)의 chunk 네 개가 코너 점에서 만납니다. 이음매 시스템은 네 에지(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 chunk와 네 이음매 모두 GPU에서 생성되고, 정점 수는 원자적 카운터로 관리되며 drawIndirect로 그려집니다. 이것이 매끄러운 LOD 전이를 갖춘 다중 해상도 볼류메트릭 지형의 완전한 GPU 파이프라인입니다.

스파이크 22-24: 하이브리드 아키텍처

스파이크 22(하이브리드 MC/하이트맵 정책): 핵심 아키텍처 스파이크. chunk는 기본적으로 하이트맵입니다. 애니메이션 변형 구가 어떤 chunk의 AABB와 교차하면, 그 chunk가 MC 모드로 전환됩니다. 나머지는 정적 하이트맵 메시로 남습니다. 레이아웃: 서로 다른 해상도의 64, 32/32, 16 셀 chunk. Transvoxel 이음매가 MC-하이트맵 전이를 포함한 모든 경계를 처리합니다. 스파이크는 MC chunk 수 대 HM chunk 수와 프레임당 정점 오버플로를 추적합니다.

스파이크 23(정책 주도 chunk 모드): 스파이크 22 위에 패치로 로드됩니다. 카메라 거리 히스테리시스(카메라가 임계값 근처에 있을 때 chunk가 모드 사이를 깜빡이지 않음)와 편집 마스크(변형된 chunk는 변형 소스가 멀어져도 MC 모드를 유지)를 추가했습니다. 이것이 스컬프팅에 필요한 "끈끈한 편집" 동작입니다. 플레이어가 동굴을 깎으면, 그 chunk는 영원히 볼류메트릭으로 남습니다.

스파이크 24(정책 + Clipmap 링): 가장 고급 스파이크. 스파이크 23의 근거리 정책 시스템과 스파이크 10의 원거리 geometry clipmap 링을 결합합니다. Three.js 0.183.1로 업그레이드했습니다. 근거리는 64/32/16 해상도에서 Transvoxel 이음매를 가진 HM/MC 하이브리드를 씁니다. 원거리는 카메라를 따라가는 정적 중심 clipmap 링을 씁니다. 이것이 완전한 지형 렌더링 아키텍처입니다. 필요한 곳에서는 청크화된 볼류메트릭 스컬프팅, 그 외 모든 곳에서는 저렴한 clipmap 지형.

왜 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/ 코드에 chunk 하이트맵을 수정하는 브러시 도구를 추가합니다. 이것은 기존 WebGL 렌더러 안에서 작동하며 WebGPU가 필요 없습니다.

브러시 입력: placement.tsPlacementTool 패턴을 따릅니다. 이미 chunkManager.getChunkMeshes()를 맞히는 Raycaster가 있고 충돌 점에서 고스트 메시를 추적합니다. TerrainBrushTool은 같은 레이캐스트를 하되 오브젝트를 놓는 대신 chunk 하이트맵을 수정합니다. World.onMouseDown 핸들러는 이미 도구 상태에 따라 디스패치합니다.

Chunk 수정(Chunk.applyBrush): 브러시 월드 위치를 하이트맵 그리드 좌표로 매핑합니다. 브러시 반경 안의 각 그리드 점에 대해 감쇠 가중 변위를 계산해 하이트맵 값에 더하거나 뺍니다. 그다음 메시를 업데이트합니다. 수정된 하이트맵에서 정점 Y 위치를 설정하고, 중심 차분으로 법선을 재계산하고(chunk.ts 155-158줄에서 이미 쓰는 것과 같은 terrainHeight(wx +/- eps, wz) 패턴), terrain-material.tscreateSplatMap()로 영향받은 영역의 splat map을 재생성해 경사 기반 텍스처 블렌딩을 업데이트합니다.

캐릭터 컨트롤러: CharacterController.update()는 접지를 위해 매 프레임 getHeight()를 호출합니다. ChunkManager.getHeight()Chunk.sampleHeight()에 위임하고, 이것은 chunk의 heightmap Float32Array에서 읽습니다. 우리가 그 배열을 직접 수정하므로, 캐릭터 컨트롤러는 추가 배선 없이 다음 프레임에 변화를 받아들입니다.

오브젝트 무효화: chunk.ts의 나무 인스턴스는 생성 시점에 terrainHeight()를 샘플링해 배치됩니다. 스컬프팅 후, 영향받은 영역의 나무가 잘못된 높이에 있을 수 있습니다. 1단계는 이것을 미룰 수 있습니다(작은 편집에서는 나무가 약간 떠 있음). 2단계는 높이를 다시 샘플링하고 인스턴스 행렬을 다시 만드는 chunk.invalidateObjects()가 필요합니다. resolveCollisions()에 쓰는 콜라이더도 마찬가지입니다.

Rapier 물리(통합된 경우): 스파이크 02는 하이트필드 콜라이더가 작동함을 증명했습니다. Rapier가 활성 상태라면, 수정된 chunk의 하이트필드 콜라이더를 다시 만들거나 패치해야 합니다. Rapier의 ColliderDesc.heightfield()는 평평한 Float32Array를 받으므로, 직접 교체입니다.

네트워크 동기화: protocol.tsMsgType.TerrainEdit = 10을 추가합니다.

typescript
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 스토리지에 저장된 chunk별 편집 로그에 덧붙입니다. 늦게 합류하는 클라이언트는 Snapshot 메시지에서 편집 로그를 받아 재생해 지형 상태를 재구성합니다. 모든 클라이언트가 동일한 결정론적 브러시 함수를 적용하므로, 동일한 하이트맵으로 수렴합니다.

Chunk 언로드/리로드: 스파이크 06과 스파이크 11이 스트리밍 패턴을 확립했습니다. chunk가 언로드되고 나중에 다시 로드될 때, 그 chunk의 편집 로그가 기저 절차적 하이트맵에 대해 재생되어야 합니다. 편집 로그는 서버 측(Durable Object)에 저장되고 Snapshot 메시지에 포함됩니다.

2단계: 스파이크 22-24 아키텍처를 쓴 볼류메트릭 스컬프팅

스파이크 24 파이프라인을 프로덕션 월드에 이식합니다. 플레이어가 표면 아래를 스컬프팅하면(동굴 깎기, 터널 파기), 영향받은 chunk가 하이트맵 모드에서 MC 모드로 전환됩니다.

WebGPU 렌더러 마이그레이션: 스파이크 13-14는 Three.js의 WebGPURenderer가 장면 그래프와 나란히 커스텀 컴퓨트를 호스팅할 수 있음을 증명했습니다. 프로덕션 월드는 MC chunk에 StorageBufferAttribute를 쓰며 WebGLRenderer에서 WebGPURenderer로 옮깁니다. WebGPU를 쓸 수 없을 때는 1단계의 하이트맵 전용 경로로 폴백합니다.

Chunk별 SDF 할당: 스파이크 22의 하이브리드 패턴을 따릅니다. 각 chunk는 하이트맵으로 시작합니다. 첫 볼류메트릭 브러시 스트로크에서 64^3 Float32Array를 할당하고, 하이트맵을 샘플링해 초기화하고(각 점의 SDF 값은 world.y - heightmap_value), MC 렌더링으로 전환합니다. 스파이크 23의 정책 시스템이 그 chunk를 영구적으로 MC 모드로 유지함을 보장합니다(편집 마스크의 "끈끈한 편집" 동작).

SDF 셰이더의 공유 하이트맵: 스파이크 16의 height_at() 함수. chunk의 하이트맵을 GPU 스토리지 버퍼에 업로드합니다. SDF 컴퓨트 셰이더는 max(height_sdf, edit_sdf)를 평가하며, 여기서 height_sdf = world.y - height_at(world.xz)이고 edit_sdf는 브러시 수정을 담습니다. MC와 하이트맵 chunk는 경계에서 기준값에 동의합니다.

Transvoxel 이음매: 스파이크 15-21의 전체 스택. MC-하이트맵 경계는 수축 간극을 가진 전이 셀을 씁니다. 서로 다른 해상도의 MC-MC 경계는 스파이크 17의 듀얼 LOD 패턴을 씁니다. 스파이크 19의 코너 케이스가 4방향 교차를 처리합니다. 스파이크 21의 GPU 컴퓨트가 같은 디스패치에서 모든 이음매 지오메트리를 생성합니다.

Clipmap 원거리: 스컬프팅 범위 밖 지형을 위한 스파이크 24의 clipmap 링. 스컬프팅은 이 링들을 절대 건드리지 않습니다. 이들은 기저 절차적 하이트맵을 샘플링합니다.

Geomorphing: 스파이크 10의 geomorphing이 LOD 전이의 팝인을 없앱니다. 편집된 chunk에 대해서는 geomorph 목표가 편집을 포함해야 합니다. chunk가 LOD0에서 MC이고 그 LOD1 이웃이 하이트맵이면, geomorph가 두 표현 사이를 블렌딩합니다. 이것은 더 낮은 LOD에서도 편집 로그를 샘플링해야 함을 뜻합니다.

머티리얼 예산: 스파이크 08은 45+ FPS에서 4레이어 트라이플래너 + 노멀을 벤치마크했습니다. MC chunk도 같은 머티리얼이 필요합니다. splat map은 하이트맵 경사 대신 SDF 기울기에서 생성할 수 있습니다(가파름 = 바위, 평평함 = 잔디). 이것은 4레이어 예산 안에 머뭅니다.

식생 무효화: 스파이크 07의 밀도 맵 식생은 지형 높이와 경사에 의존합니다. chunk가 MC 모드로 전환되면, SDF 표면을 샘플링해 나무 인스턴스를 다시 생성해야 합니다. 오버행이나 동굴 안의 나무는 컬링해야 합니다. chunk.ts의 인스턴스화된 메시 행렬을 새 표면에서 다시 만듭니다.

3단계: 실행 취소/다시 실행과 네트워크 동기화를 위한 CSG 편집 트리

원시 SDF 변형을 CSG 연산 트리로 교체합니다. 각 브러시 스트로크가 연산(추가, 빼기, 매끄러운 블렌드)을 가진 원시 형상(구, 캡슐, 박스)을 덧붙입니다. SDF는 트리에서 다시 계산됩니다.

이점:

  • 비파괴적: 어떤 편집이든 트리에서 제거해 실행 취소 가능
  • 네트워크 효율적: 원시 필드 값이 아니라 CSG 연산을 브로드캐스트
  • 결정론적: 모든 클라이언트가 동일한 연산 시퀀스에서 동일한 SDF를 구축
  • ALICE-SDF의 CSG 트리 diff/patch가 네트워크 전반의 대역폭 효율적 동기화와 실행 취소/다시 실행을 제공

Durable Object 스토리지: chunk별 편집 트리가 1단계의 평평한 편집 로그를 대체합니다. WorldChunkDO는 원시 하이트맵 델타가 아니라 CSG 트리 구조를 저장합니다. Snapshot 메시지는 트리를 포함하고, 늦게 합류하는 클라이언트는 그것을 평가해 로컬 SDF를 만듭니다.

4단계: 협업 스컬프팅

서버 순서의 연산 재생으로 동시 편집 지원을 추가합니다. Durable Object가 각 편집에 타임스탬프를 찍고 순서대로 브로드캐스트합니다. 늦게 합류하는 클라이언트는 연산 로그를 받아 월드 상태를 재구성합니다. 기존 Snapshot 메시지 타입이 chunk별 지형 편집 이력을 포함하도록 확장됩니다.

브러시 스트로크는 작고 국소적이며 가산/감산적이므로, 약간 순서가 뒤바뀐 동시 편집의 시각적 결과는 보통 "올바른" 순서와 구별되지 않습니다. 서버 순서의 last-write-wins로 충분합니다. 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천만 tri/sec)
  • 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 엔진 (brick map + geometry clipmap, 2026년 1월)

네트워크:

  • "Optimizing payload size for voxel state synchronization" (Oulu, 2024)
  • Teardown 멀티플레이어 (준결정론적 파괴 동기화, 2026년 3월)
  • cSculpt (다중 해상도 병합을 갖춘 협업 메시 스컬프팅)