Sep 17, 2020.Knowledge
- Nightly we run a Rebuild HLOD automation script on all our sublevels, which makes sure that any changes that artists make are incorporated into HLODs. This script is included as of 4.20 at this link RebuildHLODCommand.Automation.cs. You must be logged in to Github with access to the Unreal Engine repository to see this file. It builds HLODs minimally and as we require that artists manually rebuild if they alter clusters, if no changes have been made to assets (e.g. textures, static meshes, etc.) then no work is done for a sublevel. A serial full rebuild can take many hours however!
- Next, nightly we run a custom ‘HLOD Streaming Data’ commandlet (derived from ResavePackages) on our persistent root map that runs over all the referenced sublevels’ ‘foundation’ actors and extracts references to the generated HLOD static meshes (in ALODActors). It then spawns custom proxy actors in the persistent map. The end result is that the persistent map contains all the HLOD proxy meshes and represents the whole world in low detail. Note that ‘foundation’ actors represent a hierarchy, i.e. sublevels can also contain foundations, so we need to recursively load all these sublevels to extract HLOD meshes.
- The ‘HLOD Streaming Data’ commandlet also updates a structure in the world settings of the persistent map that maps foundation actors (both in the persistent map and in sublevels) to TLazyObjectPtrs of our persistent proxy actors. We uniquely identify foundations by their actor name and their location.
- At runtime, when a root foundation actor becomes network relevant we have logic to determine whether we want to stream in the level (currently distance to its bounding box, which is recomputed in the ‘HLOD Streaming Data’ commandlet). We then issue the streaming request and when the level is streamed in we hide its corresponding HLOD actor in the persistent map. The HLOD mesh is still present in the newly-loaded sublevel, so normal HLOD transitions like dithering can then occur. We perform the reverse operation when the level is streamed out.
There are a number of gotchas around instancing sublevels that you may come across if you are wanting to use non-unique content for any of your sublevels. We found that we needed to generate specific unique names for each instance that were stable on client and server. In our case we took the sublevel’s foundation actor name and its integer-rounded location (all foundations are snapped to a grid in Fortnite) and created a hash of this value to append to the level’s path (transplanted to the /Temp/ mount point).
We would like to develop the system further and make it into a feature that all users of the engine can use to make large worlds. The system suits Fortnite’s world size well, but to make it generically applicable we would need a deeper hierarchy of HLOD proxies which would not all be visible in the persistent map, which at the moment is very ‘flat’. We would also like to consider a general system for transitioning LODs, such as the potential for something like cross-level LODParentPrimitives.