Unreal Engine에서 Static Mesh를 수정하는 플러그인 개발 도중, RenderData를 기준으로 작업한 코드는 런타임에서만 작동하고 에디터에는 반영되지 않는 문제를 발견했습니다.
FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
RenderData는 런타임 데이터를 기반으로 하며, Static Mesh 에디터의 원본 데이터에 영향을 주지 않습니다. 이로 인해, UV 조정이나 재질 병합 같은 작업이 런타임에서는 정상 동작하더라도, 에디터에서는 반영되지 않는 문제가 있었습니다.
Unreal Engine에서 Static Mesh의 원본 데이터를 수정하려면 FMeshDescription을 사용해야 합니다. FMeshDescription은 Static Mesh의 기본 정보를 담고 있어 에디터에서도 수정된 내용을 반영할 수 있습니다.
수정 전 코드 (RenderData 기반):
void UMyBlueprintFunctions::ConsolidateMaterials(UStaticMesh* StaticMesh)
{
FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
FStaticMeshLODResources& LODResources = RenderData->LODResources[0];
if (LODResources.Sections.Num() > 1)
{
LODResources.Sections.SetNum(1);
LODResources.Sections[0].MaterialIndex = 0;
}
TArray<FStaticMaterial> SingleMaterial;
SingleMaterial.Add(StaticMesh->GetStaticMaterials()[0]);
StaticMesh->SetStaticMaterials(SingleMaterial);
UE_LOG(LogTemp, Log, TEXT("All materials consolidated into a single slot."));
}
수정 후 코드 (FMeshDescription 기반):
void UMyBlueprintFunctions::ConsolidateMaterials(UStaticMesh* StaticMesh)
{
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
TMap<FPolygonGroupID, FPolygonGroupID> PolygonGroupRemap;
const FPolygonGroupID FirstPolygonGroup = MeshDescription->PolygonGroups().GetFirstValidID();
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
if (PolygonGroupID != FirstPolygonGroup)
{
PolygonGroupRemap.Add(PolygonGroupID, FirstPolygonGroup);
}
}
MeshDescription->RemapPolygonGroups(PolygonGroupRemap);
StaticMesh->CommitMeshDescription(0);
UE_LOG(LogTemp, Log, TEXT("All materials consolidated into a single slot."));
}
변경점:
LODResources.Sections 대신 FPolygonGroupID를 사용해 원본 데이터를 수정.MeshDescription->RemapPolygonGroups()로 재질 병합 처리.수정 전 코드 (RenderData 기반):
FStaticMeshLODResources& LODResources = RenderData->LODResources[0];
FStaticMeshVertexBuffers& VertexBuffers = LODResources.VertexBuffers;
FVector2f UV0 = VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index0, UVChannel);
UV0.X += MaterialOffsetX;
VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(Index0, UVChannel, UV0);
수정 후 코드 (FMeshDescription 기반):
TVertexInstanceAttributesRef<FVector2f> UVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
for (const FVertexInstanceID VertexInstanceID : MeshDescription->GetPolygonVertexInstances(PolygonID))
{
FVector2f UV = UVs.Get(VertexInstanceID, UVChannel);
UV.X += MaterialOffsetX;
UVs.Set(VertexInstanceID, UVChannel, UV);
}
변경점:
LODResources.VertexBuffers를 사용하는 대신, VertexInstanceAttributes에서 UV를 직접 수정.MeshDescription->GetPolygonVertexInstances()를 사용해 다각형 데이터를 조작.수정 후 데이터를 에디터와 동기화하려면 CommitMeshDescription() 및 MarkPackageDirty()를 호출해야 합니다.
StaticMesh->CommitMeshDescription(0);
StaticMesh->Modify();
StaticMesh->MarkPackageDirty();
Unreal Engine에서 원본 데이터를 수정하지 않으면, 에디터에서 작업한 결과가 반영되지 않는다는 점을 배웠습니다. 특히, FMeshDescription을 사용하면 런타임 데이터와 원본 데이터를 모두 처리할 수 있어 더욱 일관된 결과를 얻을 수 있었습니다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctions.h"
#include "StaticMeshAttributes.h"
void UMyBlueprintFunctions::ConsolidateMaterials(UStaticMesh* StaticMesh)
{
if (!StaticMesh || StaticMesh->GetNumSourceModels() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid Static Mesh for material consolidation."));
return;
}
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
if (!MeshDescription)
{
UE_LOG(LogTemp, Warning, TEXT("No Mesh Description available."));
return;
}
// Consolidate materials into a single slot
TMap<FPolygonGroupID, FPolygonGroupID> PolygonGroupRemap;
const TArray<FStaticMaterial>& StaticMaterials = StaticMesh->GetStaticMaterials();
if (StaticMaterials.Num() > 1)
{
const FPolygonGroupID FirstPolygonGroup = MeshDescription->PolygonGroups().GetFirstValidID();
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
if (PolygonGroupID != FirstPolygonGroup)
{
PolygonGroupRemap.Add(PolygonGroupID, FirstPolygonGroup);
}
}
MeshDescription->RemapPolygonGroups(PolygonGroupRemap);
// Update material slots
TArray<FStaticMaterial> SingleMaterial;
SingleMaterial.Add(StaticMaterials[0]);
StaticMesh->SetStaticMaterials(SingleMaterial);
}
StaticMesh->CommitMeshDescription(0);
StaticMesh->Modify();
StaticMesh->MarkPackageDirty();
UE_LOG(LogTemp, Log, TEXT("All materials consolidated into a single slot."));
}
UStaticMesh* UMyBlueprintFunctions::ApplyPlanarMappingAndSeparate(UStaticMesh* StaticMesh, int32 UVChannel)
{
if (!StaticMesh || StaticMesh->GetNumSourceModels() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid Static Mesh for processing."));
return nullptr;
}
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
if (!MeshDescription)
{
UE_LOG(LogTemp, Warning, TEXT("No Mesh Description available."));
return nullptr;
}
FBox BoundingBox = MeshDescription->ComputeBoundingBox();
FVector3f Min = FVector3f(BoundingBox.Min);
FVector3f Max = FVector3f(BoundingBox.Max);
FVector3f Size = Max - Min;
float ScaleU = 1.0f / Size.X;
float ScaleV = 1.0f / Size.Z;
TVertexInstanceAttributesRef<FVector2f> UVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs())
{
FVector3f Position = MeshDescription->GetVertexPosition(MeshDescription->GetVertexInstanceVertex(VertexInstanceID));
FVector2f UV;
UV.X = (Position.X - Min.X) * ScaleU;
UV.Y = (Position.Z - Min.Z) * ScaleV;
UVs.Set(VertexInstanceID, UVChannel, UV);
}
StaticMesh->CommitMeshDescription(0);
StaticMesh->Modify();
StaticMesh->MarkPackageDirty();
UE_LOG(LogTemp, Log, TEXT("Planar mapping applied and polygons separated by Material ID."));
return StaticMesh;
}
void UMyBlueprintFunctions::OffsetUVs(UStaticMesh* StaticMesh, int32 TargetMaterialID, float UVRangeOffset, int32 UVChannel)
{
if (!StaticMesh || StaticMesh->GetNumSourceModels() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid Static Mesh or no source data available."));
return;
}
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0);
if (!MeshDescription)
{
UE_LOG(LogTemp, Warning, TEXT("No Mesh Description available."));
return;
}
TVertexInstanceAttributesRef<FVector2f> UVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonGroupAttributesConstRef<FName> PolygonGroupNames = MeshDescription->PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
if (TargetMaterialID <= 0 || TargetMaterialID > PolygonGroupNames.GetNumElements())
{
UE_LOG(LogTemp, Warning, TEXT("Invalid Material ID"));
return;
}
FPolygonGroupID TargetPolygonGroup = FPolygonGroupID(TargetMaterialID - 1);
float MaterialOffsetX = (TargetMaterialID - 1) * UVRangeOffset;
for (const FPolygonID PolygonID : MeshDescription->Polygons().GetElementIDs())
{
if (MeshDescription->GetPolygonPolygonGroup(PolygonID) == TargetPolygonGroup)
{
for (const FVertexInstanceID VertexInstanceID : MeshDescription->GetPolygonVertexInstances(PolygonID))
{
FVector2f UV = UVs.Get(VertexInstanceID, UVChannel);
UV.X += MaterialOffsetX;
UVs.Set(VertexInstanceID, UVChannel, UV);
}
}
}
StaticMesh->CommitMeshDescription(0);
StaticMesh->Modify();
StaticMesh->MarkPackageDirty();
UE_LOG(LogTemp, Log, TEXT("UVs have been adjusted for Material ID %d in UVChannel %d."), TargetMaterialID, UVChannel);
}