Spatial Hash와 그리드 시스템
World Partition의 공간 해싱과 그리드 셀 구조 이해하기
UWorldPartitionRuntimeSpatialHash
공간 해싱을 통한 스트리밍 셀 관리
World Partition은 월드를 2D 그리드로 분할하여 관리합니다. UWorldPartitionRuntimeSpatialHash가 공간 해싱을 담당합니다.
// Spatial Hash 개념 설명
// 위치 -> 셀 이름 매핑 함수
// Cook 시: 스트리밍 레벨 생성에 사용
// Runtime 시: 로드할 레벨 결정에 사용
UCLASS()
class ENGINE_API UWorldPartitionRuntimeSpatialHash : public UWorldPartitionRuntimeHash
{
GENERATED_BODY()
public:
// 특정 위치에 해당하는 스트리밍 셀 조회
void GetStreamingCells(const FVector& Location,
float LoadingRange,
TArray<const UWorldPartitionRuntimeCell*>& OutCells);
// 서버용: 모든 셀 조회 (Dedicated Server)
void GetAllStreamingCells(TArray<const UWorldPartitionRuntimeCell*>& OutCells);
protected:
// 그리드 설정
UPROPERTY(EditAnywhere, Category = "Grid")
int32 CellSize = 12800; // 128m 기본값
UPROPERTY(EditAnywhere, Category = "Grid")
int32 LoadingRange = 25600; // 256m 기본값
};
위치를 입력받아 해당 위치가 속한 셀 이름을 반환합니다. 이 매핑은 쿡 시 스트리밍 레벨을 생성하고, 런타임에서 로드할 셀을 결정하는 데 사용됩니다.
LH Grid (Location Hash Grid)
현재 기본 그리드 시스템
현재 기본 그리드인 LH Grid는 액터를 정규 그리드에 배치하되, 셀이 너무 크거나 작으면 경계를 조정합니다.
// 그리드 셀 경계 조정 로직 (개념적 설명)
struct FWorldPartitionCell
{
FBox Bounds;
TArray<AActor*> Actors;
// 셀이 너무 클 경우 분할
void SubdivideIfNeeded(int32 MaxActorsPerCell)
{
if (Actors.Num() > MaxActorsPerCell)
{
// 4개 자식 셀로 분할
SubdivideTo4();
}
}
// 셀이 너무 작을 경우 병합
void MergeWithNeighborsIfNeeded(int32 MinActorsPerCell)
{
if (Actors.Num() < MinActorsPerCell)
{
// 인접 셀과 병합
MergeWithNeighbors();
}
}
};
| 파라미터 | 설명 | 권장값 |
|---|---|---|
CellSize |
그리드 셀의 기본 크기 (cm) | 12800 (128m) |
LoadingRange |
셀 로딩 시작 거리 (cm) | 25600 (256m) |
Priority |
로딩 우선순위 | 0 (기본) |
그리드 설정 방법
World Settings에서의 런타임 그리드 구성
// World Settings > World Partition > Runtime Settings > Grids
// 기본 그리드 설정 예시
struct FWorldPartitionRuntimeGrid
{
FName GridName = "MainGrid";
int32 CellSize = 12800; // 128m (cm 단위)
int32 LoadingRange = 25600; // 256m
FColor DebugColor = FColor::Green;
bool bBlockOnSlowStreaming = false;
int32 Priority = 0; // 높을수록 우선
};
다중 그리드 설정 예시
; Close Grid - 중요 게임플레이 오브젝트
[/Script/Engine.WorldPartitionRuntimeSpatialHash]
GridName=CloseGrid
CellSize=6400
LoadingRange=12800
Priority=100
; Medium Grid - 일반 환경
[/Script/Engine.WorldPartitionRuntimeSpatialHash]
GridName=MediumGrid
CellSize=12800
LoadingRange=51200
Priority=50
; Far Grid - 배경 요소
[/Script/Engine.WorldPartitionRuntimeSpatialHash]
GridName=FarGrid
CellSize=25600
LoadingRange=102400
Priority=10
; Foliage Grid - 식생 전용
[/Script/Engine.WorldPartitionRuntimeSpatialHash]
GridName=FoliageGrid
CellSize=12800
LoadingRange=76800
Priority=25
게임플레이에 중요한 오브젝트는 작은 셀 크기와 짧은 로딩 거리로, 배경 요소는 큰 셀 크기와 긴 로딩 거리로 설정하여 메모리와 성능을 최적화합니다.
액터별 그리드 할당
특정 액터를 특정 그리드에 배치
// 액터의 Details 패널에서 설정
// World Partition > Runtime Grid = "FoliageGrid"
// 또는 C++에서 설정
UCLASS()
class AMyFoliageActor : public AActor
{
GENERATED_BODY()
public:
AMyFoliageActor()
{
// 기본 그리드 설정 (에디터에서 오버라이드 가능)
RuntimeGrid = TEXT("FoliageGrid");
}
protected:
// 런타임 그리드 이름
UPROPERTY(EditAnywhere, Category = "World Partition")
FName RuntimeGrid;
};
에디터에서 설정
액터 선택 후 Details 패널의 World Partition 카테고리에서 Runtime Grid 프로퍼티를 수정합니다.
C++에서 설정
생성자에서 RuntimeGrid 변수를 설정하면 해당 액터 타입의 기본값으로 적용됩니다.
셀 크기 결정 가이드
최적의 CellSize 선택
Cell Size는 랜드스케이프 컴포넌트 크기와 일치시키는 것이 좋습니다. 불일치 시 랜드스케이프 스트리밍에서 시각적 아티팩트가 발생할 수 있습니다.
| 월드 크기 | 권장 Cell Size | 권장 Loading Range |
|---|---|---|
| 소규모 (4km x 4km) | 12800 (128m) | 25600 (256m) |
| 중규모 (16km x 16km) | 25600 (256m) | 51200 (512m) |
| 대규모 (64km x 64km) | 51200 (512m) | 102400 (1km) |
핵심 요약
- UWorldPartitionRuntimeSpatialHash가 위치 기반 셀 조회 담당
- LH Grid는 기본 그리드로, 필요시 셀을 분할/병합
- CellSize는 랜드스케이프 컴포넌트 크기와 일치 권장
- 다중 그리드로 오브젝트 타입별 최적화 가능
- 액터별로 Runtime Grid 프로퍼티로 그리드 할당
도전 과제
배운 내용을 직접 실습해보세요
World Partition 설정에서 Runtime Spatial Hash의 CellSize를 12800, 25600, 51200으로 각각 변경하며 PIE에서 셀 로딩 동작을 관찰하세요. wp.Runtime.ToggleDrawRuntimeHash2D 명령어로 그리드를 시각화합니다.
RuntimeGrid의 LoadingRange를 다양하게 설정(10000~50000)하며 스트리밍 동작을 비교하세요. 더 넓은 로딩 범위가 메모리 사용량과 로딩 시간에 미치는 영향을 측정합니다.
UWorldPartitionRuntimeSpatialHash를 분석하여 현재 로드된 셀, 대기 중인 셀, 언로드된 셀의 상태를 3D 미니맵으로 시각화하는 에디터 유틸리티를 만드세요.