I’m trying to display a labyrinth generated at runtime in C++ via InstancedStaticMesh on an Android mobile device with feature level ES2. After setting the material shading model of walls and floors to unlit to see anything at all (although it’s really ugly like that), I was very suprised that only 33 of 279 wall/floor instances are displayed in the “Android preview” mode of Unreal Engine 4.10.2 (Settings → Preview Rendering Level → Mobile / HTML5 → Android preview). Interestingly, when I eject and select the labyrinth actor, all instances are shown, but maybe it’s using a special mode for that…
I searched a bit through the source code of Unreal Engine and found out, that batches of (probably) 64 instances are used to render when no real instanced meshes are available for the platform. But outputting the number of used batches for the walls and floors show that the expected 2-3 batches for each is returned by FStaticMeshSceneProxy::GetNumMeshBatches(). So currently I don’t see what could be causing this.
As this works perfectly on Windows and InstancedStaticMesh for mobile is supposed to work since UE 4.4, I consider this as a bug.
Here is a collage of some screenshots from playing inside the editor annotated with the number of visible instances (yes, with unlit material it’s really ugly, I’m open for suggestions ^_^):
And here’s the code used to instantiate the map:
ALabyrinth::ALabyrinth(const FObjectInitializer &ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true; //false;
static ConstructorHelpers::FObjectFinder<UStaticMesh> labyMeshes[LWT_NumWallTypes] = {
ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Game/Labyrinth/Floor_StaticMesh.Floor_StaticMesh'")),
ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Game/Labyrinth/Wall_StaticMesh.Wall_StaticMesh'"))
};
RootComponent = ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("Scene"));
for (int i = 0; i < LWT_NumWallTypes; i++)
{
InstancedComponents[i] = ObjectInitializer.CreateDefaultSubobject<UInstancedStaticMeshComponent>(this, FName(*FString::Printf(TEXT("InstancedComponents_%d"), i)));
InstancedComponents[i]->AttachTo(RootComponent);
InstancedComponents[i]->SetStaticMesh(labyMeshes[i].Object);
}
}
// Called by ALabyrinth::BeginPlay()
void ALabyrinth::InstantiateLabyrinth()
{
FVector actorLoc = GetActorLocation();
ScaleFactor = 0.75;
for (int i = 0; i < LWT_NumWallTypes; i++)
{
InstancedComponents[i]->SetRelativeLocation(FVector(-(LabyWidth / 2.f) * 100 * ScaleFactor, -(LabyHeight / 2.f) * 100 * ScaleFactor, 0));
}
for (uint32 y = 0; y < LabyHeight; y++)
{
for (uint32 x = 0; x < LabyWidth; x++)
{
uint8 walltype = Labyrinth[y * LabyHeight + x];
FVector pos = actorLoc + FVector(x * 100 * ScaleFactor, y * 100 * ScaleFactor, 0);
InstancedComponents[walltype]->AddInstance(
FTransform(FQuat::Identity, pos, FVector(ScaleFactor)));
}
}
}
// Debug message to prove GetNumMeshBatches is correct.
void ALabyrinth::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (InstancedComponents[0]->SceneProxy == NULL)
{
GEngine->AddOnScreenDebugMessage(222, 5.f, FColor::Blue, TEXT("SceneProxy 0 is NULL!"));
}
else
{
int32 numbatches = static_cast<FStaticMeshSceneProxy *>(InstancedComponents[0]->SceneProxy)->GetNumMeshBatches();
GEngine->AddOnScreenDebugMessage(222, 5.f, FColor::Blue, FString::Printf(TEXT("NumBatches for InstancedComponents[0] = %d"), numbatches));
}
if (InstancedComponents[1]->SceneProxy == NULL)
{
GEngine->AddOnScreenDebugMessage(223, 5.f, FColor::Blue, TEXT("SceneProxy 1 is NULL!"));
}
else
{
int32 numbatches = static_cast<FStaticMeshSceneProxy *>(InstancedComponents[1]->SceneProxy)->GetNumMeshBatches();
GEngine->AddOnScreenDebugMessage(223, 5.f, FColor::Blue, FString::Printf(TEXT("NumBatches for InstancedComponents[1] = %d"), numbatches));
}
}