Back to Blog

Unreal Engine Mobile Optimization: Getting MetaHumans and PCG to Run at 60 FPS

Published on June 19, 2026
Unreal Engine Mobile Optimization: Getting MetaHumans and PCG to Run at 60 FPS

In a nutshell

Master unreal engine mobile optimization metahuman pcg workflows to hit 60 FPS using our detailed step-by-step guide and production C++ code.

The Mobile Barrier: Bringing Next-Gen Visuals to Mobile

Your beautiful PC project runs at a flawless 120 FPS on your developer workstation, but the moment you test the mobile package, the frame rate plummets to single digits, the target phone runs hot, and the GPU crashes due to a skinning buffer overflow. High-end features like MetaHumans, Procedural Content Generation (PCG), and Substrate materials look stunning on PC and consoles, but they are notorious for bringing mobile devices to their knees. Mobile GPUs and CPUs operate in highly constrained thermal and power envelopes, where memory bandwidth is a premium resource. Adapting these next-generation features for mobile deployment is not just a matter of checking a configuration box; it requires a deep, systematic understanding of bone skinning limits, groom structures, procedural baking workflows, and material shading complexities.

The GPU Skinning and Bone Limit Challenge

The GPU Skinning Bottleneck on Mobile

Skinned skeletal meshes are split into chunks of vertices and bones before being sent to the GPU. Each chunk is processed in a single draw call, and mobile GPUs have a strict hardware limit on the number of bone matrices they can skin simultaneously. This limitation is defined by the number of uniform vectors available to the vertex shader. A default MetaHuman character skeleton contains over 600 bones, which easily exceeds mobile limits and leads to rendering errors, vertex tearing, or complete GPU hangs.

To bypass this hardware constraint, you must force the engine to partition the skeletal mesh chunks such that no single draw call references more than a specific number of bones. This is achieved by adjusting the skinning compatibility settings. If you do not apply this configuration, your character models will fail to skin correctly on Android and iOS devices, showing static or heavily distorted meshes.

Configuring the DefaultEngine.ini

To resolve the bone skinning limits, you must modify your project's DefaultEngine.ini configuration file. Locate this file in the Config folder of your project root. Under the [ConsoleVariables] section, add the following line:

[ConsoleVariables]
Compat.MAX_GPUSKIN_BONES=75

This console variable instructs the shader compiler to clamp the maximum number of bones per skinning chunk to 75. It is a strict hardware limit for older or mid-range mobile GPUs. Keep in mind that setting this value lower forces the skeletal mesh compiler to split the mesh into a larger number of chunks. While this resolves rendering compatibility, more chunks mean more draw calls, which can shift the performance bottleneck to your CPU render thread.

Reducing Bone Counts and Component Stripping

For background characters or non-player characters (NPCs) that do not require full facial articulation, you should strip the skeleton down. For example, removing the fingers, toes, and facial expression bones can reduce the total bone count of a character from 600+ down to under 75. This allows the character mesh to be rendered as a single chunk without any draw call inflation.

If you are also deploying dedicated servers for your multiplayer game, client-side optimizations are only half the story. You will also need to optimize server-side performance by stripping rendering assets entirely. Check out our step-by-step guide on how to master Unreal Engine dedicated server asset stripping to reduce server memory overhead and optimize CPU performance.

Optimizing MetaHuman Hair: Groom Strands vs. Hair Cards

The Strand Rendering Cost

Epic's groom strand rendering technology draws individual hair curves dynamically. While this produces highly detailed hair on high-end desktop GPUs, it is incredibly expensive. Strand rendering relies on compute shader passes for depth sorting and shadow map generation, which consumes substantial pixel fill rate and memory bandwidth.

On mobile devices, strand rendering is either completely unsupported or runs at an unacceptable cost—often consuming over 15ms of GPU time just for a single character's hair. Mobile GPUs lack the raw memory bandwidth required to sort and shade hundreds of thousands of individual hair curves per frame.

Implementing Hair Cards

The solution is to swap strand-based grooms for hair cards. Hair cards represent hair using flat, simplified polygon meshes mapped with pre-rendered hair textures. This approach is highly compatible with the mobile forward render path.

To implement hair cards, open the MetaHuman Creator and ensure that you generate the card-based groom LODs for your character. Replacing strand-based hair with card-based grooms reduces the rendering time for a single character's hair from roughly 18.2ms to 0.9ms on a modern mobile chip like the Apple A15 or Snapdragon 8 Gen 1. This massive savings allows you to allocate rendering budgets to gameplay elements or environment details.

Disabling Post-Process Anim Blueprints

MetaHumans use post-process animation blueprints to drive secondary bone movement, corrective muscle shapes, and joint dynamics. While this adds realistic skin movement on PC, it runs complex skeletal calculations on the CPU every frame. On mobile, this CPU overhead can easily bottleneck the game thread.

You can disable the post-process animation blueprint on mobile devices to recover CPU cycles. This is done by setting bDisablePostProcessAnims = true on the skeletal mesh components. Disabling these post-processes saves up to 4.5ms of CPU frame time on standard mobile hardware.

Optimizing PCG for Mobile Environments

The Overhead of Runtime PCG Execution

The Procedural Content Generation (PCG) framework allows you to populate your environments dynamically by scattering static meshes, foliage, and actors based on rules and volumes. However, executing PCG graphs at runtime on mobile CPUs causes severe performance hitches. A typical runtime graph generation can freeze the game thread for 1.5 to 3 seconds during level loading or player spawning.

In a multiplayer game, such a hitch is dangerous; it can lead to packet loss and trigger client-server state desynchronization. To maintain high performance, you must pre-bake your PCG graphs in the editor. This converts the procedural instances into static geometry before packaging your game.

Step-by-Step PCG Baking Workflow

  1. Select the PCG Volume: In the Unreal Editor viewport, select the PCG volume that contains your environment elements.
  2. Generate and Inspect: In the details panel of the volume, click Generate to preview the procedural placement of your assets.
  3. Export to Actor: Locate the Export to Actor option within the PCG utilities menu.
  4. Choose Instanced Meshes: Select Hierarchical Instanced Static Mesh (HISM) as the target format. This group of instances is highly optimized for mobile GPUs, as it draws all identical meshes in a single GPU draw call.
  5. Clear the Graph: After exporting, set the PCG volume's generation trigger to Editor Only or clear the volume. This prevents the runtime engine from attempting to recreate the graph.

Dynamic HISM Culling and Streaming

Once you have baked your PCG instances into HISMs, you must configure their culling distances. HISMs support per-instance culling, which means instances beyond a certain distance from the camera will not be drawn. Set the Start Cull Distance and End Cull Distance in the details panel of your HISM components. For mobile, a cull distance of 5000 to 8000 units is recommended to keep the total visible polygon count within the mobile GPU budget.

Production C++ Optimization Script

To automate these optimizations at runtime, you can write a custom helper class. The following C++ code demonstrates how to check the target platform, programmatically force low LODs, disable post-process animation blueprints, and force groom components to use card-based rendering when spawning a MetaHuman on a mobile device. You can implement this in a class such as UMetaHumanMobileOptimizer:

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Components/SkeletalMeshComponent.h"
#include "GroomComponent.h"
#include "MetaHumanMobileOptimizer.generated.h"

UCLASS()
class MYPROJECT_API UMetaHumanMobileOptimizer : public UBlueprintFunctionLibrary

{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, Category = "Optimization")
    static void OptimizeMetaHumanForMobile(AActor* MetaHumanActor)
    {
        if (!MetaHumanActor)
        {
            return;
        }

        // Apply optimizations exclusively on Android and iOS platforms
        #if PLATFORM_ANDROID || PLATFORM_IOS
        TArray<USkeletalMeshComponent*> SkeletalComponents;
        MetaHumanActor->GetComponents<USkeletalMeshComponent>(SkeletalComponents);

        for (USkeletalMeshComponent* MeshComp : SkeletalComponents)
        { 
            if (MeshComp)
            {
                // Force a low LOD (LOD 3 or 4) to bypass dense meshes
                MeshComp->SetMinLOD(3);
                MeshComp->SetForcedLOD(3);

                // Disable expensive post-process animation blueprints
                MeshComp->bDisablePostProcessAnims = true;

                // Adjust animation tick rate to only calculate when visible
                MeshComp->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;

                // Strip physics asset to avoid CPU collision overhead on cosmetic joints
                MeshComp->SetPhysicsAsset(nullptr);
            }
        }

        TArray<UGroomComponent*> GroomComponents;
        MetaHumanActor->GetComponents<UGroomComponent>(GroomComponents);

        for (UGroomComponent* GroomComp : GroomComponents)
        {
            if (GroomComp)
            {
                // Force the groom to use card rendering instead of strands
                GroomComp->SetUseCards(true);
            }
        }
        #endif
    }
};

This helper function can be called from your character's BeginPlay event or immediately after spawning a MetaHuman actor. By using conditional compilation macros (PLATFORM_ANDROID || PLATFORM_IOS), the compiler strips these overrides from PC and console builds, allowing you to maintain cross-platform visual fidelity automatically.

Adapting Substrate Materials for Mobile GPUs

What is Substrate?

Substrate replaces the traditional Unreal Engine shading model with a modular, multi-layered material framework. Substrate allows developers to stack multiple shading slabs (e.g., placing a glossy clear coat layer directly over a rough metal layer). While Substrate is excellent for high-end cinematic assets, it presents a serious performance hurdle for mobile renderers.

Mobile GPUs rely heavily on tiled rendering architectures where the memory bus between the GPU core and the on-chip frame buffer is a major bottleneck. Complex Substrate materials increase the number of bytes per pixel (BPP) written to the frame buffer, causing thermal throttling and frame rate drops.

Managing Material Complexity via Quality Level Switches

To keep Substrate materials performant on mobile, you should use the Material Quality Level Switch and Feature Level Switch nodes inside the Material Editor. These nodes allow you to simplify the material graph based on the target platform.

For mobile platforms, simplify the graph by bypassing multi-layered Substrate blends. Instead, fall back to a single slab that approximates the visual style of your asset. By routing your material nodes through a quality switch, you can reduce the write bandwidth from 32 bytes per pixel to a standard 8 bytes per pixel, resulting in a cooler-running device and stable frame rates.

5 Actionable Best Practices for Mobile Optimization

  1. Pre-bake all PCG graphs into HISMs: Do not execute PCG graphs on the client at runtime. Pre-bake graphs into Hierarchical Instanced Static Meshes (HISMs) during editor development and configure appropriate start/end cull distances.
  2. Restrict bone counts globally: Add Compat.MAX_GPUSKIN_BONES=75 to your project's DefaultEngine.ini file to ensure skeletal meshes render correctly on mobile GPUs without triggering buffer overflows.
  3. Use card-based grooms exclusively: Disable strand-based grooms for mobile profiles. Card-based hair rendering reduces GPU frame times from 18ms down to less than 1ms per character.
  4. Leverage Material Quality Switches: Implement the Material Quality Level Switch node in your Substrate materials to simplify complex shading layers into a single slab on mobile platforms, reducing GPU bandwidth.
  5. Disable post-process animation blueprints: Set bDisablePostProcessAnims = true on your character skeletal components on mobile to reclaim valuable CPU cycles on the game thread.

The Multiplayer and Backend Equation

Beyond Rendering: Mobile Network Optimization

When developing cross-platform multiplayer games, client-side optimizations are only one part of the equation. Mobile devices frequently experience network fluctuations, switching between cellular data (5G/4G) and Wi-Fi. These fluctuations introduce packet loss, jitter, and high latency spikes.

If your network synchronization code is not robust, these latency spikes can trigger the Unreal Engine multiplayer sync bug, where actor replication goes out of sync and breaks the world state. Managing multiplayer states under mobile network constraints is a complex problem that requires resilient network drivers, delta compression, and server-authoritative reconciliation.

Offloading Infrastructure with horizOn

Building and maintaining a resilient multiplayer backend yourself is a massive engineering effort. You would need to provision load balancers, manage global database replication, implement matchmaking logic, and handle mobile billing. This infrastructure work can take months of development time and require ongoing maintenance.

With horizOn, these backend services come pre-configured. horizOn provides game developers with session matchmaking, low-latency state synchronization, database persistence, and cross-platform authentication out of the box. This allows you to focus on optimizing your clients and polishing your game loop, rather than managing server clusters and database scalability.

Conclusion and Next Steps

Optimizing next-generation features like MetaHumans and PCG for mobile requires strict control over rendering budgets, skeletal mesh compilation, and material shading complexity. By pre-baking your procedural assets, restricting bone count limits, and using card-based hair rendering, you can deliver high-fidelity cross-platform experiences on handheld devices.

Ready to scale your multiplayer backend? Try horizOn for free or check out the API docs to see how you can simplify your infrastructure and keep your players synchronized across PC, console, and mobile.


Source: Tutorial: Optimizing Next Gen Features for Mobile Game Development