Skip to main content

Metasite Creation Guidelines: Advanced Unreal Tips

Introduction: Advanced Techniques and Optimization for Unreal Engine Metasites

Welcome to the advanced guidelines for mastering Unreal Engine in the development of Metasites. This document is tailored for seasoned developers and creators aiming to push the boundaries of performance, realism, and interactivity. Here, we delve into high-level techniques, cutting-edge workflows, and optimization strategies that can elevate your Metasite projects to the next level.

We’ll explore topics such as advanced material systems, procedural generation, performance profiling, and efficient use of Unreal Engine’s powerful rendering tools. These guidelines focus on achieving maximum visual fidelity while optimizing for diverse platforms and hardware configurations. By integrating these strategies into your development pipeline, you can create seamless, high-performance Metasites that captivate and engage users.

Let’s unlock the full potential of Unreal Engine together.


Overall tips

Must have

Fine-tuning roughness and normal maps of primary covering materials:
For tiles, plastic, and laminate, make sure to adjust the roughness and normal maps. If they are weakly detailed (with almost the same value across the entire texture), it is the covering textures that will provide the desired gloss to the image. Chairs and other props are less of a concern here.

Creating colliders for objects, walls, floors, etc.


When dealing with complex objects, it is often necessary to split them into separate meshes. For example, a massive deck mesh where the floor, stairs, and other elements are combined into one gigantic model. This significantly complicates the process of creating colliders and adjusting lighting. Distance field (DF) generation does not work correctly with such meshes. To ensure proper functioning of Lumen with RenderInMainPass=Off, it is possible to place shadow-casting cubes.

Screenshots before and after splitting the deck into parts:

Before:
Before

After:
After


Convert the geometry to nanite

Nanites work wonders with such geometry, especially when it comes to a bunch of nearly identical chairs and tables. And if you create the content correctly (using one material so that they all fall into one nanite pass), the performance will be even better, and the visual quality will be more impressive (given the increased geometry detail).


Adjusting texture saturation

The textures are too saturated for proper light interaction. For example, the vivid blue tile is one of the main reasons for the shadow issues on the deck. It is necessary to reduce the saturation to below 55%.

Before fixing saturation:
Before Fixing Saturation

After:
After Fixing Saturation


Lowering roughness across the scene and aligning it for similar materials

This step is crucial as it will provide the correct balance of highlights and light to illuminate the shadow gaps. It is difficult to provide specific values, but lowering it should bring out highlights on materials like plastic, tile (!), and laminate. Additionally, there might be incorrect textures/values for similar materials.


Removing unnecessary materials

There are numerous unnecessary materials that are almost indistinguishable, leading to additional draw calls and decreased performance.


Setting up/creating custom depth fields for large decorative elements

For example, all chairs and tables suffer from insufficient shading due to this.


Character Structure

Hierarchy and Bounds

An efficient hierarchy for meshes is crucial when rendering characters. By making the body mesh the root that stores the Fixed Bound inside its mesh and all other meshes (such as the head, legs, and apparel) using Attach Parent Bound, we can drastically reduce the number of calculations needed for each mesh. This optimization reduces the workload for the CPU and GPU, which translates to improved performance.

Selective shadow casting and single bound holder hierarchy example:
Hierarchy Example


Shadows

  1. Shadow caster components – Disabling shadowing for small meshes and meshes that don't significantly affect the silhouette of a character is another optimization that can help improve performance. Examples of meshes that we can disable shadowing for include slim-fit clothing, beards, and hair strands. This optimization reduces the workload on the GPU and ensures that resources are utilized only where they matter.

  2. Shadow casting mesh sections – Similarly, disabling shadow casting on any mesh section that does not significantly impact the projected shadow can also help improve performance. We can disable shadow casting on small details such as eyelashes, lacrimal fluid, and eye AO meshes. This optimization reduces the workload for the GPU, which in turn improves overall performance.

Example of mesh section that can easily have its shadow disabled:
Mesh Section Shadow Disabled


Updates

  1. Tick – Since we are rendering a large number of skeletal mesh components and not (currently) using any streaming, it is essential to control how many of them are actually ticking. By disabling ticking for meshes that are not currently visible, we can significantly reduce the workload on the CPU, improving performance. This optimization ensures that resources are utilized only where they matter. Exact approach TBD @Roman Leshchenko @Andrey Kharchenko

Animation optimization

Finally, optimizing animations can greatly improve performance. Using Update Rate Optimization and Tick Pose Only When Rendered reduces the overhead for animation updates. By updating the animation state of the skeletal mesh only when it needs to be rendered, rather than continuously updating even when not visible, we can reduce the workload on the CPU, which translates to improved performance. Given the fact we are also pretty effective with Occlusion Culling (see Hierarchy and Bounds) - this should keep overhead of updating animation for a lot of characters at minimum.

An example of load created by characters' Tick updates:
Tick Load


Character Customization

Use of Material Instance Dynamic

Our game features modular characters that are currently made up of 5–10 separate meshes. Each mesh creates a Dynamic Material Instance to adjust customization parameters, which given the number of AI and user character, can create thousands of calls to FMaterialRenderProxy::EvaluateUniformExpressions, causing a significant performance hit on both the CPU and GPU. This function updates parameters inside MID despite the fact whether it was changed or not.

Profiler Example (material performance):
Profiler Example


Optimization Recommendation

To mitigate this issue we need to remove all and every Material Instance Dynamic on characters. Instead, we should use Custom Primitive Data to set customization parameters directly on the instance in the level. This will effectively remove the aforementioned function calls completely, which by itself is a great optimization. On top of that, we will gain some memory from removing MID instances, as well as decrease draw call resource binding and set parameters overhead.

The artistic strategy here is to leave the MID for cool environment surfaces and effects, that have to be precisely authored to allow for great level of interactivity and visual complexity.