[UE4]Remove and Add Instanced Foliage Mesh at Run-time
Keywords:InstancedFoliageActor, AInstancedFoliageActor, UInstancedStaticMeshComponent, InstancedStaticMeshComponent, Instanced Foliage, Instanced Static Mesh, Remove and Add at Run-time
Add and Remove Instance
1, Use Foliage Painter to create Instanced Foliage Mesh
2, Test code
GameMode:
UCLASS(minimalapi)
class ATestProjGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
ATestProjGameMode();
protected:
void BeginPlay() override;
UFUNCTION(BlueprintCallable)
void RemoveAllFoliages();
UFUNCTION(BlueprintCallable)
void AddAllFoliages();
private:
//Instanced Static Mesh Components Array in current Level.
TArray<UActorComponent*> InstancedStaticMeshCompArray;
//Each Instanced Static Mesh's Transform. Key: Instanced Static Mesh Component Name, Value:All Transforms in current Instanced Static Mesh Component
TMap<FString, TArray<FTransform>> TransformMap;
};
Begin Play
void ATestProjGameMode::BeginPlay()
{
Super::BeginPlay();
for (TActorIterator<AInstancedFoliageActor> Iter(GetWorld()); Iter; ++Iter)
{
if (*Iter)
{
//Get the all InstancedStaticMeshComponent in current Level.
InstancedStaticMeshCompArray = Iter->GetComponentsByClass(UInstancedStaticMeshComponent::StaticClass());
//Print the name of InstancedStaticMeshComponent
for (UActorComponent* ActorComp : InstancedStaticMeshCompArray)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, ActorComp->GetName());
}
break;
}
}
}
Remove all Foliage
void ATestProjGameMode::RemoveAllFoliage()
{
TransformMap.Empty();
for (UActorComponent* Comp : InstancedStaticMeshCompArray)
{
if (UInstancedStaticMeshComponent* InstancedComp = Cast<UInstancedStaticMeshComponent>(Comp))
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, FString::Printf(TEXT("%d"), InstancedComp->GetInstanceCount()));
TArray<FTransform> TransformArray;
for (int i = InstancedComp->GetInstanceCount() - 1; i >= 0; i--)
{
//Save the transform of current Instance.
FTransform Out;
InstancedComp->GetInstanceTransform(i, Out);
TransformArray.Add(Out);
//Remove Instance at Run-time.
InstancedComp->RemoveInstance(i);
}
TransformMap.Add(InstancedComp->GetName(), TransformArray);
}
}
}
Another way to hide Instance: set Scale of Instance to Zero, using UInstancedStaticMeshComponent::UpdateInstanceTransform(int32 InstanceIndex, const FTransform& NewInstanceTransform)
Add all Foliage
void ATestProjGameMode::AddAllFoliage()
{
for (UActorComponent* Comp : InstancedStaticMeshCompArray)
{
if (UInstancedStaticMeshComponent* InstancedComp = Cast<UInstancedStaticMeshComponent>(Comp))
{
if (TArray<FTransform>* TransformArray = TransformMap.Find(InstancedComp->GetName()))
{
for (FTransform& Trans : *TransformArray)
{
//Add Instance at Run-time, use the previous Transform.
InstancedComp->AddInstance(Trans);
}
}
}
}
}
These code are applicable to not only Foliage Painter, but also ProceduralFoliageVolume.
How to create InstancedStaticMeshComponent at run-time
const TArray<UMaterialInterface*> Materials = ...;
const TArray<FMatrix> Instances = ...;
const int32 InstanceNum = ...;
for (int32 Index = 0; i < InstanceNum; i++)
{
UInstancedStaticMeshComponent* Component = NewObject<UInstancedStaticMeshComponent>(GetTransientPackage(), *FString::Printf(TEXT("InstancedMesh_%d"), Index), RF_Transient);
Component->SetRelativeLocation(FVector(0.f, 0.f, 0.f));
Component->SetRelativeRotation(FRotator(0.f, 0.f, 0.f));
Component->SetRelativeScale3D(FVector(1.f, 1.f, 1.f));
Component->SetMobility(EComponentMobility::Type::Static);
Component->SetStaticMesh(Mesh);
for (int i = 0; i < Materials.Num(); i++)
{
Component->SetMaterial(i, Materials[i]);
}
for (const FMatrix& Matrix : Instances)
{
FInstancedStaticMeshInstanceData InstancedStaticMeshInstanceData(Matrix);
Component->PerInstanceSMData.Add(InstancedStaticMeshInstanceData);
}
}
How to get StaticMesh and Materials of Instaced Mesh
e.g. code:
void ATestProjGameMode::BeginPlay()
{
Super::BeginPlay();
for (TActorIterator<AInstancedFoliageActor> Iter(GetWorld()); Iter; ++Iter)
{
if (*Iter)
{
//Get the all InstancedStaticMeshComponent in current Level.
TArray<UActorComponent*> InstancedStaticMeshCompArray = Iter->GetComponentsByClass(UInstancedStaticMeshComponent::StaticClass());
for (UActorComponent* ActorComp : InstancedStaticMeshCompArray)
{
if (UInstancedStaticMeshComponent* InstancedComp = Cast<UInstancedStaticMeshComponent>(ActorComp))
{
UStaticMesh* Mesh = InstancedComp->GetStaticMesh();
TArray<UMaterialInterface*> Materials = InstancedComp->GetMaterials();
}
}
break;
}
}
}
How to set Transform of Instaced Mesh
void ATestProjGameMode::BeginPlay()
{
Super::BeginPlay();
for (TActorIterator<AInstancedFoliageActor> Iter(GetWorld()); Iter; ++Iter)
{
if (*Iter)
{
//Get the all InstancedStaticMeshComponent in current Level.
TArray<UActorComponent*> InstancedStaticMeshCompArray = Iter->GetComponentsByClass(UInstancedStaticMeshComponent::StaticClass());
for (UActorComponent* ActorComp : InstancedStaticMeshCompArray)
{
if (UInstancedStaticMeshComponent* InstancedComp = Cast<UInstancedStaticMeshComponent>(ActorComp))
{
for (int i = InstancedComp->GetInstanceCount() - 1; i >= 0; i--)
{
//Set Transform of Instance
FTransform NewTransform;
InstancedComp->UpdateInstanceTransform(i, NewTransform);
}
}
}
break;
}
}
}
Optimization
1, Issue:
If Instanced Mesh count is too large, e.g. one million, execute RemoveInstance
or UpdateInstanceTransform
multiple times at one frame, frame rate would drop violently.
Solution:
Set bAutoRebuildTreeOnInstanceChanges
to false before manipulate Instanced Mesh, then execute all the operation of Instanced Mesh you want, then set bAutoRebuildTreeOnInstanceChanges
to true, then execute BuildTreeIfOutdated(true, false);
, thus frame rate dropping isn’t as violent as before.
Reference
Get locations of foliage instances
for (TActorIterator<AInstancedFoliageActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
AInstancedFoliageActor* FoliageMesh = *ActorItr;
for (auto& MeshPair : FoliageMesh->FoliageMeshes)
{
const FFoliageMeshInfo& MeshInfo = *MeshPair.Value;
UHierarchicalInstancedStaticMeshComponent* MeshComponent = MeshInfo.Component;
TArray<FInstancedStaticMeshInstanceData> MeshDataArray = MeshComponent->PerInstanceSMData;
FString MeshName = MeshComponent->GetStaticMesh()->GetName();
for (auto& MeshMatrix : MeshDataArray)
{
FTransform MeshTransform = FTransform(MeshMatrix.Transform);
UE_LOG(LogTemp, Warning, TEXT("%s, %f, %f, %f, %f, %f, %f, %f, %f, %f,"),
*MeshName,
MeshTransform.GetLocation().X,
MeshTransform.GetLocation().Y,
MeshTransform.GetLocation().Z,
MeshTransform.GetRotation().X,
MeshTransform.GetRotation().Y,
MeshTransform.GetRotation().Z,
MeshTransform.GetScale3D().X,
MeshTransform.GetScale3D().Y,
MeshTransform.GetScale3D().Z);
}
}
}
How to get locations of foliage instances?
https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1474454-how-to-get-locations-of-foliage-instances
[UE4] Take advantage of Auto Instancing with Custom Primitive Data
https://qiita.com/EGJ-Nori_Shinoyama/items/8d0cde73654386dc00cf
真正的强者,不是没有眼泪的人,而是含着眼泪奔跑的人。