참고자료
https://itscai.us/blog/post/ue-physics-framework/
Exploring Unreal's physics framework
Where does Unreal calculate x=x+v*dt, *really*?
itscai.us

Physics의 메모리 사용량 분포
대규모 게임 맵을 구축할 때 물리 시스템의 메모리 점유는 개발자가 반드시 직면해야 하는 과제 중 하나입니다. 본 글에서는 물리 시스템의 메모리 할당 방식을 심층 분석하고, 효과적인 메모리 피크 최적화 전략을 제시하여 개발자가 메모리 사용을 효율적으로 관리할 수 있도록 돕습니다.
ChaosTrimesh
메모리 할당 시기
Chaos 소스 코드에서 ChaosTrimesh가 메모리를 할당하는 부분을 확인할 수 있습니다:
//ChaosDerivedDataReader.cpp
{
LLM_SCOPE(ELLMTag::ChaosTrimesh);
ChaosAr << TrimeshImplicitObjects << UVInfo << FaceRemap;
}
이 코드에는 세 가지 핵심 메모리 할당 부분인 TrimeshImplicitObjects, UVInfo 및 FaceRemap이 포함됩니다. 메모리 분석을 세분화하기 위해 소스 코드를 수정하여 아래와 같이 이 세 메모리 할당 영역을 각각 추적할 것을 권장합니다:
{
LLM_SCOPE(ELLMTag::ChaosTrimesh);
LLM_SCOPE_BYNAME(TEXT("Physics/TrimeshImplicitObjects"));
ChaosAr << TrimeshImplicitObjects;
}
{
LLM_SCOPE_BYNAME(TEXT("Physics/UVInfo"));
ChaosAr << UVInfo;
}
{
LLM_SCOPE_BYNAME(TEXT("Physics/FaceRemap"));
ChaosAr << FaceRemap;
}
이렇게 소스 코드를 수정하면 메모리 분석 도구에서 세 가지 측면의 메모리 할당 상황을 더 명확하게 식별할 수 있습니다.。
TrimeshImplicitObjects
선행 지식
ChaosTriMeshes는 일반적으로 복잡한 충돌 모델 처리에 사용됩니다. UE5에서 ChaosTrimesh와 ChaosConvex는 일반적으로 UStaticMesh 로드 시 메모리가 사전 할당됩니다. 이 과정은 주로 BodySetup.cpp의 FinishCreatingPhysicsMeshes_Chaos 메서드에서 발생합니다. 구체적인 물리 지오메트리의 충돌 생성 과정은 ChaosInterfaceUtils.cpp에서 구현됩니다.
// ChaosInterfaceUtils.cpp
// void CreateGeometry
const bool bMakeComplexGeometry
= (CollisionTraceType != CTF_UseSimpleAsComplex) || (SimpleShapeCount == 0);
bMakeComplexGeometry==true인 경우, 복잡한 콜리전을 생성하게 되며 이는 TrimeshImplicitObjects를 생성하는 것을 의미합니다. 게임이 쿡(Cook) 처리될 때, 프로젝트 설정 및 스태틱 메시(staticMesh) 상의 설정에 따라 이 정보가 실제 패키지에 쿡될지 여부가 결정됩니다.
// BodySetup.cpp
void UBodySetup::GetCookInfo(FCookBodySetupInfo& OutCookInfo, EPhysXMeshCookFlags InCookFlags) const
ComplexCollisionMesh가 지정된 경우 이를 복잡한 충돌 메시로 사용하며, 그렇지 않으면 LODForCollision에서 지정한 LOD를 복잡한 충돌 메시로 사용합니다:
// StaticMesh.cpp
// bool UStaticMesh::GetPhysicsTriMeshDataCheckComplex
const int32 UseLODIndex = bInUseAllTriData ? 0 : FMath::Clamp(LODForCollision, 0, GetRenderData()->LODResources.Num()-1);
FStaticMeshLODResources& LOD = GetRenderData()->LODResources[UseLODIndex];
Nanite의 경우 NaniteMesh에는 LOD가 없으며, 대신 FallbackMesh를 사용합니다(FallbackMesh는 Nanite를 지원하지 않는 머신에서 이 메쉬를 렌더링하는 것을 의미합니다). 최종 쿡(Cook) 단계에서 Chaos는 이 메쉬를 단순화하기 위해 일부 정리 작업을 수행하여 불필요한 정점을 제거하지만, 크게 단순화되지는 않습니다.
// ChaosDerivedDataUtil.cpp
void CleanTrimesh(TArray<FVector3f>& InOutVertices, TArray<int32>& InOutIndices, TArray<int32>* OutOptFaceRemap, TArray<int32>* OutOptVertexRemap)
주로 중복된 정점을 찾아 병합하는 작업으로, 공간 가속 구조(예: AABB 트리)를 사용하여 공간 내에서 매우 가까운 정점을 빠르게 찾아냅니다. 이 정점들은 중복된 것으로 간주되며, 임계값 WeldThresholdSq를 설정하여 어느 정도 가까운 정점을 중복으로 처리할지 결정합니다. 마지막으로 각 고유 정점에 새로운 인덱스를 할당하고, 모든 중복 정점을 이 새로운 인덱스에 매핑합니다.
어떻게 최적화할 것인가
복잡한 충돌이 필요 없는 프로젝트에서는 Project Settings → Engine → Physics → Simulation → Default Shape Complexity 옵션을 Use Simple Collision As Complex로 변경하면 Trimesh Implicit Objects 부분의 메모리 할당을 직접 제거할 수 있습니다. 프로젝트에 Complex 충돌이 필요한 경우, 각 StaticMesh->Collision->CollisionComplexity에서 이 속성을 개별적으로 설정해야 합니다.
UVInfo
사전 지식
/** UV information for BodySetup, only created if UPhysicsSettings::bSupportUVFromHitResults */
struct FBodySetupUVInfo
{
/** Index buffer, required to go from face index to UVs */
TArray<int32> IndexBuffer;
/** Vertex positions, used to determine barycentric co-ords */
TArray<FVector> VertPositions;
/** UV channels for each vertex */
TArray< TArray<FVector2D> > VertUVs;
friend FArchive& operator<<(FArchive& Ar, FBodySetupUVInfo& UVInfo)
{
Ar << UVInfo.IndexBuffer;
Ar << UVInfo.VertPositions;
Ar << UVInfo.VertUVs;
return Ar;
}
/** Get resource size of UV info */
void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) const;
void FillFromTriMesh(const FTriMeshCollisionData& TriMeshCollisionData);
};
콜리전 감지를 위한 다양한 정보를 저장하여 콜리전 포인트의 UV를 실제 메시로 복원합니다.
// StaticMesh.cpp
// bool UStaticMesh::GetPhysicsTriMeshDataCheckComplex
bool bCopyUVs = bSupportPhysicalMaterialMasks || UPhysicsSettings::Get()->bSupportUVFromHitResults; // See if we should copy UVs
// If copying UVs, allocate array for storing them
if (bCopyUVs)
{
CollisionData->UVs.AddZeroed(LOD.GetNumTexCoords());
}
다음은 Cook의 정보를 바탕으로 제작된 실제 게임에서 사용된 콜리전입니다.
최적화 방법
/**
* Try and find the UV for a collision impact. Note this ONLY works if 'Support UV From Hit Results' is enabled in Physics Settings.
*/
UFUNCTION(BlueprintPure, Category = "Collision")
static ENGINE_API bool FindCollisionUV(const struct FHitResult& Hit, int32 UVChannel, FVector2D& UV);
UVInfo의 경우 프로젝트가 콜리전 감지 결과에서 UV 좌표를 검색하는 기능(예: FindCollisionUV)에 의존하지 않는 경우 프로젝트 설정에서 해당 지원 옵션을 끄면 불필요한 메모리 소비를 줄일 수 있습니다.ProjectSetting->Engine->Physics->Optimisation->SupportUVFromHitResults. >SupportUVFromHitResults에서 해제할 수 있지만, 특정 메시에서 bSupportPhysicalMaterialMasks가 체크되어 있으면 실제 런타임에서 해당 메시의 UV 정보를 계속 생성합니다.
FaceRemap
사전 지식
/** Additional face remap table, if available. Used for determining face index mapping from collision mesh to static mesh, for use with physical material masks */
TArray<int32> FaceRemap;
일반적으로 충돌 감지를 위해 3D 모델을 물리 엔진으로 임포트할 때 모델의 지오메트리 데이터는 런타임 성능을 개선하기 위해 최적화된 포맷으로 쿠킹됩니다. 이 과정에서 UV 좌표 및 페이스 인덱스와 같은 일부 원시 모델 데이터는 충돌 감지에 필요하지 않으므로 폐기될 수 있습니다.
그러나 경우에 따라 이 데이터를 유지하는 것이 유용할 수 있습니다. 예를 들어, 물리적 머티리얼 기반 물리적 머티리얼 마스크를 충돌 쿼리 결과에서 지원해야 하는 경우 원본 모델의 UV 좌표와 페이스 인덱스에 대한 액세스가 필요합니다. 물리적 머티리얼 마스크를 사용하면 개발자가 씬 쿼리에서 각 페이스의 머티리얼 유형을 정의할 수 있으므로 머티리얼의 물리적 특성(예: 마찰, 탄성)에 따라 다음과 같이 충돌 응답을 세밀하게 조정할 수 있습니다. 머티리얼의 물리적 특성(예: 마찰, 탄성)에 따라 충돌 반응을 미세 조정할 수 있습니다.
FaceRemap 테이블은 최적화된 메시 데이터와 원본 모델 데이터 간의 매핑을 생성하는 데 사용됩니다. FaceRemap이 지원된다는 것은 씬 쿼리에서 물리적 머티리얼을 지원하기 위해 물리적 메시가 UV 좌표와 페이스 리맵 테이블을 저장한다는 의미입니다. 즉, 최적화 후에도 이 매핑 테이블을 통해 원본 얼굴 인덱스와 해당 UV 좌표를 검색하여 더 복잡한 인터랙션 효과를 구현하는 데 사용할 수 있습니다.
최적화 방법
페이스맵은 특정 스태틱 메시에서 bSupportPhysicalMaterialMasks가 체크된 경우에만 생성되며, 일반 프로젝트에는 오버헤드가 없습니다.
ChaosGeometry
메모리 할당 타이밍
// PhysInterface_Chaos.cpp
void FPhysInterface_Chaos::AddGeometry(FPhysicsActorHandle& InActor, const FGeometryAddParams& InParams, TArray<FPhysicsShapeHandle>* OutOptShapes)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FPhysInterface_Chaos::AddGeometry);
LLM_SCOPE(ELLMTag::ChaosGeometry);
TArray<TUniquePtr<Chaos::FImplicitObject>> Geoms;
Chaos::FShapesArray Shapes;
ChaosInterface::CreateGeometry(InParams, Geoms, Shapes);
...
}
맵 로딩 중에 카오스 피직스 시스템은 각 오브젝트에 적합한 콜리전 바디를 생성하며, 이 단계에서 카오스 지오메트리와 관련된 메모리 할당이 이루어집니다.
// ChaosInterfaceUtils.cpp
void CreateGeometry(const FGeometryAddParams& InParams, TArray<TUniquePtr<Chaos::FImplicitObject>>& OutGeoms, Chaos::FShapesArray& OutShapes)
{
LLM_SCOPE(ELLMTag::ChaosGeometry);
const FVector& Scale = InParams.Scale;
TArray<TUniquePtr<Chaos::FImplicitObject>>& Geoms = OutGeoms;
Chaos::FShapesArray& Shapes = OutShapes;
ECollisionTraceFlag CollisionTraceType = InParams.CollisionTraceType;
...
}
CreateGeometry 함수 내에서 카오스 엔진은 각 기본 지오메트리 모양(예: 구, 큐브, 캡슐, 원뿔 캡슐 등)에 대한 ImplicitObject를 구성합니다.FImplicitObject에는 모양에 대한 실제 지오메트리 데이터(예: 다음과 같은)가 포함됩니다. 구의 위치와 반지름.
// ChaosInterfaceUtils.cpp
for (const FKSphereElem& SphereElem : InParams.Geometry->SphereElems)
{
const FKSphereElem ScaledSphereElem = SphereElem.GetFinalScaled(Scale, InParams.LocalTransform);
const float UseRadius = FMath::Max(ScaledSphereElem.Radius, UE_KINDA_SMALL_NUMBER);
auto ImplicitSphere = MakeUnique<Chaos::TSphere<Chaos::FReal, 3>>(ScaledSphereElem.Center, UseRadius);
TUniquePtr<Chaos::FPerShapeData> NewShape = NewShapeHelper(MakeSerializable(ImplicitSphere), Shapes.Num(), (void*)SphereElem.GetUserData(), SphereElem.GetCollisionEnabled());
Shapes.Emplace(MoveTemp(NewShape));
Geoms.Emplace(MoveTemp(ImplicitSphere));
}
구형 콜리전 클래스 TSphere final : public FImplicitObject 를 만드는 예제를 사용하면, 카오스는 모든 구형 지오메트리 엘리먼트(FKSphereElem)를 반복하고 각각에 해당하는 Chaos::FPerShapeData 및 Chaos::TSphere 를 생성합니다. 그런 다음 카오스 피직스 엔진에서 사용할 수 있도록 해당 셰이프 및 지오메트리 컬렉션에 추가합니다.
최적화 권장 사항
씬의 총 충돌기 수를 줄이거나 더 복잡한 범프 지오메트리를 대략적으로 대체하는 간단한 지오메트리를 사용하는 것이 가장 간단하고 효과적인 전략입니다. 이렇게 하면 메모리 공간을 줄일 수 있을 뿐만 아니라 물리 시뮬레이션의 계산 효율도 향상됩니다.
ChaosUpdate
LLM_SCOPE(ELLMTag::ChaosUpdate)로 표시된 곳에서 볼 수 있듯이, ChaosUpdate에 메모리를 할당하는 코드는 모두 PhysicsSolverBase.cpp에 있습니다. 대부분의 메모리 할당 오버헤드는 각 지오메트리에 해당하는 ShapeInstance를 생성하는 데 있습니다.
// ShapeInstance.cpp
CHAOS_API static TUniquePtr<FShapeInstance> Make(int32 InShapeIdx, TSerializablePtr<FImplicitObject> InGeometry);
기술적 배경
class FShapeInstance : public FPerShapeData
{
...
union FMaterialUnion
{
FMaterialUnion() : MaterialHandle() {} // Default to single-shape mode
~FMaterialUnion() {} // Destruction handled by FShapeInstance
FMaterialHandle MaterialHandle; // Set if we have only 1 material, no masks etc
FMaterialData* MaterialData; // Set if we have multiple materials or any masks
};
FCollisionData CollisionData;
mutable FMaterialUnion Material;
// FPerShapeData
EPerShapeDataType Type : 2;
uint32 bIsSingleMaterial : 1; // For use by FShapeInstance (here because the space is available for free)
uint32 ShapeIdx : 29;
FShapeDirtyFlags DirtyFlags; // For use by FShapeInstanceProxy as there's 4 bytes of padding here
TSerializablePtr<FImplicitObject> Geometry;
TAABB<FReal, 3> WorldSpaceInflatedShapeBounds;
...
}
struct FCollisionData
{
FCollisionFilterData QueryData;
FCollisionFilterData SimData;
void* UserData;
EChaosCollisionTraceFlag CollisionTraceType;
uint8 bSimCollision : 1;
uint8 bQueryCollision : 1;
uint8 bIsProbe : 1;
...
}
카오스는 계산 복잡성을 줄이기 위해 가능한 한 단순화된 방식으로 물체의 물리적 동작을 처리하는 것을 목표로 합니다. 이 프레임워크에서는 구와 정육면체와 같은 단순한 기하학이 선호되는 처리 단위가 됩니다. 이 접근 방식은 효율적이지만 더 복잡한 객체를 다룰 때는 한계가 있습니다.
현실 세계의 다양한 오브젝트를 보다 정확하게 시뮬레이션하기 위해 카오스에서는 컨벡스 메시 개념을 도입했습니다. 단순한 구든 삼각형으로 구성된 복잡한 메시든, 3D 기하학적 표현인 컨벡스 메시의 기반이 됩니다.
기술적인 수준에서 ConvexMesh는 두 가지 주요 데이터 유형, 즉 FImplicitObject와 그 서브클래스, FPerShapeData를 통해 표현됩니다.FImplicitObject는 주로 모양의 기본 지오메트리 데이터를 전달합니다. 구의 경우, 구의 중심 위치 및 구의 반지름과 같은 정보를 포함합니다. 반면 FPerShapeData에는 물리적 재질 및 충돌 반응 채널과 같이 순수한 기하학적 정보가 아닌 도형에 대한 다른 중요한 속성이 포함되어 있습니다.
최적화 권장 사항
여기와 카오스 지오메트리는 비슷하지만 하나의 콜리전 아래 다른 정보에 속하며, 씬의 총 콜리더 수를 나타내는 카오스 업데이트의 메모리가 너무 높습니다.
ChaosAcceleration
기술적 배경
LLM_SCOPE(ELLMTag::ChaosAcceleration) 로 표시된 곳에서 볼 수 있듯이, ChaosAcceleration 에 메모리를 할당하는 코드는 모두 PBDRigidsEvolution.cpp 에 있으며, 이 섹션의 메모리는 공간 가속 구조체( SpatialAcceleration) 구성에 사용됩니다.
// ISpatialAcceleration.h
using SpatialAccelerationType = uint8; //see ESpatialAcceleration. Projects can add their own custom types by using enum values higher than ESpatialAcceleration::Unknown
enum class ESpatialAcceleration : SpatialAccelerationType
{
BoundingVolume,
AABBTree,
AABBTreeBV,
Collection,
Unknown,
//For custom types continue the enum after ESpatialAcceleration::Unknown
};
UE5 에는 위의 가속화된 구조가 있으며, 실제 프로젝트에서 대부분의 메모리는 AABB트리를 구성하는 데 사용됩니다. 리짓 바디가 추가, 삭제되거나 리짓 바디의 상태(예: 위치, 회전)가 변경될 때 AABB트리가 업데이트됩니다. 이는 AABB 트리의 구조가 리지드 바디의 AABB(축 정렬 브래킷 박스)에 따라 달라지고, 리지드 바디의 상태가 변경되면 해당 AABB가 변경되어 AABB 트리의 구조에 영향을 미칠 수 있기 때문입니다.
새로운 강체가 월드에 추가되었는데 AABB 트리가 아직 업데이트되지 않은 경우 충돌 감지는 이러한 새로운 강체를 독립된 개체(더티 엘리먼트)로 취급합니다. 즉, 트리에 없는 이러한 리지드 바디는 충돌 감지 중에 추가로 고려됩니다. 이 접근 방식은 트리 구조가 아직 업데이트되지 않은 경우에도 새로 추가된 리지드 바디가 충돌 감지에 포함되도록 합니다. 트리 구조가 업데이트되면 캐시된 리지드 바디는 새 트리 구조에 통합되고 메인 스레드의 AABB 트리로 업데이트됩니다.
이진 트리는 균형 트리인지 아닌지를 고려해야 하며, 균형 트리가 아닌 경우 잦은 추가 및 삭제 후 연쇄 목록으로 쿼리 효율이 저하될 수 있습니다. 하지만 실제 개발에서는 노드를 삽입할 때마다 밸런싱을 하는 데 시간이 많이 걸리는 작업도 허용되지 않기 때문에 카오스에서는 이 부분을 절충하는 방법을 채택했습니다.
//AABBTree.h
// Chaos查找插入位置的方法:
int32 FindBestSibling(const TAABB<T, 3>& InNewBounds, bool& bOutAddToLeaf)
최적의 삽입 위치를 찾기 위한 이 방법의 선택은 휴리스틱을 기반으로 하며 트리의 균형을 보장하지 않습니다. 따라서 이 방법으로 구성된 트리는 반드시 균형 잡힌 이진 트리가 아닙니다. 대신 휴리스틱 평가를 기반으로 한 최적에 가까운 이진 트리이므로 실제 애플리케이션에서 트리의 성능이 충분히 우수할 수 있지만 균형 이진 트리의 엄격한 균형 보장은 제공하지 않습니다.
template <typename TPayloadType, bool bComputeBounds = true, typename T = FReal>
struct TAABBTreeLeafArray : public TBoundsWrapperHelper<TPayloadType, T, bComputeBounds>
{
...
TArray<TPayloadBoundsElement<TPayloadType, T>> Elems;
bool bDirtyLeaf = false;
...
}
TAABBTree의 비리프 노드는 TAABBTreeNode이며, 부모 노드와 자식 노드의 인덱스를 포함하는 것 외에도 주요 정보는 두 자식 노드의 AABB를 둘러싸는 박스입니다.
template <typename TPayloadType, bool bComputeBounds = true, typename T = FReal> struct TAABBTreeLeafArray : public TBoundsWrapperHelper<TPayloadType, T, bComputeBounds> { ... TArray<TPayloadBoundsElement<TPayloadType, T>> Elems; bool bDirtyLeaf = false; ... }
TAABBTree의 리프 노드에는 다음과 같은 배열이 포함됩니다.
:TAABBTreeLeafArray<FAccelerationStructureHandle>
class FAccelerationStructureHandle
{
...
FGeometryParticle* ExternalGeometryParticle;
FGeometryParticleHandle* GeometryParticleHandle;
FUniqueIdx CachedUniqueIdx;
FCollisionFilterData UnionQueryFilterData;
FCollisionFilterData UnionSimFilterData;
bool bCanPrePreFilter;
...
}
template <typename T, int d>
class TGeometryParticle
{
...
TChaosProperty<FParticlePositionRotation, EChaosProperty::XR> MXR;
TChaosProperty<FParticleNonFrequentData,EChaosProperty::NonFrequentData> MNonFrequentData;
void* MUserData;
FShapeInstanceProxyArray MShapesArray;
...
}
class FParticleNonFrequentData
{
...
TSharedPtr<const FImplicitObject,ESPMode::ThreadSafe> MGeometry;
FUniqueIdx MUniqueIdx;
FSpatialAccelerationIdx MSpatialIdx;
FParticleID MParticleID;
EResimType MResimType;
bool MEnabledDuringResim;
...
}
각 리프 노드는 변위, 회전, 지오메트리 포인터, 기타 관련 피직스 및 콜리전 프로퍼티를 포함하는 하나 이상의 FGeometryParticle 인스턴스와 연결됩니다.
최적화 권장 사항
리지드 바디의 위치와 회전이 변경되면 AABBTree가 재구성되며, 카오스액셀러레이션 메모리 최적화의 핵심은 게임 월드에서 모션 프레임당 충돌하는 바디의 수를 줄이는 것입니다.
AncientValley 작동 중
다운로드 및 패키징

먼저 에픽게임런처에서 AncientValley 프로젝트를 다운로드하세요.

프로젝트를 열고 패키지, 대상 플랫폼 창을 선택하고 여기에서 개발 패키지를 누르면 콘솔 명령을 잃기 쉽습니다 (필요한 경우).
메모리 분석은 에디터에서도 수행할 수 있지만, 에디터가 실제 게임에서 사용되지 않을 수 있는 많은 리소스를 로드하기 때문에 대부분 의미가 없습니다.
메모리 분석 시작 준비 완료

패키지가 완료되면 파일을 마우스 오른쪽 버튼으로 클릭하여 바로 가기를 만듭니다.

대상 공간 다음에 메모리 분석을 위한 시작 항목 매개 변수를 추가합니다. 여기서 권장되는 매개 변수는 다음과 같습니다.
-trace=default,memory,metadata,assetmetadata,loadtime -tracehost=XXXXX -llm
XXXX를 컴퓨터의 IP 주소로 바꾸세요.
그런 다음 언리얼인사이트를 연 상태에서 실행하면 메모리 분석이 시작되며, 보스전이 끝나면 종료하세요.
메모리 분석

빨간색 상자의 A를 LLM 합계에서 가장 높은 지점으로 드래그하고, 규칙에서 ActiveAllocs를 선택한 다음 쿼리 실행을 클릭합니다.

다음은 위의 계층 구조를 약간 변경한 것으로, 먼저 태그 행을 기준으로 한 다음 패키지를 기준으로 합니다.

이 프로젝트의 피직스는 대부분 카오스트리메시에 사용되었습니다.

패키지별로 정렬하는 경우 확장하여 특정 에셋 이름을 확인합니다.
정렬

위에서 언급한 프로젝트 프리셋을 UseSimpleAsComplex로 변경하면 복잡한 콜리전에 사용할 특정 에셋을 여전히 복잡한 콜리전에 사용한 다음 테스트를 위해 패키징할 수 있습니다.

프로젝트의 피직스 메모리 피크가 392MB에서 84.3MB로, 카오스트리메시 섹션은 317MB에서 9.5MB로 감소한 것을 확인할 수 있습니다.
궁극
이 글은 길이가 제한되어 있으므로 독자들이 UE5 프로젝트에서 메모리 급증을 최적화하는 데 도움이 되기를 바랍니다. 읽어주셔서 감사드리며 이 콘텐츠가 도움이 되셨기를 바랍니다. 제 동료에게 감사드립니다.
류유안유
이 글을 작성하는 동안 기술 지원을 제공했습니다. 댓글 섹션에서 피크 메모리 최적화와 관련된 콘텐츠를 공유할 수도 있습니다.