RealTimeDestructibleMesh - GridCell Debris System

조창근·2026년 2월 11일

RealTimeDestructibleMesh

목록 보기
3/4

FAB링크

발표자료

1. 디브리 작업 순서

디브리 시스템은 단순히 "메쉬를 자르는 것"이 아닙니다. [탐색] → [추출] → [생성]의 3단계 파이프라인을 거칩니다.

  1. 탐색 (Detection): 구조적으로 고립된 Cell 그룹(Floating Island)을 찾습니다.
  2. 추출 (Extraction): 즉시 생성하지 않고, 데이터화하여 대기열(Queue)에 넣습니다.
  3. 생성 (Spawning): 매 프레임 예산(Budget) 내에서 물리 액터로 변환합니다.

실제 코드에서는 FConnectivityContextWorkStack을 사용한 스택 기반 DFS로 구현되어 있습니다.

TArray<FCellNode>& Stack = Context.WorkStack;

// Anchor Cell에서 시작
Stack.Push(FCellNode::MakeSupercell(SupercellId));

// DFS 순회
while (Stack.Num() > 0)
{
    FCellNode Current = Stack.Pop();
    // 이웃 탐색 후 미방문 셀을 Stack에 Push...
}

2. 디브리 작업 순서

Step 1. 구조적 고립 감지 (Island Detection)

파괴가 발생하면 DFS(깊이 우선 탐색)가 실행됩니다.

  • Anchor: 지면에 닿아있는 고정된 Cell.
  • Logic: Anchor와 연결되지 않은 모든 Detached Cell들을 찾아냅니다.
  • Result: TArray<int32> ConnectedCells로 구성된 '섬(Island)' 목록이 반환됩니다.

위 그림과 같이 DetachedCell과 겹친 부분의 ToolMesh 를 찾아 DebriMesh를 추출해줍니다.후에 ProcedureMesh로 스폰시켜줍니다.

Step 2. '추출 대기열'과 부하 분산 (The Queue System)

가장 중요한 최적화 구간입니다. 건물이 붕괴될 때 수십 개의 파편이 동시에 발생할 수 있습니다. 이를 한 프레임에 모두 SpawnActor 해버리면 심각한 프레임 드랍이 발생합니다.

RealTimeDestructibleMesh에서는 PendingDebrisQueue를 도입하여 이를 해결했습니다.

struct FDebrisExtractionData
{
    TArray<int32> CellIds; // 파편을 구성하는 셀 ID 목록
    FTransform OriginTransform; // 원본 위치
    FVector InitialVelocity; // 폭발력 등
};

// 큐에 데이터만 먼저 저장 (메쉬 생성 X, 액터 스폰 X)
PendingDebrisQueue.Enqueue(ExtractionData);

Step 3. 메쉬 조립 (Mesh Assembly via Cell ID)

이제 큐에서 데이터를 꺼내 실제 ADebrisActor를 만듭니다. 여기서 Grid Cell System의 진가가 발휘됩니다.

기존 방식은 런타임에 메쉬를 자르는(Slicing) 무거운 연산을 수행하지만, RealTimeDestructibleMesh"조립(Assembly)" 방식을 택했습니다.

  1. Lookup: 파편이 포함하는 CellId 목록을 순회합니다.
  2. Fetch: 원본 메쉬(Grid Manager)에 캐싱된 Triangle Data 중, 해당 Cell에 속한 삼각형들만 가져옵니다.
  3. Build: 가져온 삼각형들을 ProceduralMeshComponent에 밀어 넣습니다.

Why Fast?
실시간 Boolean 연산이나 정점 계산이 전혀 없습니다. 이미 계산된 데이터(Cached Data)를 복사하는 과정일 뿐이므로 매우 빠릅니다.

Why ProceduralMesh?
| 특징 | StaticMesh | DynamicMesh | ProceduralMesh (채택) |
| 주 용도 | 배경, 고정 사물 | 런타임 모델링 도구 | 절차적 생성, 파편 |
| 수정 가능성 | 불가 (X) | 높음 (High) | 중간 (Raw Data) |
| 데이터 구조 | 고정 에셋 | FDynamicMesh3 (복잡) | TArray (단순) |
| 초기화 속도 | 매우 빠름 | 느림 (구조 빌드 필요) | 빠름 (버퍼 복사) |

DynamicMesh는 생성 시 내부 토폴로지 구조(Edge 정보 등)를 빌드하는 과정이 필요합니다. 반면, PMC는 단순히 "이 점들을 이 순서대로 그려라"라는 명령만 수행합니다.

수백 개의 파편이 한 프레임에 생성되어야 하는 상황에서는, 기능이 적고 단순한 PMC가 초기화 비용(Spawning Cost) 면에서 훨씬 유리합니다.

Step 4. Time-Slicing (시분할 처리)

Tick 함수에서 매 프레임 처리할 작업량을 제한합니다.

void ADebrisManager::Tick(float DeltaTime)
{
    int32 ProcessedCount = 0;
    const int32 MaxProcessPerFrame = 2; // 프레임당 최대 2개만 생성

    while (!PendingDebrisQueue.IsEmpty() && ProcessedCount < MaxProcessPerFrame)
    {
        SpawnDebris(PendingDebrisQueue.Dequeue());
        ProcessedCount++;
    }
}

이로써 대규모 붕괴 시에도 프레임 레이트가 안정적으로 유지됩니다.


3. 네트워크 동기화 전략

가장 핵심이 되는 부분입니다. 디브리 역시 서버 권한(Server Authoritative)을 따르지만, 네트워크 대역폭을 아끼기 위한 전략을 사용합니다.

  • 서버: 물리 시뮬레이션을 수행하고 중요한 위치(Transform)만 동기화합니다.
  • 클라이언트:
    1. 서버로부터 CellID 목록을 받습니다.
    2. 로컬에서 서버와 똑같은 로직으로 메쉬를 조립합니다.
    3. Result: CellID가 정수(Integer)이므로, 서버와 클라이언트는 100% 동일한 모양의 파편을 가지게 됩니다. (Floating Point 오차 없음)

4.성능과 품질의 균형

이 시스템의 의의는 "화려한 파괴 효과를 주면서도 게임플레이에 방해되지 않는 성능"을 확보했다는 점입니다.

  • 최적화: Queue & Time-Slicing으로 프레임 스파이크 제거.
  • 속도: 캐싱된 그리드 데이터 활용으로 런타임 연산 최소화.
  • 정합성: Cell ID 기반의 완벽한 형태 동기화.

다음 글에서는 네트워크 시스템에서는 총알로 인해 파괴된 메쉬의 충돌처리 최적화 과정에 대해서 설명하도록 하겠습니다.

0개의 댓글