title: UE4:MergeActors修改 date: 2022-07-05 19:36:39 tags: - UE4 - Tools
媒介文章若是有问题不会在知乎更新(太费事了),能够存眷那个小站:
UE4:MergeActors修改muchenhen.com/posts/7750/此次是给TA大佬打打杂~
针对利用需要修改了一下UE的MergeActors办法,便利美术利用,进步美术造做效率。
次要问题有:
1. 东西Merge之后的Actor的中心轴点是(0,0,0),场景平分批合并后所有的Actor的中心轴点全都在中间,有的间隔会十分远,不便利利用
2. Merge后的Actor的名字是类类型+数字,和本来的欠好对上,不曲不雅
3. 没有返回值,希望批量化处置的时候能拿到Merge的Actors停止其他的处置
4. 根据我们的设置,只会Merge出一个Actor,但是因为StaticMeshComponents的不不异,每个合并出的Actor上会有差别的UInstancedStaticMeshComponent实例,不如间接合并成不不异的Actors,更便利一点版本 4.27
相关构造体 FMeshInstancingSettings一个参数构造体,用来便利传递参数,详细参数意义能够看下面的源码部门的正文
/** Mesh instance-replacement settings */ USTRUCT(Blueprintable) struct FMeshInstancingSettings { GENERATED_BODY() FMeshInstancingSettings() : ActorClassToUse(AActor::StaticClass()) , InstanceReplacementThreshold(2) , MeshReplacementMethod(EMeshInstancingReplacementMethod::KeepOriginalActorsAsEditorOnly) , bSkipMeshesWithVertexColors(true) , bUseHLODVolumes(true) , ISMComponentToUse(UInstancedStaticMeshComponent::StaticClass()) {} // 要将合并后的instance static mesh components附加到的actor的类 UPROPERTY(BlueprintReadWrite, EditAnywhere, NoClear, Category="Instancing") TSubclassOf<AActor> ActorClassToUse; // 若是static mesh数量小于那个值不会施行合并 UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Instancing", meta=(ClampMin=1)) int32 InstanceReplacementThreshold; // 是删掉本来的static mesh actor仍是留在原地 那个列举只要两个值 UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Instancing") EMeshInstancingReplacementMethod MeshReplacementMethod; // 能否跳过有定点颜色的static mesh,实例静态网格不撑持每个实例的顶点颜色,合并的话将丧失此数据 UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Instancing") bool bSkipMeshesWithVertexColors; // 能否按照实instanced static mesh components与HLOD体积的交点拆分instanced static mesh components UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Instancing", meta=(DisplayName="Use HLOD Volumes")) bool bUseHLODVolumes; // 指定一下要用的InstancedStaticMeshComponent的类 UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Instancing", meta = (DisplayName = "Select the type of Instanced Component", DisallowedClasses = "FoliageInstancedStaticMeshComponent")) TSubclassOf<UInstancedStaticMeshComponent> ISMComponentToUse; }; FComponentEntry一个部分定义的构造体,次要目标是存储UStaticMeshComponent的一些数据,便利合并出的新的Component利用
没什么好说的,变量名都很曲不雅 /** Helper struct representing a spawned ISMC */ 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成员
/** Helper struct representing a spawned ISMC-containing actor */ struct FActorEntry { FActorEntry(UStaticMeshComponent* InComponent, ULevel* InLevel) : MergedActor(nullptr) { // intersect with HLOD volumes if we have a level 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中// 搜集有效的Components // 确认能否有顶点颜色 auto HasInstanceVertexColors = [](UStaticMeshComponent* StaticMeshComponent) { for (const FStaticMeshComponentLODInfo& CurrentLODInfo : StaticMeshComponent->LODData) { if(CurrentLODInfo.OverrideVertexColors != nullptr || CurrentLODInfo.PaintedVertices.Num() > 0) { return true; } } return false; }; // 存放Components TArray<UStaticMeshComponent*> ValidComponents; // for(UPrimitiveComponent* ComponentToMerge : ComponentsToMerge) { if(UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(ComponentToMerge)) { // 若是Components的父类类型和我们要用来挂InstanceMesh的类类型一样的话就不合并 // 不太大白为什么要有那一步处置,但是制止选了一堆StaticMeshActors然后要合并成一个StaticMeshActor的那种操做= = // 究竟结果,理论上来讲 // 如果用MergeActors那个功用就是为了把static mesh actors都合并成instances if(StaticMeshComponent->GetOwner()->GetClass() != MeshInstancingSettings.ActorClassToUse.Get()) { // 那个查抄是不是要跳过有顶点颜色的Actor if( !MeshInstancingSettings.bSkipMeshesWithVertexColors || !HasInstanceVertexColors(StaticMeshComponent)) { ValidComponents.Add(StaticMeshComponent); } } } }生成ActorEntries,那里若是bUseHLODVolumes是false的情况,那就只会有一个ActorEntry
// FActorEntry的容器 TArray<FActorEntry> ActorEntries; for(UStaticMeshComponent* StaticMeshComponent : ValidComponents) { int32 ActorEntryIndex = ActorEntries.AddUnique(FActorEntry(StaticMeshComponent, InSettings.bUseHLODVolumes ? Level : nullptr)); FActorEntry& ActorEntry = ActorEntries[ActorEntryIndex]; // FComponentEntry构造的时候StaticMeshComponent就会add到OriginalComponents中 FComponentEntry ComponentEntry(StaticMeshComponent); // 若是ActorEntry中已经有了一样的FComponentEntry,就间接添加对应的FComponentEntry中就能够 // 否则的话就是一个新的FComponentEntry if(FComponentEntry* ExistingComponentEntry = ActorEntry.ComponentEntries.FindByKey(ComponentEntry)) { ExistingComponentEntry->OriginalComponents.Add(StaticMeshComponent); } else { ActorEntry.ComponentEntries.Add(ComponentEntry); } } 挑选ActorEntries // 返回OriginalComponents数量大于设置中要求的阈值的ActorEntry的ComponentEntry for(FActorEntry& ActorEntry : ActorEntries) { ActorEntry.ComponentEntries = ActorEntry.ComponentEntries.FilterByPredicate([&InSettings](const FComponentEntry& InEntry) { return InEntry.OriginalComponents.Num() >= InSettings.InstanceReplacementThreshold; }); } // 若是ActorEntry的ComponentEntries是空的就去掉 ActorEntries.RemoveAll([](const FActorEntry& ActorEntry){ return ActorEntry.ComponentEntries.Num() == 0; }); 保留原始StaticMeshActors // 通过OriginalComponents获取到本来的Static Mesh Actor // 后面会按照Setting的参数决定那些能否要删除掉 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 Level->Modify(); FActorSpawnParameters Params; Params.OverrideLevel = Level; // We now have the set of component data we want to apply for(FActorEntry& ActorEntry : ActorEntries) { // 创建新的Actor,那各Actor类类型默认是AActor ActorEntry.MergedActor = World->SpawnActor<AActor>(InSettings.ActorClassToUse.Get(), Params); // 遍历ComponentEntries // 若果合并的Actors不完全不异,那么ComponentEntries就不会只要一个 // 最初的成果是一个Actor上会有多个UInstancedStaticMeshComponent for(const FComponentEntry& ComponentEntry : ActorEntry.ComponentEntries) { UInstancedStaticMeshComponent* NewComponent = nullptr; // ISMComponentToUse的默认值是UInstancedStaticMeshComponent // 那里先从Actor身上试图获取一个UInstancedStaticMeshComponent NewComponent = (UInstancedStaticMeshComponent*)ActorEntry.MergedActor->FindComponentByClass(InSettings.ISMComponentToUse.Get()); // 若是发现确实存在UInstancedStaticMeshComponent但是数据不是空的,仍是回选择去新建一个 if (NewComponent && NewComponent->PerInstanceSMData.Num() > 0) { NewComponent = nullptr; } if (NewComponent == nullptr) { // 创建一个新的Component NewComponent = NewObject<UInstancedStaticMeshComponent>(ActorEntry.MergedActor, InSettings.ISMComponentToUse.Get()); if (ActorEntry.MergedActor->GetRootComponent()) { // 有root的话能够间接Attach NewComponent->AttachToComponent(ActorEntry.MergedActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); } else { // 没有root那么你就是root! ActorEntry.MergedActor->SetRootComponent(NewComponent); } // Take instanced ownership so it persists with this actor ActorEntry.MergedActor->RemoveOwnedComponent(NewComponent); NewComponent->CreationMethod = EComponentCreationMethod::Instance; ActorEntry.MergedActor->AddOwnedComponent(NewComponent); } // 把原先的属性赋值给新的Component 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); // 那里,就是把多个不异的本来的StaticMeshComponents合并成了一个UInstancedStaticMeshComponent,AddInstance for(UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents) { NewComponent->AddInstance(OriginalComponent->GetComponentTransform()); } NewComponent->RegisterComponent(); } World->UpdateCullDistanceVolumes(ActorEntry.MergedActor); } 清理原始Actors就是很简单的按照设置看看需不需要把被合并的那些Actors删掉
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不是显示出来的那个名字
那里间接取会被合并的一堆里面的第一个名字间接加后缀 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改为了一个对象,因为只需要一个 // 省略部门源码 struct FComponentEntry { AActor* MergedActor; AHierarchicalLODVolume* HLODVolume; - TArray<FComponentEntry> ComponentEntries; + FComponentEntry ComponentEntry; };然后仍是生成ActorEntries
只要当Component生成的ComponentEntry完全不异的时候才会合并到一个Actor上
不然就会创建一个新的Actor // Gather a list of components to merge 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的位置去一个均匀值,然后取整便利后续的计算利用
C++ 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后就能够设置位置了
ActorEntry.MergedActor->GetRootComponent()->SetWorldLocation(Position);设置Actor位置后,本来根据KeepRelativeTransform体例添加的其他Component就会偏离本来在世界中的位置,需要处置一下
for(UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents) { FTransform OriginalComponentTransform = OriginalComponent->GetComponentTransform(); FVector OriginalComponentLocation = OriginalComponentTransform.GetLocation() - Position; OriginalComponentTransform.SetLocation(OriginalComponentLocation); NewComponent->AddInstance(OriginalComponentTransform); }如许就能够了
返回值问题那个不消多说,简单
总结本来引擎供给的MergeActor并没有法子通过蓝图挪用到函数,也没有返回值,在需要批量化处置场景的时候会不太便利,但是此中详细的实现相对简单,略微修改就能够满足需求
源码位置:MeshMergeUtilities.cpp