PART 11 · 강의 2/7

PCG Production Ready

UE 5.7에서 정식 출시된 프로시저럴 콘텐츠 생성 프레임워크

01

PCG 프레임워크 발전 과정

실험적 기능에서 프로덕션 레디까지

Experimental
UE 5.4
Beta
UE 5.5
Production Ready
UE 5.7

Procedural Content Generation (PCG) 프레임워크는 UE 5.7에서 정식 출시(Production Ready) 상태로 승격되었습니다. 노드 기반 비주얼 그래프로 오픈월드의 환경 요소를 절차적으로 생성할 수 있습니다.

UE 5.7 PCG 주요 개선사항
  • UE 5.5 대비 거의 2배 성능 향상
  • GPU 파라미터 오버라이드 지원
  • 새로운 PCG Editor Mode 추가
  • Python Interop Plugin 지원
02

PCG 그래프 기본 구조

C++에서 커스텀 PCG 노드 생성

C++
// PCGForestGeneratorNode.h #pragma once #include "CoreMinimal.h" #include "PCGSettings.h" #include "PCGForestGeneratorNode.generated.h" /** * 커스텀 PCG 노드: 숲 생성기 * 지형 데이터를 입력받아 나무를 배치하는 포인트 생성 */ UCLASS(BlueprintType, ClassGroup = (Procedural)) class MYGAME_API UPCGForestGeneratorSettings : public UPCGSettings { GENERATED_BODY() public: UPCGForestGeneratorSettings(); //~ Begin UPCGSettings Interface virtual FName GetDefaultNodeName() const override { return FName(TEXT("ForestGenerator")); } virtual FText GetDefaultNodeTitle() const override { return NSLOCTEXT("PCG", "ForestGenerator", "Forest Generator"); } virtual FText GetNodeTooltipText() const override { return NSLOCTEXT("PCG", "ForestGeneratorTooltip", "지형 기반으로 나무를 절차적으로 배치합니다."); } protected: virtual TArray<FPCGPinProperties> InputPinProperties() const override; virtual TArray<FPCGPinProperties> OutputPinProperties() const override; virtual FPCGElementPtr CreateElement() const override; //~ End UPCGSettings Interface public: // 나무 밀도 (제곱미터당) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings", meta = (ClampMin = "0.0001", ClampMax = "1.0")) float TreeDensity = 0.01f; // 최소 경사 각도 (이 이상이면 배치 안 함) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings", meta = (ClampMin = "0.0", ClampMax = "90.0")) float MaxSlopeAngle = 45.0f; // 최소 고도 (바다 레벨) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") float MinAltitude = 0.0f; // 최대 고도 (설선) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") float MaxAltitude = 3000.0f; // 나무 종류별 가중치 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") TMap<TSoftObjectPtr<UStaticMesh>, float> TreeMeshWeights; // 랜덤 시드 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") int32 RandomSeed = 12345; // 스케일 변화량 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") FVector2D ScaleVariation = FVector2D(0.8f, 1.2f); };
C++
// PCGForestGeneratorNode.cpp #include "PCGForestGeneratorNode.h" #include "PCGContext.h" #include "PCGPoint.h" #include "Data/PCGPointData.h" #include "Data/PCGSpatialData.h" #include "Helpers/PCGAsync.h" UPCGForestGeneratorSettings::UPCGForestGeneratorSettings() { bUseSeed = true; } TArray<FPCGPinProperties> UPCGForestGeneratorSettings::InputPinProperties() const { TArray<FPCGPinProperties> Properties; Properties.Emplace(PCGPinConstants::DefaultInputLabel, EPCGDataType::Spatial); // 지형 데이터 입력 return Properties; } TArray<FPCGPinProperties> UPCGForestGeneratorSettings::OutputPinProperties() const { TArray<FPCGPinProperties> Properties; Properties.Emplace(PCGPinConstants::DefaultOutputLabel, EPCGDataType::Point); // 나무 배치 포인트 출력 return Properties; } // PCG Element 구현 class FPCGForestGeneratorElement : public FSimplePCGElement { protected: virtual bool ExecuteInternal(FPCGContext* Context) const override { const UPCGForestGeneratorSettings* Settings = Context->GetInputSettings<UPCGForestGeneratorSettings>(); if (!Settings) return false; // 입력 데이터 가져오기 TArray<FPCGTaggedData> Inputs = Context->InputData.GetInputs(); TArray<FPCGTaggedData>& Outputs = Context->OutputData.TaggedData; for (const FPCGTaggedData& Input : Inputs) { const UPCGSpatialData* SpatialData = Cast<UPCGSpatialData>(Input.Data); if (!SpatialData) continue; // 출력 포인트 데이터 생성 UPCGPointData* OutputData = NewObject<UPCGPointData>(); TArray<FPCGPoint>& Points = OutputData->GetMutablePoints(); // 영역 바운드 계산 FBox Bounds = SpatialData->GetBounds(); FVector BoundsSize = Bounds.GetSize(); // 랜덤 스트림 초기화 FRandomStream RandomStream(Settings->RandomSeed); // 포인트 수 계산 float Area = BoundsSize.X * BoundsSize.Y / 10000.0f; // cm² -> m² int32 PointCount = FMath::RoundToInt(Area * Settings->TreeDensity); Points.Reserve(PointCount); // 포인트 생성 for (int32 i = 0; i < PointCount; ++i) { FVector RandomPosition( RandomStream.FRandRange(Bounds.Min.X, Bounds.Max.X), RandomStream.FRandRange(Bounds.Min.Y, Bounds.Max.Y), 0.0f ); // 지형 높이 샘플링 FPCGPoint SampledPoint; if (SpatialData->SamplePoint( FTransform(RandomPosition), Bounds, SampledPoint, nullptr)) { // 고도 체크 if (SampledPoint.Transform.GetLocation().Z < Settings->MinAltitude || SampledPoint.Transform.GetLocation().Z > Settings->MaxAltitude) { continue; } // 경사 체크 FVector Normal = SampledPoint.Transform.GetRotation().GetUpVector(); float SlopeAngle = FMath::RadiansToDegrees( FMath::Acos(FVector::DotProduct(Normal, FVector::UpVector))); if (SlopeAngle > Settings->MaxSlopeAngle) { continue; } // 스케일 변화 적용 float Scale = RandomStream.FRandRange( Settings->ScaleVariation.X, Settings->ScaleVariation.Y); SampledPoint.Transform.SetScale3D(FVector(Scale)); // 랜덤 회전 적용 float RandomYaw = RandomStream.FRandRange(0.0f, 360.0f); FRotator Rotation = SampledPoint.Transform.Rotator(); Rotation.Yaw = RandomYaw; SampledPoint.Transform.SetRotation(Rotation.Quaternion()); Points.Add(SampledPoint); } } // 출력 데이터 추가 FPCGTaggedData& Output = Outputs.Emplace_GetRef(); Output.Data = OutputData; } return true; } }; FPCGElementPtr UPCGForestGeneratorSettings::CreateElement() const { return MakeShared<FPCGForestGeneratorElement>(); }
03

PCG Editor Mode (UE 5.7)

새로운 전용 에디터 도구

UE 5.7에서는 PCG Editor Mode가 추가되어 스플라인 드로잉, 볼륨 브러시, 독립 실행 그래프 도구를 제공합니다.

Spline Intersection

스플라인 교차점 감지 및 처리. 도로와 강이 만나는 지점에 다리 배치 등에 활용

Split Splines

스플라인을 여러 세그먼트로 분할. 도로를 구간별로 나누어 다른 처리 적용

Polygon2D 데이터 타입

2D 영역 표현용 새로운 지오메트리 타입. 복잡한 영역 정의에 사용

GPU Fast Geo Interop

GPU 가속 메시 생성. 고밀도 메시 처리 성능 대폭 향상

C++
// PCGSplineProcessorNode.h - 스플라인 처리 노드 #pragma once #include "CoreMinimal.h" #include "PCGSettings.h" #include "PCGSplineProcessorNode.generated.h" UCLASS(BlueprintType, ClassGroup = (Procedural)) class MYGAME_API UPCGSplineRoadSettings : public UPCGSettings { GENERATED_BODY() public: virtual FName GetDefaultNodeName() const override { return FName(TEXT("SplineRoadProcessor")); } protected: virtual TArray<FPCGPinProperties> InputPinProperties() const override; virtual TArray<FPCGPinProperties> OutputPinProperties() const override; virtual FPCGElementPtr CreateElement() const override; public: // 도로 폭 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road") float RoadWidth = 800.0f; // 도로 메시 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road") TSoftObjectPtr<UStaticMesh> RoadSegmentMesh; // 교차점 처리 메시 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road") TSoftObjectPtr<UStaticMesh> IntersectionMesh; // 세그먼트 길이 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road", meta = (ClampMin = "100.0")) float SegmentLength = 500.0f; // 지형에 맞춤 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road") bool bConformToTerrain = true; // 가드레일 추가 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Road") bool bAddGuardrails = true; }; // 스플라인 교차점 감지 구현 class FPCGSplineRoadElement : public FSimplePCGElement { protected: virtual bool ExecuteInternal(FPCGContext* Context) const override { const UPCGSplineRoadSettings* Settings = Context->GetInputSettings<UPCGSplineRoadSettings>(); // 스플라인 데이터 가져오기 TArray<FPCGTaggedData> SplineInputs = Context->InputData.GetInputsByPin( FName(TEXT("Splines"))); TArray<FPCGSplineStruct> AllSplines; for (const FPCGTaggedData& Input : SplineInputs) { if (const UPCGSplineData* SplineData = Cast<UPCGSplineData>(Input.Data)) { AllSplines.Add(SplineData->GetSplineStruct()); } } // 교차점 감지 TArray<FVector> Intersections; FindSplineIntersections(AllSplines, Intersections); // 도로 세그먼트 생성 UPCGPointData* RoadPoints = NewObject<UPCGPointData>(); UPCGPointData* IntersectionPoints = NewObject<UPCGPointData>(); for (const FPCGSplineStruct& Spline : AllSplines) { GenerateRoadSegments(Spline, Settings, RoadPoints); } for (const FVector& Intersection : Intersections) { FPCGPoint Point; Point.Transform.SetLocation(Intersection); IntersectionPoints->GetMutablePoints().Add(Point); } // 출력 FPCGTaggedData& RoadOutput = Context->OutputData.TaggedData.Emplace_GetRef(); RoadOutput.Data = RoadPoints; RoadOutput.Pin = FName(TEXT("RoadSegments")); FPCGTaggedData& IntersectionOutput = Context->OutputData.TaggedData.Emplace_GetRef(); IntersectionOutput.Data = IntersectionPoints; IntersectionOutput.Pin = FName(TEXT("Intersections")); return true; } private: void FindSplineIntersections( const TArray<FPCGSplineStruct>& Splines, TArray<FVector>& OutIntersections) const; void GenerateRoadSegments( const FPCGSplineStruct& Spline, const UPCGSplineRoadSettings* Settings, UPCGPointData* OutPoints) const; };
04

Python Interop Plugin

PCG 그래프에서 Python 스크립트 실행

UE 5.7에서는 Python Interop Plugin을 통해 PCG 그래프 내에서 Python 스크립트를 실행할 수 있습니다. 복잡한 알고리즘이나 외부 데이터 처리에 유용합니다.

C++
// PCGPythonInteropNode.h #pragma once #include "CoreMinimal.h" #include "PCGSettings.h" #include "PCGPythonInteropNode.generated.h" /** * PCG Python 인터롭 노드 * PCG 그래프 내에서 Python 스크립트를 실행합니다. */ UCLASS(BlueprintType, ClassGroup = (Procedural)) class MYGAME_API UPCGPythonScriptSettings : public UPCGSettings { GENERATED_BODY() public: virtual FName GetDefaultNodeName() const override { return FName(TEXT("PythonScript")); } protected: virtual FPCGElementPtr CreateElement() const override; public: // Python 스크립트 경로 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Python") FFilePath ScriptPath; // 인라인 Python 코드 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Python", meta = (MultiLine = "true")) FString InlineScript; // Python 함수명 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Python") FString FunctionName = TEXT("process_points"); }; // PCGPythonInteropNode.cpp #include "PCGPythonInteropNode.h" #include "IPythonScriptPlugin.h" #include "PythonScriptTypes.h" class FPCGPythonScriptElement : public FSimplePCGElement { protected: virtual bool ExecuteInternal(FPCGContext* Context) const override { const UPCGPythonScriptSettings* Settings = Context->GetInputSettings<UPCGPythonScriptSettings>(); if (!Settings) return false; // Python 스크립트 플러그인 가져오기 IPythonScriptPlugin& PythonPlugin = FModuleManager::LoadModuleChecked<IPythonScriptPlugin>("PythonScriptPlugin"); // 입력 포인트 수집 TArray<FPCGTaggedData> Inputs = Context->InputData.GetInputs(); TArray<FVector> InputPositions; for (const FPCGTaggedData& Input : Inputs) { if (const UPCGPointData* PointData = Cast<UPCGPointData>(Input.Data)) { for (const FPCGPoint& Point : PointData->GetPoints()) { InputPositions.Add(Point.Transform.GetLocation()); } } } // Python으로 데이터 전달 FPyObjectPtr PyInputList = ConvertToPyList(InputPositions); // Python 함수 호출 FPyObjectPtr PyResult; if (!Settings->InlineScript.IsEmpty()) { PyResult = PythonPlugin.ExecuteString( *Settings->InlineScript, Settings->FunctionName, PyInputList); } else if (!Settings->ScriptPath.FilePath.IsEmpty()) { PyResult = PythonPlugin.ExecuteFile( *Settings->ScriptPath.FilePath, Settings->FunctionName, PyInputList); } // 결과를 PCG 포인트로 변환 TArray<FVector> OutputPositions; ConvertFromPyList(PyResult, OutputPositions); // 출력 데이터 생성 UPCGPointData* OutputData = NewObject<UPCGPointData>(); for (const FVector& Pos : OutputPositions) { FPCGPoint Point; Point.Transform.SetLocation(Pos); OutputData->GetMutablePoints().Add(Point); } FPCGTaggedData& Output = Context->OutputData.TaggedData.Emplace_GetRef(); Output.Data = OutputData; return true; } };
SUMMARY

핵심 요약

  • PCG 프레임워크가 UE 5.7에서 Production Ready 상태로 승격
  • UE 5.5 대비 거의 2배 성능 향상
  • PCG Editor Mode: 스플라인 드로잉, 볼륨 브러시, 독립 실행 그래프 도구
  • 새로운 노드: Spline Intersection, Split Splines, Polygon2D
  • Python Interop Plugin으로 그래프 내 Python 스크립트 실행 지원
  • GPU Fast Geo Interop으로 고밀도 메시 생성 성능 향상
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 프로덕션 레벨 PCG 그래프 구축

PCG 그래프에서 Landscape Sampler, Mesh Spawner, Distance/Density Filter를 조합하여 RPG 오픈월드의 식생을 자동 배치하세요. 결과를 확정(Finalize)하여 에디터에서 영구 배치하세요.

실습 2: PCG 규칙 체계 구축

길, 건물, 수역 근처에 나무가 배치되지 않도록 Exclusion Volume과 Distance Filter를 설정하세요. 바이옴(숲, 초원, 사막)에 따라 다른 식생 세트를 자동 선택하는 로직을 구현하세요.

심화 과제: 대규모 PCG 퍼포먼스 최적화

수 km^2 규모의 오픈월드에 PCG를 적용하고, 빌드 시간과 런타임 성능을 최적화하세요. Partition Grid로 PCG 데이터를 분할하고, World Partition과 연동되는 PCG 스트리밍을 구성하세요.