
64kB 인트로에서의 절차적 3D 메쉬 생성 (Procedural 3D mesh generation)
요약
64kB라는 극도로 제한된 용량 내에서 3D 그래픽을 구현하기 위해 절차적 메쉬 생성 기술을 사용하는 방법을 설명합니다. 큐브의 변형과 Catmull-Rom 스플라인을 이용한 회전체(surfaces of revolution) 생성 방식을 통해 최소한의 데이터로 복잡한 기하학적 형상을 만드는 과정을 다룹니다.
핵심 포인트
- 용량 제한(Size coding) 환경에서는 전통적인 3D 모델링 대신 절차적 기하학(Procedural geometry)이 필수적임
- 변형된 큐브(Deformed cubes)를 조립하여 테이블, 선반 등 다양한 구조물을 생성 가능
- Catmull-Rom 스플라인과 회전체 원리를 결합하여 적은 데이터로 정교한 3D 모델 구현
- 기술적 제약이 창의적인 설계와 코드 최적화의 동력이 됨
이 기사는 H – Immersion 제작 과정에 관한 시리즈의 후속편입니다 (YouTube에서 데모를 확인하세요). 첫 번째와 두 번째 파트는 여기서 읽을 수 있습니다: A dive into the making of Immersion ; Texturing a 64kB intro.
이전 파트에서는 H – Immersion에서 텍스처가 어떻게 생성되는지 살펴보았습니다. 이번에는 용량 제한 코딩 (size coding)을 위한 또 다른 중요한 도구인 절차적 기하학 (procedural geometry)을 살펴보겠습니다.
더 구체적으로, 우리의 렌더링 (rendering)은 전통적인 폴리곤 (polygons)을 사용하기 때문에, 우리는 절차적 메쉬 생성기 (procedural mesh generator)를 작성했습니다. 몇 가지 잘 선택된 기술을 통해 어떻게 다양한 모양을 만들거나, 혹은 시청자가 그렇게 믿게 만들 수 있는지 알아보겠습니다.
첫 번째, 큐브 (Cubes)
데모 제작을 시작했을 때, 64kB 제한은 위협적으로 느껴졌습니다. 우리는 절차적 메쉬 생성 (procedural mesh generation)에 대해 전혀 몰랐고, 렌더링 (rendering), 카메라 (camera), 텍스처 (textures), 스토리 (story)... 말하자면 모든 것에 이미 할 일이 많았습니다. 그래서 우리의 첫 번째 데모인 B – Incubation에서는 3D 모델링 (3D modeling)을 아예 건너뛰기로 조기에 결정했습니다. 대신, 우리는 큐브 (cubes)만을 사용하기로 선택했고 이 개념을 중심으로 데모를 설계했습니다.
이것은 기술적 제약이 어떻게 창의적인 도전이 될 수 있는지, 그리고 어떻게 우리로 하여금 새로운 아이디어를 찾고 예상치 못한 일을 하도록 강제하는지에 대한 예시입니다. 우리의 모든 64kB 인트로 (intros)에서, 용량 제한은 때때로 작고 예상치 못한 방식으로 디자인에 영향을 미칩니다. 우리는 이 장벽을 피하기 위해 끊임없이 트릭, 코드 재사용 (code reuse), 그리고 우회 방법 (workarounds)을 찾고 있습니다.
혁명! (Revolution!)
이 첫 번째 64kB 이후, 마침내 절차적 메쉬 (procedural meshes)를 도입할 때가 되었습니다! F – Felix’s Workshop를 위해 우리는 몇 가지 기본적인 메쉬 생성 (mesh generation)을 구현했습니다. 이 데모는 좋은 피드백을 받았지만, 코드는 아마 많은 사람들이 기대하는 것보다 더 단순할 것입니다.
아래 이미지를 주의 깊게 살펴보면, 모든 기하학적 형상 (geometry)에 단 두 종류의 모양만 사용되었다는 것을 눈치챌 수도 있습니다. 테이블, 선반, 벽과 같은 일부 요소들은 변형된 큐브 (deformed cubes)를 조립하여 만들어졌습니다. 나머지는 다양한 모양을 가지고 있지만, 모두 일종의 원통형입니다. 실제로 우리는 회전체 (surfaces of revolution)를 사용하여 그것들을 구축했습니다.
아이디어는 간단한 스플라인 (splines)을 그린 다음, 이를 축을 중심으로 회전시켜 3D 모델을 만드는 것입니다. 다음은 체스판의 폰 (pawns)을 만들기 위해 사용한 스플라인입니다.
왼쪽의 숫자들은 제어점 (control points) 목록의 2D 좌표입니다. 우리는 Catmull-Rom 스플라인 (Catmull-Rom splines)을 사용하여 점들 사이를 보간 (interpolate)합니다. Catmull-Rom은 1974년에 처음 발표된 훌륭한 알고리즘으로, Iñigo Quilez가 상세히 설명하고 권장하는 방식입니다. 오른쪽의 모양은 이 점들의 목록에 해당 기술을 적용한 결과 (대칭 적용 후)입니다.
완료되면, 스플라인을 따라 면 (faces)을 생성함으로써 데이터를 3D로 변환할 수 있습니다. 약간의 변화만 주면 다른 체스 기물들도 만들 수 있습니다. 이것이 최종 결과물입니다.
이를 위해 몇 바이트가 필요할까요? 그리 많지는 않습니다. 특히 데모 전반에 걸쳐 이 기술을 다양한 방식으로 재사용할 때는 더욱 그렇습니다. 만약 각 숫자를 1바이트에 저장한다면, 폰을 표현하는 데 40바이트 미만의 데이터가 필요할 것입니다... 그리고 이는 압축 (compression) 단계를 고려하지 않은 수치입니다.
체스판의 소스 코드를 살펴보면, 실제로 0과 255 사이의 정수들을 저장하기 위해 부동 소수점 (floating-point) 타입을 사용하고 있음을 알 수 있습니다. 이 32비트 부동 소수점들은 각각 4바이트를 사용합니다. 이것이 바이트 낭비일까요? 꼭 그렇지는 않습니다. 앞서 언급했듯이 프로그램은 압축됩니다. 해당 부동 소수점들의 이진 표현 (binary representation)을 확인해 보면, 값들이 매우 유사하며 끝에 많은 0이 붙어 있다는 것을 알 수 있습니다. 압축 도구 (kkrunchy)는 이를 효율적으로 패킹 (pack)할 것이며, 이는 우리가 똑똑하게 처리하려고 시도했을 때보다 더 작아질 수 있습니다.
더 나아가, 델타 인코딩 (delta encoding)을 통해 압축률을 높일 수 있지만, 이는 저장할 데이터가 충분할 때만 이점이 있습니다. 부동 소수점에 대해서는 더 할 말이 많으며, 우리는 이전에 '부동 소수점 숫자를 더 작게 만들기 (Making floating point numbers smaller)'라는 글에서 이 주제를 다룬 적이 있습니다.
확장 및 결합
위의 장면에서 드럼 (drum)이 뚜렷한 면들을 가지고 있다는 점에 주목하십시오. 우리의 함수는 생성할 면의 개수를 제어할 수 있게 해주므로, 모든 것이 완벽하게 원형일 필요는 없습니다. 예를 들어, 책상 위의 연필은 육각형 모양입니다.
체스 장면의 배경에서는 벽난로 상단의 흰색 장식조차 이 기술로 만들어졌습니다. 이는 뾰족한 팔각형 모양으로 구축됩니다. 그런 다음 중앙 부분이 한 축을 따라 길게 늘려져, 모서리가 깎인 (beveled) 커다란 형태가 됩니다. 우리는 형태를 축을 따라 늘릴 뿐만 아니라, 곡선을 따라 생성할 수도 있습니다. 기차 경사로(train ramp)가 만들어지는 방식이 바로 이것이며, 그 경로는 또 다른 스플라인 (spline)으로 기술됩니다.
Felix’s Workshop을 다시 시청해 보면, 모든 것이 회전체 (revolution) 또는 큐브 (cube)에서 비롯된다는 것을 알 수 있습니다. 우리는 이 두 가지 기본 도형 (primitives)을 결합하는 것만으로도 매우 폭넓은 객체들을 만들어냅니다.
큐브 키우기 (Growing Cubes)
단순한 큐브를 결합하고 변형하는 것 또한 많은 잠재력을 가지고 있습니다. H – Immersion의 식생 (vegetation)을 위해, 우리는 직육면체 (cuboid)에서 시작하여 이를 약간 변형했습니다. 그런 다음 가상의 축을 중심으로 수직으로 배치된 메쉬 (mesh)의 복사본을 무작위 크기와 방향으로 많이 만들었습니다. 이렇게 하면 대략적으로 식물처럼 보이는 무언가가 생성됩니다. 우리는 더 많은 식물을 만들기 위해 무작위 매개변수 (parameters)를 사용하여 이 과정을 반복합니다.
이것은 매우 거칠어 보이며, 여러분은 아마 형태를 정교하게 다듬기 위한 다음 단계가 무엇인지 읽게 될 것이라 예상할 것입니다. 하지만 다음 단계는 없습니다. 이것이 최종 메쉬입니다. 우리는 이를 위해 커스텀 텍스처 (custom texture)조차 만들지 않았습니다. 대신, 그 메쉬에 그냥 지면 텍스처 (ground texture)를 적용했을 뿐입니다!
하지만 데모 중에 이 효과는 렌더링 (rendering), 빛과 그림자, 그리고 단순하지만 설득력 있는 애니메이션 (animation) 덕분에 충분히 잘 작동합니다. 편집 (editing) 또한 큰 도움이 됩니다. 형태와 움직임이 분위기를 조성하지만, 시청자는 카메라가 이동하기 전까지 세부 사항을 알아차릴 시간이 없습니다. 때로는 적절한 분위기로 형태를 환기시키는 것이 공들여 모델링하는 것보다 더 효과적입니다.
큐브 돌출시키기 (Extruding Cubes)
어느 시점에 도달했을 때, 우리는 더 복잡한 메쉬 (mesh)를 갖기를 원했습니다. 평소처럼 우리는 우리가 사랑하는 큐브 (cube)에서 시작하여 이를 수정하기로 결정했습니다. 단순히 큐브를 변형하는 것만으로는 여전히... 음, 직육면체 (cuboid)에 머물게 될 것입니다. 그래서 우리는 그 이상의 무언가가 필요했습니다. 이때 돌출 (extrusion)이 등장합니다. 우리는 면 (face)을 선택하고, 그것을 돌출시킵니다. 이 연산은 새로운 면을 생성하며, 우리는 이 면을 객체로부터 끌어내거나, 크기를 조절하거나, 원하는 방식대로 변형할 수 있습니다.
우리는 원하는 형태를 만들기 위해 이 과정을 여러 번 반복합니다. 각 돌출은 더 많은 디테일을 추가할 것입니다. 결과물은 종종 로우 폴리 (low poly) 형태이지만, 우리는 Catmull-Clark 세분화 (subdivision) 알고리즘을 사용하여 결과물을 부드럽게 만듭니다. 이 접근 방식은 Qoob 모델링 도구에서 영감을 얻었습니다.
우리가 설명한 방식은 데모의 여러 곳에서 장식용 소품으로 사용된 작은 조각상들을 생성하기 위해 우리가 실제로 수행한 작업과 정확히 일치합니다:
모든 것이 절차적 생성 (procedural generation)이기 때문에, 우리는 함수에 인자 (arguments)를 전달할 수 있습니다. 이 인자들은 다리, 팔 등의 각도를 제어할 수 있습니다. 무작위 매개변수 (random parameters)를 사용하여 수많은 조각상을 생성하는 루프를 작성하면, 짜잔! 시각적으로 지루해지지 않을 만큼 충분한 변형을 얻을 수 있습니다.
또한 더 나은 결과를 위해 하드코딩된 매개변수 (hard-coded parameters)를 가진 두 개의 조각상도 만들었습니다. 다음은 텍스처 (textures)와 조명 (lighting)을 적용한 후의 모습입니다.
그리고 당연히 조각상들은 재사용됩니다. 우리는 회전체 (revolution surface)로 생성된 분수 위에 이러한 조각상들의 변형들을 배치하기도 했습니다.
마칭 큐브 (Marching Cubes)
신전에서는 왕좌에 앉아 있는 거대한 포세이돈 조각상을 보여주고 싶었습니다. 작은 조각상들에 사용된 기술은 더 많은 초점이 맞춰질 모델에 사용하기에는 너무 거칠었습니다. 포세이돈은 거대하며 우리는 더 많은 디테일을 원했습니다. 데모에는 많은 콘텐츠가 담겨 있어 모든 것을 맞추는 것이 도전 과제였습니다. 많은 크기 최적화 (size optimization) 작업 끝에, 우리는 약 1킬로바이트 (kilobyte) 정도의 여유 공간을 확보할 수 있었습니다. 우리는 이를 포세이돈을 위한 더 나은 모델을 얻는 데 사용하기로 결정했습니다.
이를 위해 우리는 지금까지 보아온 것과는 완전히 다른 기술인 부호 거리 함수 (Signed-Distance Fields, SDF)로 표현되는 암시적 표면 (Implicit surface) 기술을 사용했습니다. 이는 4kB 인트로에서 매우 인기 있는 기술로, 보통 레이 마칭 (Ray marching) 알고리즘과 함께 사용하여 결과를 생성하고 스크린 스페이스 셰이더 (Screenspace shader)로 구현됩니다. 하지만 우리의 렌더링은 메쉬 (Mesh)를 기반으로 하기 때문에, 우리는 레이 마칭 대신 마칭 큐브 (Marching cube) 알고리즘을 사용하여 SDF 함수를 평가함으로써 폴리곤 메쉬 (Polygon mesh)를 생성했습니다. 우리는 휴머노이드 형태를 일련의 세그먼트 (Segments)로 구축하였으며, 유기적인 느낌을 주기 위해 약간의 미세 조정을 거쳤습니다.
우리가 감당할 수 있는 디테일에는 한계가 있었고, 인간을 모델링하는 것이 어렵다는 점과 사람들이 인간과 유사한 모델의 결함을 매우 잘 찾아낸다는 점은 말할 것도 없었습니다. 우리는 생성된 메쉬의 낮은 해상도를 오히려 이점으로 활용했습니다. (SDF 함수로부터 법선 (Normals)을 유도하는 대신) 최종 메쉬에서 법선을 평가하는 것이 눈에 보이는 아티팩트 (Artifacts)를 생성한다는 사실이 밝혀졌는데, 표면에 매끄러운 주름과 모서리가 가득하게 됩니다. 이러한 매우 거친 외형은 일종의 조각상 같은 느낌을 줄 수 있습니다. 우리는 그 위에 조명과 영화 촬영 기법 (Cinematographic techniques)을 더해 시청자가 디테일을 스스로 채워 넣도록 유도했습니다. 최종 장면에서 조각상은 실제보다 더 정교해 보입니다.
온라인 큐브 (Online Cubes)
창의적인 활동에서는 디자인을 빠르게 반복 (Iterate)하는 것이 매우 중요할 때가 많습니다. 첫 시도에 모든 것을 완벽하게 할 수는 없으므로, 변경 사항을 쉽게 만들고, 반복하고, 탐색하며, 무엇이 효과적인지 확인할 수 있어야 합니다.
어느 시점에 우리는 텍스처를 했던 것과 마찬가지로 메쉬 생성기를 웹 서버에 올렸습니다. 웹 페이지에는 C++ 코드를 작성할 수 있는 텍스트 영역 (Textarea)이 있었습니다. 버튼을 클릭하면 서버에서 코드를 컴파일하고 메쉬를 JSON 형식으로 반환했습니다. 웹 페이지는 three.js를 사용하여 결과를 표시했으므로, 마우스로 모델을 보고 회전할 수 있었습니다. Shadertoy에서와 마찬가지로, 이를 통해 우리는 아이디어를 빠르게 시도하고, 팀과 공유하며, 다른 모델을 포크 (Fork)하거나 미세 조정할 수 있었습니다.
우리는 이후 첫 번째 파트에서 언급했던 다른 솔루션인 C++ 재컴파일 (recompilation) 방식으로 전환했습니다.
결론
메쉬 생성 (Mesh generation)은 텍스처 생성 (texture generation)보다 설계하기가 분명히 더 어렵고 구현 과정도 더 복잡합니다. 텍스처는 단순히 평면적인 표면인 반면, 메쉬는 서로 다른 위상 (topologies)을 가지고 있어 새로운 복잡성이 추가되기 때문입니다.
하지만 텍스처와 마찬가지로, 가장 단순한 빌딩 블록 (building blocks)이라 할지라도 다양한 방식으로 조합할 수만 있다면 탐구할 수 있는 폭넓은 가능성을 제공할 수 있습니다. 몇 가지 단순한 요소들을 창의적으로 사용하면 매우 다양한 형태를 만들어낼 수 있습니다.
게다가 적어도 두 가지 사례를 통해 확인했듯이, 암시 (suggestion)의 힘은 중요한 역할을 할 수 있으며, 가용한 빌딩 블록만으로는 지루하거나 심지어 불가능할 수도 있는 모델링 작업을 대체할 수 있습니다.
우리가 입증하고자 했던 것처럼, 이 두 가지 관찰 내용을 모두 활용하면 큰 도움이 될 수 있습니다. 핵심은 모델링 작업과 표현력 (expressiveness) 사이의 적절한 균형을 찾는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN Game Dev의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기