Cap Mesh Generation
절단면 재구성 알고리즘 - Arc-Length 기반 Triangulation
2025.01 | 개발 기간 2주
기술 스택
프로젝트 개요
이 프로젝트는 Mesh Slicing 프로젝트의 후속 작업으로, 메쉬를 절단한 후 발생하는 빈 공간을 채우는 Cap Mesh를 생성하는 알고리즘을 개발하는 것이 목표였습니다. 단순히 메쉬를 자르는 것만으로는 절단면에 구멍이 생겨 시각적으로 부자연스럽고 물리 충돌 문제가 발생합니다.
절단면을 완벽하게 막는 새로운 메쉬를 생성하여 기존 메쉬와 완벽하게 연결해야 했습니다. 이는 단순한 문제처럼 보이지만, Floating Point 정밀도, Boundary Loop 추출, 자연스러운 Triangulation 등 여러 기술적 도전이 있었습니다.
해결해야 할 문제
구멍 문제
메쉬를 절단하면 내부가 노출되어 구멍이 발생합니다. 이는 여러 문제를 야기합니다.
- 시각적으로 부자연스러움 - 메쉬 내부가 그대로 보임
- 물리 충돌 문제 발생 - Collider가 제대로 작동하지 않음
- 렌더링 오류 - 뒷면(Backface)이 보이는 문제
Cap Mesh 필요성
절단면을 막는 새로운 메쉬(Cap Mesh)가 필요합니다. 이 메쉬는 다음 조건을 만족해야 합니다.
- 기존 메쉬와 완벽하게 연결되어야 함
- 빈틈없이 절단면을 채워야 함
- 실시간으로 생성되어야 함 (목표: ~1ms)
- 시각적으로 자연스러워야 함
기술적 도전
- Boundary Loop 추출 - 절단면의 경계선을 정확히 찾아야 함
- 자연스러운 Triangulation - 삼각형 배치가 자연스러워야 함
- Floating Point 정밀도 - 미세한 오차로 Loop가 닫히지 않을 수 있음
- 실시간 처리 - VR 환경에서 지연 없이 작동해야 함
핵심 과제
"메쉬 절단은 Slicing만으로 끝나지 않는다. 절단면을 완벽하게 재구성해야만 진정한 완성이다."
개발 과정 - 3번의 시도와 돌파구
완벽한 솔루션을 찾기까지 3번의 시도와 실패를 거쳤습니다. 각 시도마다 문제점을 파악하고 개선하는 과정을 통해 최종 솔루션에 도달했습니다.
Point Cloud Reconstruction
실패 - 50ms접근 방법:
- Boundary 정점들을 점 구름(Point Cloud)으로 취급
- Delaunay Triangulation 적용
- 자동으로 최적 삼각형 생성
왜 이 방법을 선택했나:
- 학술적으로 검증됨 - 논문에서 많이 사용되는 방법
- 이론적으로 완벽함 - 최적의 삼각형 배치 보장
- 다른 대안들의 한계:
- Ear Clipping: O(n²) 복잡도로 너무 느림
- Sweep Line: 2D에서만 작동, 3D에서 사용 불가
문제점:
- 너무 복잡함: Delaunay 알고리즘 구현 난이도가 매우 높음
- 성능 문제: 처리 시간이 ~50ms로 목표의 50배
- 이상한 메쉬 생성: Point Cloud 방식이라 공간 정보를 제대로 활용하지 못함
- Unity에 Delaunay 라이브러리 부재
배운 점:
학술적으로 완벽한 방법이 실무에서 항상 좋은 것은 아니다. 더 단순한 접근이 필요하다는 것을 깨달았습니다.
Ear Clipping Algorithm
실패 - Loop 문제접근 방법:
- Boundary Loop을 순차적으로 추출
- Ear Clipping으로 삼각형화
- 표준 알고리즘으로 안정성 확보
왜 Ear Clipping을 선택했나:
- Delaunay보다 구현이 쉬움
- 2D 변환 가능 - Plane projection 적용
- 잘 알려진 알고리즘으로 안정성 확보
- 다른 방법들:
- Monotone Polygon: Boundary가 단조롭지 않아 적용 불가
- Greedy Triangulation: 품질 보장이 없음
치명적 문제:
- Boundary Loop이 닫히지 않음: 시작점과 끝점이 일치하지 않음
- Floating Point 오차로 인한 불일치 - 0.0001 단위의 미세한 차이
- Vector3를 Dictionary Key로 사용 시도 → 실패
- GetHashCode()가 불안정 - 같은 좌표도 다른 해시값
- Floating point 비교의 근본적 한계
배운 점:
Floating Point 정밀도 문제의 심각성을 깨달았습니다. Vector3를 Dictionary key로 절대 사용해서는 안 되며, 다른 방식의 Loop 추출이 필요했습니다.
Fan Triangulation (부채꼴 방식)
부분 성공 - 0.5ms접근 방법:
- Boundary 정점들의 중심점 계산
- 중심점에서 각 정점으로 방사형 연결
- 부채꼴(Fan) 모양의 삼각형 생성
왜 Fan 방식을 선택했나:
- 극도로 단순함 - 10줄 코드로 구현 가능
- 항상 작동함 - Convex/Concave 무관
- Loop 문제 완전 회피 - 정점 순서 불필요
- 매우 빠름 - ~0.5ms로 목표 달성
성공한 점:
- 매우 간단함 - 코드 10줄
- 안정적 - 항상 작동
- 빠름 - ~0.5ms
- Loop 닫힘 문제 해결
문제점:
- 부자연스러운 방사형 패턴
- 중심점이 최적 위치가 아닐 수 있음
- 시각적으로 인위적임
배운 점:
간단한 해결책도 때로는 유효하다. 하지만 더 나은 방법이 존재한다는 것을 알게 되었습니다.
Breakthrough: Arc-Length Based Triangulation
성공 - 1ms결정적 발견
정점의 인덱스 순서가 아닌 실제 거리(Arc-length)로 정렬하면 자연스러운 삼각형 생성!
문제 분석:
- Index 기반의 문제:
- 정점 배열 순서 ≠ 실제 공간 순서
- 멀리 떨어진 점들끼리 연결됨
- 부자연스러운 삼각형 생성
Arc-Length 해결책:
- 중심점에서 각 정점까지 실제 거리 계산
- 거리 순으로 정렬
- 인접한 점들끼리만 연결
- 공간 정보를 활용한 자연스러운 배치
완벽한 결과:
- 자연스러운 삼각형 배치
- 시각적으로 완벽함
- 성능: ~1ms (목표 달성)
- 100% 안정성
핵심 인사이트:
공간적 정보를 활용하는 것이 핵심이었습니다. 단순한 정렬 방식 변경으로 완벽한 해결이 가능했습니다.
최종 솔루션
3번의 시도와 돌파구를 통해 얻은 교훈을 모두 결합하여 완성된 알고리즘입니다.
핵심 컴포넌트 4가지
1. Boundary Extractor (경계선 추출기)
절단된 메쉬에서 경계 정점들을 추출합니다. 한쪽 Triangle만 마킹된 Edge를 찾아내어 Boundary Loop을 구성합니다. Loop이 닫히지 않는 문제를 피하기 위해 Edge 기반이 아닌 정점 리스트로 직접 작업합니다.
2. Center Calculator (중심점 계산기)
Boundary 정점들의 평균 위치를 계산하여 중심점을 구합니다. 이 중심점은 Fan Triangulation의 시작점이 되며, 모든 삼각형이 이 점을 공유합니다.
3. Arc-Length Sorter (거리 기반 정렬기)
중심점에서 각 정점까지의 실제 거리(Arc-length)를 계산하고 정렬합니다. 이것이 이 프로젝트의 핵심 혁신입니다. 인덱스 순서가 아닌 공간적 거리로 정렬함으로써 자연스러운 삼각형 배치가 가능해집니다.
4. Mesh Builder (메쉬 생성기)
정렬된 정점들로 삼각형 메쉬를 생성합니다. 중심점과 인접한 두 정점을 연결하여 삼각형을 만들고, UV 좌표와 Normal 벡터를 계산하여 완전한 메쉬를 구성합니다.
핵심 알고리즘 코드
Arc-Length 정렬
// 중심점 계산
Vector3 center = Vector3.zero;
foreach (Vector3 vertex in boundaryVertices)
{
center += vertex;
}
center /= boundaryVertices.Count;
// Arc-length 계산 및 정렬
List<(Vector3 vertex, float arcLength)> verticesWithArc = new List<(Vector3, float)>();
foreach (Vector3 vertex in boundaryVertices)
{
float distance = Vector3.Distance(center, vertex);
verticesWithArc.Add((vertex, distance));
}
// 거리 순으로 정렬
verticesWithArc.Sort((a, b) => a.arcLength.CompareTo(b.arcLength));
// 정렬된 정점 리스트
List<Vector3> sortedVertices = verticesWithArc.Select(v => v.vertex).ToList();Fan Triangulation
// Fan 방식으로 삼각형 생성
List<int> triangles = new List<int>();
for (int i = 0; i < sortedVertices.Count; i++)
{
int next = (i + 1) % sortedVertices.Count;
// 중심점, 현재 정점, 다음 정점으로 삼각형 구성
triangles.Add(centerIndex); // 중심점
triangles.Add(vertexStart + i); // 현재 정점
triangles.Add(vertexStart + next); // 다음 정점
}전체 알고리즘 흐름
- 경계 추출: 절단된 메쉬에서 Boundary 정점 리스트 추출
- 중심점 계산: 모든 Boundary 정점의 평균 위치 계산
- Arc-Length 정렬: 중심점에서 각 정점까지 거리 계산 후 정렬
- 삼각형 생성: 중심점과 인접한 두 정점을 연결하여 Fan 형태 삼각형 생성
- UV 매핑: 각 정점에 UV 좌표 할당 (텍스처 적용 가능)
- Normal 계산: 각 삼각형의 Normal 벡터 계산하여 조명 적용
- 메쉬 통합: 생성된 Cap Mesh를 원본 메쉬와 결합
핵심 성공 요인
Arc-Length 정렬이 모든 것을 해결했습니다. 인덱스 순서가 아닌 실제 공간 거리를 활용함으로써 자연스러운 삼각형 배치와 ~1ms의 빠른 성능을 모두 달성할 수 있었습니다.
특히 Fan Triangulation의 단순함과 Arc-Length의 공간적 정보 활용이 완벽하게 결합되었습니다.
기술 상세 설명
Arc-Length 정렬의 원리
Arc-Length는 "호의 길이"를 의미하며, 여기서는 중심점에서 각 정점까지의 실제 거리를 뜻합니다. 이 거리로 정점을 정렬하면 공간상에서 가까운 점들끼리 순서대로 배치됩니다.
왜 Index가 아닌 Arc-Length인가:
- 정점 배열의 인덱스는 메쉬 생성 순서일 뿐, 공간적 위치와 무관
- Index 순서로 연결하면 먼 곳의 점들이 연결되어 비정상적인 삼각형 생성
- Arc-Length로 정렬하면 공간상 인접한 점들이 자동으로 순서대로 배치됨
- 결과적으로 자연스럽고 균일한 삼각형 분포 달성
Fan Triangulation의 장점
Fan(부채꼴) 방식은 가장 단순한 Triangulation 방법입니다. 하나의 중심점에서 모든 삼각형이 방사형으로 펼쳐집니다.
- 극도로 단순함: 10줄 이내의 코드로 구현 가능
- 항상 작동함: Convex/Concave 폴리곤 무관하게 작동
- 빠른 성능: O(n) 시간 복잡도로 매우 빠름
- 예측 가능함: 항상 같은 패턴으로 삼각형 생성
Arc-Length 정렬과 결합하면 Fan 방식의 단순함을 유지하면서도 자연스러운 결과를 얻을 수 있습니다.
Floating Point 오차 회피
Attempt #2에서 겪었던 Floating Point 정밀도 문제를 완전히 회피할 수 있었습니다.
문제 상황:
- Vector3를 Dictionary Key로 사용 시 GetHashCode() 불안정
- 0.0001 단위의 미세한 차이로 Loop이 닫히지 않음
- Epsilon 비교로도 해결이 어려움
해결 방법:
- Dictionary를 완전히 제거 - 정점 리스트만 사용
- Loop 닫힘을 체크하지 않음 - Fan 방식이라 불필요
- Arc-Length 정렬로 자연스러운 순서 보장
- Floating Point 비교를 최소화
UV 매핑과 Normal 계산
Cap Mesh에도 UV 좌표와 Normal 벡터를 부여하여 완전한 메쉬로 만듭니다.
UV 매핑:
- 중심점을 UV (0.5, 0.5)로 설정
- Boundary 정점들을 원주 상에 배치
- 각도에 따라 UV 좌표 계산
- 결과적으로 원형 텍스처 매핑 가능
Normal 계산:
- 각 삼각형의 두 Edge 벡터를 외적하여 Normal 계산
- 모든 정점의 Normal을 평균화
- 부드러운 조명 효과 달성
프로젝트 성과
~1ms
처리 시간
실시간 VR 환경에서 지연 없이 작동
100%
완전성
모든 테스트 케이스에서 완벽한 Cap Mesh 생성
3번
시도
3번의 시도 끝에 완벽한 솔루션 발견
성능 비교
| 시도 | 방법 | 처리 시간 | 결과 |
|---|---|---|---|
| Attempt #1 | Point Cloud (Delaunay) | ~50ms | 실패 (너무 복잡) |
| Attempt #2 | Ear Clipping | N/A | 실패 (Loop 문제) |
| Attempt #3 | Fan (Index 기반) | ~0.5ms | 부분 성공 (부자연스러움) |
| Final | Fan + Arc-Length | ~1ms | 완벽 성공 |
핵심 배움
- 공간 정보의 힘: 단순한 인덱스가 아닌 실제 거리를 활용하여 자연스러운 결과 달성
- 단순함의 가치: Fan Triangulation이라는 단순한 방법이 Arc-Length와 결합하여 완벽한 솔루션이 됨
- Floating Point 정밀도: Vector3를 Dictionary Key로 절대 사용하지 말 것. 정밀도 문제를 회피하는 알고리즘 설계가 중요
- 학술적 완벽함 vs 실무 적용성: Delaunay처럼 이론적으로 완벽한 방법이 항상 최선은 아님
- 반복적 개선: 3번의 시도를 통해 점진적으로 문제를 해결하고 최종 솔루션에 도달
향후 계획
- Mesh Slicing과 통합: Cap Mesh 생성을 Mesh Slicing 시스템에 완전히 통합하여 하나의 완성된 시스템 구축
- 다양한 Cap 스타일: Flat Cap 외에 Curved Cap, Beveled Cap 등 다양한 스타일 지원
- 멀티 Boundary 지원: 하나의 메쉬에 여러 개의 구멍이 있을 때 각각에 Cap 생성
- 텍스처 연속성: Cap Mesh의 텍스처가 원본 메쉬와 자연스럽게 연결되도록 UV 매핑 개선
- 성능 최적화: Job System과 Burst Compiler를 활용하여 더욱 빠른 처리 속도 달성