前言
这次是给TA大佬打打杂~
针对使用需要修改了一下UE的MergeActors方法,方便美术使用,提高美术制作效率。
主要问题有:
工具Merge之后的Actor的中心轴点是(0,0,0),场景中分批合并后所有的Actor的中心轴点全都在中间,有的距离会非常远,不方便使用
Merge后的Actor的名字是类类型+数字,和原本的不好对上,不直观
没有返回值,希望批量化处理的时候能拿到Merge的Actors进行其他的处理
按照我们的设置,只会Merge出一个Actor,但是由于StaticMeshComponents的不相同,每个合并出的Actor上会有不同的UInstancedStaticMeshComponent实例,不如直接合并成不相同的Actors,更方便一点
版本 4.27
相关结构体
FMeshInstancingSettings
一个参数结构体,用来方便传递参数,具体参数意义可以看下面的源码部分的注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 USTRUCT (Blueprintable)struct FMeshInstancingSettings { GENERATED_BODY () FMeshInstancingSettings () : ActorClassToUse (AActor::StaticClass ()) , InstanceReplacementThreshold (2 ) , MeshReplacementMethod (EMeshInstancingReplacementMethod::KeepOriginalActorsAsEditorOnly) , bSkipMeshesWithVertexColors (true ) , bUseHLODVolumes (true ) , ISMComponentToUse (UInstancedStaticMeshComponent::StaticClass ()) {} UPROPERTY (BlueprintReadWrite, EditAnywhere, NoClear, Category="Instancing" ) TSubclassOf<AActor> ActorClassToUse; UPROPERTY (BlueprintReadWrite, EditAnywhere, Category="Instancing" , meta=(ClampMin=1 )) int32 InstanceReplacementThreshold; UPROPERTY (BlueprintReadWrite, EditAnywhere, Category="Instancing" ) EMeshInstancingReplacementMethod MeshReplacementMethod; UPROPERTY (BlueprintReadWrite, EditAnywhere, Category="Instancing" ) bool bSkipMeshesWithVertexColors; UPROPERTY (BlueprintReadWrite, EditAnywhere, Category="Instancing" , meta=(DisplayName="Use HLOD Volumes" )) bool bUseHLODVolumes; UPROPERTY (BlueprintReadWrite, EditAnywhere, Category = "Instancing" , meta = (DisplayName = "Select the type of Instanced Component" , DisallowedClasses = "FoliageInstancedStaticMeshComponent" )) TSubclassOf<UInstancedStaticMeshComponent> ISMComponentToUse; };
FComponentEntry
一个局部定义的结构体,主要目的是存储UStaticMeshComponent的一些数据,方便合并出的新的Component使用
没什么好说的,变量名都很直观
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct FComponentEntry { FComponentEntry (UStaticMeshComponent* InComponent) { StaticMesh = InComponent->GetStaticMesh (); InComponent->GetUsedMaterials (Materials); bReverseCulling = InComponent->GetComponentTransform ().ToMatrixWithScale ().Determinant () < 0.0f ; CollisionProfileName = InComponent->GetCollisionProfileName (); CollisionEnabled = InComponent->GetCollisionEnabled (); OriginalComponents.Add (InComponent); } bool operator ==(const FComponentEntry& InOther) const { return StaticMesh == InOther.StaticMesh && Materials == InOther.Materials && bReverseCulling == InOther.bReverseCulling && CollisionProfileName == InOther.CollisionProfileName && CollisionEnabled == InOther.CollisionEnabled; } UStaticMesh* StaticMesh; TArray<UMaterialInterface*> Materials; TArray<UStaticMeshComponent*> OriginalComponents; FName CollisionProfileName; bool bReverseCulling; ECollisionEnabled::Type CollisionEnabled; };
FActorEntry
一个局部结构体,要保存StaticMeshComponents生成的FComponentEntry,有一个MergedActor成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct FActorEntry { FActorEntry (UStaticMeshComponent* InComponent, ULevel* InLevel) : MergedActor (nullptr ) { if (InLevel) { for (AActor* Actor : InLevel->Actors) { if (AHierarchicalLODVolume* HierarchicalLODVolume = Cast <AHierarchicalLODVolume>(Actor)) { FBox BoundingBox = InComponent->Bounds.GetBox (); FBox VolumeBox = HierarchicalLODVolume->GetComponentsBoundingBox (true ); if (VolumeBox.IsInside (BoundingBox) || (HierarchicalLODVolume->bIncludeOverlappingActors && VolumeBox.Intersect (BoundingBox))) { HLODVolume = HierarchicalLODVolume; break ; } } } } } bool operator ==(const FActorEntry& InOther) const { return HLODVolume == InOther.HLODVolume; } AActor* MergedActor; AHierarchicalLODVolume* HLODVolume; TArray<FComponentEntry> ComponentEntries; };
原处理流程
处理Static Mesh Components
收集有效的Static Mesh Components,根据需要选择是否跳过有顶点颜色的Component
生成StaticMeshComponent的FActorEntry
对所有有效的Components生成对应FComponentEntry
将ComponentEntry添加到ActorEntry的ComponentEntries中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 auto HasInstanceVertexColors = [](UStaticMeshComponent* StaticMeshComponent) { for (const FStaticMeshComponentLODInfo& CurrentLODInfo : StaticMeshComponent->LODData) { if (CurrentLODInfo.OverrideVertexColors != nullptr || CurrentLODInfo.PaintedVertices.Num () > 0 ) { return true ; } } return false ; }; TArray<UStaticMeshComponent*> ValidComponents;for (UPrimitiveComponent* ComponentToMerge : ComponentsToMerge) { if (UStaticMeshComponent* StaticMeshComponent = Cast <UStaticMeshComponent>(ComponentToMerge)) { if (StaticMeshComponent->GetOwner ()->GetClass () != MeshInstancingSettings.ActorClassToUse.Get ()) { if ( !MeshInstancingSettings.bSkipMeshesWithVertexColors || !HasInstanceVertexColors (StaticMeshComponent)) { ValidComponents.Add (StaticMeshComponent); } } } }
生成ActorEntries,这里如果bUseHLODVolumes是false的情况,那就只会有一个ActorEntry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 TArray<FActorEntry> ActorEntries;for (UStaticMeshComponent* StaticMeshComponent : ValidComponents) { int32 ActorEntryIndex = ActorEntries.AddUnique (FActorEntry (StaticMeshComponent, InSettings.bUseHLODVolumes ? Level : nullptr )); FActorEntry& ActorEntry = ActorEntries[ActorEntryIndex]; FComponentEntry ComponentEntry (StaticMeshComponent) ; if (FComponentEntry* ExistingComponentEntry = ActorEntry.ComponentEntries.FindByKey (ComponentEntry)) { ExistingComponentEntry->OriginalComponents.Add (StaticMeshComponent); } else { ActorEntry.ComponentEntries.Add (ComponentEntry); } }
筛选ActorEntries
1 2 3 4 5 6 7 8 9 10 11 for (FActorEntry& ActorEntry : ActorEntries) { ActorEntry.ComponentEntries = ActorEntry.ComponentEntries.FilterByPredicate ([&InSettings](const FComponentEntry& InEntry) { return InEntry.OriginalComponents.Num () >= InSettings.InstanceReplacementThreshold; }); } ActorEntries.RemoveAll ([](const FActorEntry& ActorEntry){ return ActorEntry.ComponentEntries.Num () == 0 ; });
保存原始StaticMeshActors
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 TArray<AActor*> ActorsToCleanUp;for (FActorEntry& ActorEntry : ActorEntries) { for (const FComponentEntry& ComponentEntry : ActorEntry.ComponentEntries) { for (UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents) { if (AActor* OriginalActor = OriginalComponent->GetOwner ()) { ActorsToCleanUp.AddUnique (OriginalActor); } } } }
合并StaticMeshComponents
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 Level->Modify (); FActorSpawnParameters Params; Params.OverrideLevel = Level;for (FActorEntry& ActorEntry : ActorEntries) { ActorEntry.MergedActor = World->SpawnActor <AActor>(InSettings.ActorClassToUse.Get (), Params); for (const FComponentEntry& ComponentEntry : ActorEntry.ComponentEntries) { UInstancedStaticMeshComponent* NewComponent = nullptr ; NewComponent = (UInstancedStaticMeshComponent*)ActorEntry.MergedActor->FindComponentByClass (InSettings.ISMComponentToUse.Get ()); if (NewComponent && NewComponent->PerInstanceSMData.Num () > 0 ) { NewComponent = nullptr ; } if (NewComponent == nullptr ) { NewComponent = NewObject <UInstancedStaticMeshComponent>(ActorEntry.MergedActor, InSettings.ISMComponentToUse.Get ()); if (ActorEntry.MergedActor->GetRootComponent ()) { NewComponent->AttachToComponent (ActorEntry.MergedActor->GetRootComponent (), FAttachmentTransformRules::KeepRelativeTransform); } else { ActorEntry.MergedActor->SetRootComponent (NewComponent); } ActorEntry.MergedActor->RemoveOwnedComponent (NewComponent); NewComponent->CreationMethod = EComponentCreationMethod::Instance; ActorEntry.MergedActor->AddOwnedComponent (NewComponent); } NewComponent->SetStaticMesh (ComponentEntry.StaticMesh); for (int32 MaterialIndex = 0 ; MaterialIndex < ComponentEntry.Materials.Num (); ++MaterialIndex) { NewComponent->SetMaterial (MaterialIndex, ComponentEntry.Materials[MaterialIndex]); } NewComponent->SetReverseCulling (ComponentEntry.bReverseCulling); NewComponent->SetCollisionProfileName (ComponentEntry.CollisionProfileName); NewComponent->SetCollisionEnabled (ComponentEntry.CollisionEnabled); NewComponent->SetMobility (EComponentMobility::Static); for (UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents) { NewComponent->AddInstance (OriginalComponent->GetComponentTransform ()); } NewComponent->RegisterComponent (); } World->UpdateCullDistanceVolumes (ActorEntry.MergedActor); }
清理原始Actors
就是很简单的根据设置看看需不需要把被合并的那些Actors删掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (AActor* ActorToCleanUp : ActorsToCleanUp) { if (InSettings.MeshReplacementMethod == EMeshInstancingReplacementMethod::RemoveOriginalActors) { ActorToCleanUp->Destroy (); } else if (InSettings.MeshReplacementMethod == EMeshInstancingReplacementMethod::KeepOriginalActorsAsEditorOnly) { ActorToCleanUp->Modify (); ActorToCleanUp->bIsEditorOnlyActor = true ; ActorToCleanUp->SetHidden (true ); ActorToCleanUp->bHiddenEd = true ; ActorToCleanUp->SetIsTemporarilyHiddenInEditor (true ); } }
针对问题做出的改动
根据需求这里没有直接修改源码哦
Actor名字问题
这个容易,但是美术需要的其实是outline里面显示的那个名字,SpawnActor的Params的Name是Name不是显示出来的这个名字
这里直接取会被合并的一堆里面的第一个名字直接加后缀
1 2 3 4 5 6 7 8 9 10 11 12 if (ActorEntry.ComponentEntry.OriginalComponents.Num () > 0 ) { MergedActorName = ActorEntry.ComponentEntry.OriginalComponents[0 ]->GetOuter ()->GetName () + FString (TEXT ("_merged" )); }else { MergedActorName = ActorsToCleanUp[0 ]->GetName () + FString (TEXT ("_merged" )); } ActorEntry.MergedActor->SetActorLabel (MergedActorName);
分成多个Actor
如果要分成多个Actor,那就说明每个Actor只有一个Component
先把结构体给改掉:
把之前的Array改为了一个对象,因为只需要一个
1 2 3 4 5 6 7 8 9 // 省略部分源码 struct FComponentEntry { AActor* MergedActor; AHierarchicalLODVolume* HLODVolume;- TArray<FComponentEntry> ComponentEntries; + FComponentEntry ComponentEntry; };
然后还是生成ActorEntries
只有当Component生成的ComponentEntry完全相同的时候才会合并到一个Actor上
否则就会创建一个新的Actor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 TArray<FActorEntry> ActorEntries;auto GenActorEntry = [](FComponentEntry& ComponentEntry, TArray<FActorEntry>& ActorEntries, UStaticMeshComponent* StaticMeshComponent) { for (auto & ActorEntry : ActorEntries) { if (ActorEntry.ComponentEntry == ComponentEntry) { ActorEntry.ComponentEntry.OriginalComponents.Add (StaticMeshComponent); return true ; } } FActorEntry ActorEntry = FActorEntry (StaticMeshComponent, nullptr ); ActorEntry.ComponentEntry = ComponentEntry; ActorEntries.Add (ActorEntry); return false ; };for (UStaticMeshComponent* StaticMeshComponent : ValidComponents) { FComponentEntry ComponentEntry (StaticMeshComponent) ; GenActorEntry (ComponentEntry, ActorEntries, StaticMeshComponent); }
生成的Actor轴心点位置问题
不出意外的情况下,理论上OriginalComponents不可能是空的
对所有的OriginalComponents的位置去一个平均值,然后取整方便后续的计算使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if (ActorEntry.ComponentEntry.OriginalComponents.Num () > 0 ) { for (auto & OriginalComponent:ActorEntry.ComponentEntry.OriginalComponents) { auto Actor = Cast <AActor>(OriginalComponent->GetOuter ()); Position += Actor->GetActorLocation (); } Position = Position / ActorEntry.ComponentEntry.OriginalComponents.Num (); Position = FVector (static_cast <int >(Position.X), static_cast <int >(Position.Y), static_cast <int >(Position.Z)); Position.Z = static_cast <int >(Cast <AActor>(ActorEntry.ComponentEntry.OriginalComponents[0 ]->GetOuter ())->GetActorLocation ().Z); }else { for (auto & Actor: ActorsToCleanUp) { Position += Actor->GetActorLocation (); } Position = Position / ActorsToCleanUp.Num (); Position = FVector (static_cast <int >(Position.X), static_cast <int >(Position.Y), static_cast <int >(Position.Z)); Position.Z = static_cast <int >(ActorsToCleanUp[0 ]->GetActorLocation ().Z); }
之后在MergedActor确认存在RootComponent后就可以设置位置了
1 ActorEntry.MergedActor->GetRootComponent ()->SetWorldLocation (Position);
设置Actor位置后,原本按照KeepRelativeTransform方式添加的其他Component就会偏离原本在世界中的位置,需要处理一下
1 2 3 4 5 6 7 for (UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents) { FTransform OriginalComponentTransform = OriginalComponent->GetComponentTransform (); FVector OriginalComponentLocation = OriginalComponentTransform.GetLocation () - Position; OriginalComponentTransform.SetLocation (OriginalComponentLocation); NewComponent->AddInstance (OriginalComponentTransform); }
这样就可以了
返回值问题
这个不用多说,简单
总结
原本引擎提供的MergeActor并没有办法通过蓝图调用到函数,也没有返回值,在需要批量化处理场景的时候会不太方便,但是其中具体的实现相对简单,稍微修改就可以满足需求
源码位置:MeshMergeUtilities.cpp