Fast Geometry Streaming
UE 5.6의 대규모 오픈월드 스트리밍 최적화 기술
Fast Geometry Streaming 개요
CD Projekt RED와 공동 개발한 오픈월드 스트리밍 솔루션
Fast Geometry Streaming Plugin은 Unreal Engine 5.6에서 실험적 기능으로 도입된 대규모 오픈월드 스트리밍 솔루션입니다. 더 위처 4 개발 과정에서 CD Projekt RED와 Epic Games가 공동으로 개발했으며, 정적 지오메트리의 빠른 스트리밍을 통해 메모리 효율성을 극대화합니다.
Fast Geometry Streaming은 게임플레이에 영향을 주지 않는 정적 요소(건물, 표지판, 장식물 등)를 대상으로 합니다. World Partition과 병행하여 사용하며, 존 전환 시 끊김을 제거합니다.
플러그인 설정 및 활성화
프로젝트에서 Fast Geometry Streaming 사용하기
플러그인 활성화
; Fast Geometry Streaming 플러그인 설정
[/Script/Engine.RendererSettings]
r.FastGeometryStreaming=1
[FastGeometryStreaming]
; 스트리밍 거리 설정 (단위: Unreal Units)
StreamingDistance=50000
; 메모리 예산 (MB)
MemoryBudgetMB=2048
; 우선순위 모드
PriorityMode=Distance
; LOD 전환 속도
LODTransitionSpeed=2.0
C++ 런타임 설정
// FastGeoStreamingManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "FastGeometryStreaming/FastGeoStreamingTypes.h"
#include "FastGeoStreamingManager.generated.h"
/**
* Fast Geometry Streaming 관리 서브시스템
* 대규모 오픈월드의 정적 지오메트리 스트리밍을 관리
*/
UCLASS()
class MYGAME_API UFastGeoStreamingManager : public UWorldSubsystem
{
GENERATED_BODY()
public:
// 서브시스템 초기화
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// 스트리밍 설정 변경
UFUNCTION(BlueprintCallable, Category = "FastGeo")
void SetStreamingDistance(float NewDistance);
// 특정 영역 우선 로드
UFUNCTION(BlueprintCallable, Category = "FastGeo")
void PrioritizeArea(const FBox& Area, EFastGeoPriority Priority);
// 스트리밍 상태 조회
UFUNCTION(BlueprintPure, Category = "FastGeo")
FFastGeoStreamingStats GetStreamingStats() const;
// 메모리 예산 동적 조절
UFUNCTION(BlueprintCallable, Category = "FastGeo")
void SetMemoryBudget(int32 BudgetMB);
protected:
// 현재 스트리밍 거리
UPROPERTY()
float CurrentStreamingDistance;
// 메모리 예산
UPROPERTY()
int32 MemoryBudgetMB;
// 활성 스트리밍 요청
TArray<FFastGeoStreamingRequest> ActiveRequests;
};
// FastGeoStreamingManager.cpp
#include "FastGeoStreamingManager.h"
#include "FastGeometryStreaming/FastGeoStreamingSubsystem.h"
void UFastGeoStreamingManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// 기본 설정 로드
CurrentStreamingDistance = 50000.0f;
MemoryBudgetMB = 2048;
// INI 파일에서 설정 읽기
GConfig->GetFloat(
TEXT("FastGeometryStreaming"),
TEXT("StreamingDistance"),
CurrentStreamingDistance,
GEngineIni
);
UE_LOG(LogFastGeo, Log,
TEXT("Fast Geometry Streaming 초기화: Distance=%f, Budget=%dMB"),
CurrentStreamingDistance, MemoryBudgetMB);
}
void UFastGeoStreamingManager::SetStreamingDistance(float NewDistance)
{
CurrentStreamingDistance = FMath::Clamp(NewDistance, 10000.0f, 200000.0f);
// 엔진 스트리밍 시스템에 전달
if (UFastGeoStreamingSubsystem* FastGeoSS =
GetWorld()->GetSubsystem<UFastGeoStreamingSubsystem>())
{
FastGeoSS->SetGlobalStreamingDistance(CurrentStreamingDistance);
}
}
void UFastGeoStreamingManager::PrioritizeArea(
const FBox& Area,
EFastGeoPriority Priority)
{
FFastGeoStreamingRequest Request;
Request.Bounds = Area;
Request.Priority = Priority;
Request.RequestTime = GetWorld()->GetTimeSeconds();
ActiveRequests.Add(Request);
// 우선순위에 따라 즉시 로드 트리거
if (Priority == EFastGeoPriority::Immediate)
{
if (UFastGeoStreamingSubsystem* FastGeoSS =
GetWorld()->GetSubsystem<UFastGeoStreamingSubsystem>())
{
FastGeoSS->ForceLoadArea(Area);
}
}
}
FFastGeoStreamingStats UFastGeoStreamingManager::GetStreamingStats() const
{
FFastGeoStreamingStats Stats;
if (UFastGeoStreamingSubsystem* FastGeoSS =
GetWorld()->GetSubsystem<UFastGeoStreamingSubsystem>())
{
Stats.LoadedMeshCount = FastGeoSS->GetLoadedMeshCount();
Stats.PendingLoadCount = FastGeoSS->GetPendingLoadCount();
Stats.MemoryUsedMB = FastGeoSS->GetMemoryUsageMB();
Stats.StreamingEfficiency = FastGeoSS->GetEfficiencyPercent();
}
return Stats;
}
World Partition 통합
기존 스트리밍 시스템과의 협력적 운영
Fast Geometry Streaming은 World Partition과 병행하여 작동합니다. World Partition이 게임플레이 관련 액터를 처리하는 동안, Fast Geometry Streaming은 시각적 요소에 집중합니다.
// WorldPartitionFastGeoIntegration.h
#pragma once
#include "CoreMinimal.h"
#include "WorldPartition/WorldPartitionStreamingSource.h"
#include "WorldPartitionFastGeoIntegration.generated.h"
/**
* World Partition과 Fast Geometry Streaming 통합 컴포넌트
* 플레이어 카메라 기반으로 스트리밍 소스를 제공
*/
UCLASS(ClassGroup=(Streaming), meta=(BlueprintSpawnableComponent))
class MYGAME_API UFastGeoStreamingSourceComponent : public UWorldPartitionStreamingSourceComponent
{
GENERATED_BODY()
public:
UFastGeoStreamingSourceComponent();
// 스트리밍 소스 설정
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FastGeo")
float FastGeoStreamingRadius = 30000.0f;
// 카메라 전방 우선 로드 거리
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FastGeo")
float ForwardPriorityDistance = 50000.0f;
// LOD 바이어스 설정
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FastGeo")
float LODBias = 0.0f;
protected:
virtual void TickComponent(float DeltaTime,
ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
// 카메라 방향 기반 우선순위 계산
void UpdateStreamingPriorities();
private:
// 마지막 업데이트 위치
FVector LastUpdateLocation;
FRotator LastUpdateRotation;
};
// WorldPartitionFastGeoIntegration.cpp
#include "WorldPartitionFastGeoIntegration.h"
#include "FastGeometryStreaming/FastGeoStreamingSubsystem.h"
#include "Camera/CameraComponent.h"
UFastGeoStreamingSourceComponent::UFastGeoStreamingSourceComponent()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickInterval = 0.1f; // 10Hz 업데이트
}
void UFastGeoStreamingSourceComponent::TickComponent(
float DeltaTime,
ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 위치 변화 감지 (최적화)
FVector CurrentLocation = GetOwner()->GetActorLocation();
FRotator CurrentRotation = GetOwner()->GetActorRotation();
float LocationDelta = FVector::Dist(CurrentLocation, LastUpdateLocation);
float RotationDelta = FMath::Abs(CurrentRotation.Yaw - LastUpdateRotation.Yaw);
// 의미 있는 변화가 있을 때만 업데이트
if (LocationDelta > 500.0f || RotationDelta > 15.0f)
{
UpdateStreamingPriorities();
LastUpdateLocation = CurrentLocation;
LastUpdateRotation = CurrentRotation;
}
}
void UFastGeoStreamingSourceComponent::UpdateStreamingPriorities()
{
UFastGeoStreamingSubsystem* FastGeoSS =
GetWorld()->GetSubsystem<UFastGeoStreamingSubsystem>();
if (!FastGeoSS) return;
FVector Location = GetOwner()->GetActorLocation();
FVector ForwardVector = GetOwner()->GetActorForwardVector();
// 카메라 전방 영역 우선 로드
FBox ForwardPriorityBox = FBox::BuildAABB(
Location + ForwardVector * (ForwardPriorityDistance * 0.5f),
FVector(ForwardPriorityDistance, FastGeoStreamingRadius, 5000.0f)
);
// 스트리밍 힌트 전달
FFastGeoStreamingHint Hint;
Hint.PriorityBounds = ForwardPriorityBox;
Hint.LODBias = LODBias;
Hint.bForceHighestLOD = false;
FastGeoSS->SetStreamingHint(Hint);
}
- World Partition: 게임플레이 액터, NPC, 인터랙티브 오브젝트
- Fast Geometry Streaming: 건물, 지형 장식, 정적 배경 요소
- 두 시스템은 독립적으로 작동하며 메모리를 공유하지 않음
메모리 최적화 전략
메모리 단편화 감소 및 효율적인 리소스 관리
// FastGeoMemoryManager.h
#pragma once
#include "CoreMinimal.h"
#include "FastGeoMemoryManager.generated.h"
/**
* Fast Geometry 메모리 관리자
* 메모리 풀링과 단편화 방지를 담당
*/
UCLASS()
class MYGAME_API UFastGeoMemoryManager : public UObject
{
GENERATED_BODY()
public:
// 메모리 풀 초기화
void InitializeMemoryPools(int32 TotalBudgetMB);
// 메시 데이터 할당 요청
void* AllocateMeshData(SIZE_T Size, EFastGeoMeshType MeshType);
// 메시 데이터 해제
void FreeMeshData(void* Data, SIZE_T Size);
// 메모리 압박 시 정리
void TrimMemory(int32 TargetFreeMB);
// 현재 메모리 상태
UFUNCTION(BlueprintPure, Category = "Memory")
FFastGeoMemoryStats GetMemoryStats() const;
private:
// 메모리 풀 (크기별)
TArray<FFastGeoMemoryPool> MemoryPools;
// LRU 캐시 관리
TMap<FName, FFastGeoCacheEntry> MeshCache;
// 메모리 예산
int32 TotalBudgetMB;
int32 UsedMemoryMB;
};
// 구현
void UFastGeoMemoryManager::InitializeMemoryPools(int32 InTotalBudgetMB)
{
TotalBudgetMB = InTotalBudgetMB;
UsedMemoryMB = 0;
// 크기별 메모리 풀 생성
// 작은 메시 (1MB 이하): 전체의 20%
MemoryPools.Add(FFastGeoMemoryPool(1 * 1024 * 1024, TotalBudgetMB * 0.2f));
// 중간 메시 (1-10MB): 전체의 50%
MemoryPools.Add(FFastGeoMemoryPool(10 * 1024 * 1024, TotalBudgetMB * 0.5f));
// 큰 메시 (10MB 이상): 전체의 30%
MemoryPools.Add(FFastGeoMemoryPool(100 * 1024 * 1024, TotalBudgetMB * 0.3f));
UE_LOG(LogFastGeo, Log,
TEXT("메모리 풀 초기화: 총 %dMB 예산"), TotalBudgetMB);
}
void UFastGeoMemoryManager::TrimMemory(int32 TargetFreeMB)
{
// LRU 기반으로 오래된 캐시 제거
int32 FreedMB = 0;
// 접근 시간 기준 정렬
MeshCache.ValueSort([](const FFastGeoCacheEntry& A,
const FFastGeoCacheEntry& B)
{
return A.LastAccessTime < B.LastAccessTime;
});
// 가장 오래된 것부터 제거
TArray<FName> KeysToRemove;
for (auto& Pair : MeshCache)
{
if (FreedMB >= TargetFreeMB) break;
KeysToRemove.Add(Pair.Key);
FreedMB += Pair.Value.SizeMB;
}
for (const FName& Key : KeysToRemove)
{
FFastGeoCacheEntry* Entry = MeshCache.Find(Key);
if (Entry)
{
FreeMeshData(Entry->Data, Entry->Size);
MeshCache.Remove(Key);
}
}
UE_LOG(LogFastGeo, Log,
TEXT("메모리 정리 완료: %dMB 해제됨"), FreedMB);
}
- 메모리 풀링 - 크기별 풀로 단편화 방지
- LRU 캐시 - 최근 사용되지 않은 메시 우선 해제
- 동적 예산 조절 - 시스템 상태에 따른 자동 조정
- 비동기 로딩 - 메인 스레드 블로킹 방지
핵심 요약
- Fast Geometry Streaming은 UE 5.6의 실험적 기능으로, CD Projekt RED와 공동 개발
- 정적 지오메트리 전용으로 게임플레이 액터는 World Partition이 처리
- 메모리 단편화 감소와 존 전환 시 끊김 제거가 주요 목표
- CPU 최대 35%, GPU 최대 25% 성능 향상 달성
- 메모리 풀링과 LRU 캐시를 통한 효율적 리소스 관리
Fast Geometry Streaming은 UE 5.6에서 실험적(Experimental) 기능입니다. 프로덕션 사용 전 충분한 테스트가 필요하며, 향후 API 변경 가능성이 있습니다.
도전 과제
배운 내용을 직접 실습해보세요
Project Settings에서 Fast Geometry Streaming을 활성화하고, 오픈월드 RPG에서 원거리 지오메트리의 스트리밍 속도 개선을 확인하세요. 기존 Nanite 스트리밍과 비교하세요.
Fast Geometry Streaming의 풀 크기와 우선순위를 조정하여, 빠른 카메라 이동(말 타기, 패스트 트래블) 시에도 팝인이 발생하지 않도록 최적화하세요.
stat Streaming과 Unreal Insights를 사용하여 Fast Geometry Streaming의 디스크 I/O, 메모리 사용량, GPU 전송 시간을 프로파일링하세요. SSD vs NVMe 환경에서의 성능 차이를 비교하세요.