World Composition LOD system

I want to preface this entire article by stating that the information below has been gathered by experimenting with the system, and as such, is incomplete. I will need to dive into the C++ code to get a really solid idea of what is going on, but hopefully, what follows will be enough to help you along. Once I have dove into the code to see what, exactly, is happening when we press the generate button to make our levels-of-detail, I will post another article. I don’t know when that will be, so no promises.

Image 1: The level details dialog opened with sublevel E5-2 selected. We are currently viewing the basic settings for the sublevel’s first defined level-of-detail. There are a total of four levels-of-detail defined for each of this test map’s sublevels. That is the limit for the number of levels-of-detail that W.C. will allow.

To get started with setting up the levels-of-detail for the sublevels in World Composition (W.C. going forward), you’ll need to have at least one sublevel selected in the Levels tab and press the small level details button. I’ve circled this in red in Image 1. This will bring up the level details dialog allowing you to define all of the information that you want to use when generating your levels-of-detail. When you first open this dialog, you won’t have any levels-of-detail defined, and the only control that you will see under the “LODSettings” rollout is the one labeled “Num LOD”…not very descriptive, I know. This is the first step to defining your levels-of-detail and how they will be created.

By setting the value for “Num LOD” to 1, you will then be able to define a single LOD level for each sublevel in your W.C. map. Each of the other LOD levels will follow the exact same steps to create, but the values you enter will be different for each LOD level. If you want to have three levels-of-detail for your W.C. map, you would type 3 into the field for “Num LOD”. For LOD1, you will want to use the best quality settings that you feel you can get away with. Each game is different, and each one will have it’s own requirements for performance. Obviously, we don’t want a very noticeable “pop” when the player crosses the point where each LOD level transitions to the next. Which leads me to the first setting to pay attention to, which is the “Relative Distance” field.

Relative distance is the distance that this LOD level will use to transition to the next level-of-detail. This will be added to the base streaming distance. For example, the “Uncategorized” layer in W.C. has a default streaming distance of 50,000. Once the player is further away from that sublevel, it will be removed from the player’s viewport, even if they are looking directly at it. This is where our first level-of-detail, LOD1, would be streamed in to take the place of the actual sublevel. The setting for “Relative Distance” serves the same purpose as the default streaming distance; it is the point at which we want our LOD1 to be removed from the player’s viewport and the next level-of-detail to be streamed in. However, it is very important to note that the value for “Relative Distance” is cumulative. It is added to the values preceding it in the level-of-detail settings. An example is in order here to make this a bit more clear.

If you have not defined any other layers in W.C. and all of your sublevels are contained within “Uncategorized”, their default streaming distance is 50,000. In Image 1, you can see that the relative distance is set to 418,353. This may seem like a strange number to choose, but I arrived at this value after some mathematical calculations and quite a bit of experimentation. What you can’t see in the image above is that ALL of the LOD levels defined have the same relative distance value. When the player moves more than 50,000 units away from the sublevel, LOD1 will be streamed in and once that streaming is complete, the sublevel will be removed from view and LOD1 will be displayed in it’s place. Then, once the player has moved more than 468,353 units away from the sublevel’s location, the engine will start to stream in LOD2, and once LOD2 is fully loaded into memory, LOD1 will be removed and LOD2 will be displayed. This is the key part to remember: the relative distance value is added to the previous total. So, when the player has moved a total of 886706 units away from the sublevel’s location, Unreal will start to stream in the content for LOD3 and will remove LOD2 once that streaming process is complete. Why 886706? Because the engine is doing the following math: 50,000+418,353+418,353 to come up with the total distance the player needs to be for LOD2 to be too far away for the player to see, requiring LOD3 to be streamed in.

Most of the fields under the “Simplification Details” rollout are similar to the fields used when using the Actor Merging feature of the engine, so those won’t be covered here. There are a few key differences between the actor merging tool and the W.C. LOD generation tool.

One of the cool features of this system is that it will combine all of the static mesh objects within your sublevel in W.C. This is very similar to the Actor Merging feature, with a few differences. The first being that you do not get to chose what LOD level will be used when merging the static meshes contained in the sublevel. The system is choosing a single LOD level (which appears to be the lowest) from each static mesh asset and using that when combining them. We don’t have any control over this portion of the generation process. It is done behind-the-scenes, but we do get to specify a “Static Mesh Details Percentage”, which is the second departure from the Actor Merging feature. I am guessing a bit, but I believe that after the static meshes are merged, the result is then being reduced further in an attempt to reach the percentage we specify. So, if your merged static mesh is 15,000 triangles, and you enter a value of 68.5 for the percentage, the actual merged static mesh used in the W.C. LOD asset will be approximately 10,275. Considering the fact that the system is already (apparently) using the lowest LOD level from each static mesh, the resulting combined mesh is already pretty light-weight. Further reduction via this “Static Mesh Details Percentage” field would probably result in an unusable merged mesh if pushed too far.

The next field that we really need to pay attention to is the “Landscape Export LOD” field. At first glance, the “Static Mesh Details Percentage” field may appear to be an option to use instead of “Landscape Export LOD”, but in fact they are completely different. When W.C. generates the static mesh for the landscape actor (not the static meshes within the sublevel, but the actual landscape itself), it will use the LOD level specified in this field. This field defaults to LOD7 for the landscape actor, which will result in a static mesh with approximately 2,048 triangles. If you want/need higher resolution for the static mesh generated from the landscape actor for your sublevel, you will need to enter a different value for this field. In Image 1 you can see that I chose 3 as the LOD level to use from the landscape actor when generating the static mesh for LOD1 in W.C. This resulted in a static mesh that has most of the detail that is contained in the actual landscape actor, while being significantly less triangles. In Image 2, you can see a screen capture of the landscape with the actual landscape actors being displayed.

Image 2: Here we can see the landscape stretching off into the distance. This is showing the sublevels near the character with the actual landscape actors and assets. There is no levels-of-detail being displayed in our viewport. When looking at this image, pay close attention to the very dark mountains in the background.

In Image 3, we can see that the character has moved far enough away from one of the sublevels to cause it’s first LOD to be displayed. The static mesh that is being displayed instead of the landscape actor retains almost all of the detail of the actual landscape actor itself. The silhouette that can be seen against the sky matches very closely, and even if there is a little ‘pop’ when the LOD is swapped for the original, it shouldn’t be dramatic. Some experimentation will be needed to wring as much performance out of each sublevel’s LODs. I would advise finding a “Landscape Export LOD” value that works for your most distinctive landscape features, and stick with that value for all sublevels. If you have a sublevel that is relatively flat, and doesn’t have incredibly distinct skyline silhouettes, you might be tempted to set it’s “Landscape Export LOD” to a more aggressive value than the others around it. But remember that the edges of the surrounding proxy meshes have to match up, and using different “Landscape Export LOD” settings may result in gaps that can be seen by the player.

Image 3: One of the sublevels have been removed and it’s LOD1 asset has been displayed in it’s place. We can see that the generated mesh is fairly close to the landscape actor that it is derived from. However, the material created for this LOD is far from acceptable. This was due to the settings that I provided to the system. Garbage In, Garbage Out.

For the last two features that are unique to W.C.’s level-of-detail generation system, we will look at the “Bake Foliage to Landscape” and “Bake Grass to Landscape” options. These will render the foliage or grass assets to the 2D texture that will be used on the landscape proxy mesh. This is to give the player the impression that the foliage or grass is still on the landscape proxy mesh, even though the player is just seeing a 2D texture on the landscape proxy mesh. However, the system doesn’t appear to capture the color of tree leaves very well (or at all), which reveals the lack of trees very easily. The system does render the color of the tree trunk to the texture reasonably well. In Image 3, if there were trees on the slope of that mountain, it would be very obvious that the actual 3D assets were no longer there. I think there is a way around this (though, I haven’t tried it yet), and I will cover that briefly below. It may be that if you are using a stylized look, where your tree’s aren’t using masked materials for your leaves/branches, you could end up with a much better result. I am not sure how much better, though, because I have done very little testing using stylized assets.

One detail in Image 3 that would be hard to miss is the large difference between the LOD asset’s material and the material of the landscape actor that is adjacent to it. There is a hard-edge that would be nearly impossible to hide. This was due to the default settings that I chose when setting up the “Landscape Material Settings” for the generation of this LOD asset. I did not want to incur the cost of having separate textures for specular and roughness, so I used constant values instead. When the landscape proxy mesh is generated, the material assigned to it will use these constants for specular and roughness. Because I set the values so poorly, it resulted in a very reflective surface, which is why it appears the way that it does in Image 3. When I created these LOD assets for this open world map, I actually selected all of the sublevels and set their options, resulting in all LOD1 assets sharing these constant values in their generated materials. You can see this in Image 4. The sublevel adjacent to the first one we were observing has been removed and it’s LOD1 asset is being shown in it’s place.

Image 4: The character has moved far enough away to cause the adjacent sublevel to use it’s LOD1 asset as well. As you can see from the image, there is something very wrong with the texture for the tree in the middle of the village.

In Image 5, you can see the landscape proxy meshes for both sublevels with much better constant values for specular and roughness. While this isn’t a perfect match to the sublevel’s landscape material, it does provide a huge step in the right direction. This is why I highly advise you to do some testing on a single sublevel’s LOD settings and find material settings that work well with your map. Make sure to move your lighting around the same way that it might be moved in-game. This way, you will see if your lights are going to cause problems with the specular and roughness values if they are set too aggressively. Yet, if you don’t have any specularity and you set the roughness all the way to 1, you will lose any definition in your landscape proxy mesh. It will just look like a flat, 2D card that you have placed in the distance, with no highlighting of any of the landscape features. Once you have values that you are happy with, you can then use those values to generate all of the LOD assets for your sublevels in W.C.

Image 5: Both landscape proxy meshes materials have much better specular and roughness constant values. The ‘pop’ from the sublevel to the LOD asset is noticeable if you’re looking directly at it, but not extreme.

Like Image 3, Image 4 and Image 5 have their own detail that would be impossible to miss. The tree that I placed in the middle of the small test village has been included in the merged static mesh for that LOD asset. But, no matter what settings I used, I could not get the tree to merge with the other assets in that sublevel correctly. I duplicated it in the project, and then deleted the lowest LOD for the asset, thinking that it may be the fact that LOD4 for that asset was effectively two quads turned at right angles with a texture of a few branches on them. That didn’t work. I changed the material settings in the “Static Mesh Material Settings” rollout, changing the material type to masked instead of opaque. That resulted in the same broken looking tree after regenerating the LOD asset. But, don’t get too frustrated, because I haven’t mentioned the last aspect of W.C.’s LOD generation system that I am going to cover.

When I make a reference to the sublevel’s LOD asset, I am actually referring to a completely different sublevel that W.C. streams in and uses to replace the parent sublevel. For the sublevel being shown in Image 1, you will see that we are looking at the options for LOD1 of sublevel E5-2. When W.C. generates an LOD asset for E5-2, it actually creates a completely separate level which has the landscape proxy mesh and merged static mesh actors contained within it. It is a complete level! This is stored in a folder named E5-2LOD, and contains all of the LOD assets for the sublevel E5-2 with the name E5-2_LOD1. There are no lights within the level E5-2_LOD1, because I have my direction light in the persistent level. Actually, there isn’t much in this level to be honest, but it is a complete level. To fix the tree issue, I have exported the merged static mesh for E5-2_LOD1 and removed all of the triangles for the tree. I then set this new FBX file as the source file for the editor to use for the asset. After that, I just pressed the reimport button in the editor for that merged static mesh and the tree was gone. I know, you’re probably saying that this isn’t much of a solution; that you want your tree. But, after I removed the tree from the merged static mesh, I opened E5-2_LOD1 and placed the tree back into the level as a separate asset. Once I saved E5-2_LOD1, the replacement tree was now a part of the LOD asset for E5-2 and whenever the player moved far enough away from E5-2, the LOD1 asset would be streamed in and displayed. Sure enough, there was my replacement tree; exactly what I wanted. Because the replacement tree will still follow all the rules set out in it’s own LOD settings, as the tree takes up less-and-less of the screen space, it will use lower-and-lower LODs of the mesh.

With the realization that each LOD asset generated by W.C. for each sublevel is nothing more than a separate level object that is swapped in, you may be having the same idea as me. We may be able to just open these LOD assets and use the foliage mode in the editor to place simplified versions of our trees into these LOD assets. They are, after all, just levels like any other that we might work with. We would have to define different static mesh foliage assets, because we would want to use a much lower static mesh LOD for these. But I don’t believe that these static mesh foliage assets are very large, so the cost may be well worth the results. I haven’t tried this (yet), but I see no reason why this wouldn’t work.

Throughout this article, with the exception of a few places, I have been talking about LOD1 for the sublevel named E5-2. Nevertheless, everything that I have said applies to all of the sublevels in my open world test map. Not just for LOD1 either, but for all four of the levels-of-detail that W.C. will allow us to create per sublevel. There are a total of 36 sublevels making up this open world test map, and for each of these I have the maximum of four LOD assets. That brings the grand total up to 144 levels that W.C. creates for me to use as the LOD assets for the sublevels. Yes, there is still quite a bit of work that would need to be done if I was to use these, but it is a huge help that W.C. can generate these for us.

One last word of warning. Do not edit any of the LOD assets until you are sure that you are happy with your LOD settings in W.C. This is because when W.C. generates these LOD assets, it will gleefully overwrite any changes that you have made. When I removed the tree in the middle of the village, I regenerated LOD1 for that sublevel again, and the merged static mesh actor that was created had the tree right back where it was. My change was gone, but I knew that would be the case. The same is true for all of the meshes and/or materials for the LOD assets. Only alter them once you know you will not be regenerating them again.

Well, this has been one of the longest posts I have made on the site. I don’t claim to be an expert with this tool, and with UE5 moving to World Partition, we may never see any information coming from Epic about this tool again. I hope that it helps somebody. Thanks for sticking with me through this very long article. Have a great day and happy developing!

Landscape Optimization

There has been so much going on since the last post. The design document has received more work, detailing some changes to the backstory that is going to directly effect the overall structure of the game. Also, in the design document, we have detailed more recent game features that we are adding. Each major region of the game is going to have it’s own environmental game feature. For example, in the jungle, players will be able to obtain a grappling hook and swing through the trees. In the forest area the player will be able to gain access to a wing suit and glide through parts of that region. This last feature lead to some interesting observations and some decisions about the size of the game itself.

It was always our intention to make this a reasonably large open world. But, I hadn’t given much thought to just how big the main map would be. While testing the wing suit in it’s prototype project, I was able to glide over 0.7km (0.43m) in a single glide. This lead to the obvious question: Do we want to nerf this feature, or do we want to go big on the landscape? Having done Capuchin Capers as a series of reasonably large islands, and having dealt with the optimizations needed for performance, I knew we needed to test truly large landscapes before making this decision.

Landscapes are a big topic, no pun intended. I spent numerous days in Unreal Engine 5 Early Access 2 because I knew that Epic wanted to focus more on open worlds with UE5. After my testing, I have decided that we are going to stick with UE4 for this game. There are some great features in UE5 that directly impacts exactly how developers go about building open worlds, but I just can’t count on all of the performance issues with landscape actors in UE5 to be fixed at launch. A landscape in UE4 that would run at a very comfortable +120fps runs in the low 60’s to high 50’s in UE5. I know that Epic will get that sorted out, but we can’t put years of work into this project, all the while hoping that these issues will be resolved to a reasonable degree. World Partition, Lumen, Nanite, and MetaSounds are all great features, and I really wanted to take advantage of these. That is why I spent almost four days trying various approaches to be able to use UE5. But in terms of the workflow building landscapes, UE5 has a long way to go.

My experience with UE5 lead me back to UE4 and World Composition. I had watched the live-streams for W.C. and read the documentation for the system. Unfortunately, there isn’t much information for this system compared to other parts of the engine…it just doesn’t get used nearly as much as the ‘main’ areas of the engine. But, after spending several days testing and experimenting, I feel that I am getting my feet underneath me enough to be confident that we will be able to achieve our goals with W.C. There is a lot to learn about it, and some serious quarks to the whole thing that I spent way too much time fiddling with.

The first is making a truly large area without any seams between the landscapes, stored in the various levels in World Composition. I wanted a total map size of approximately 12km2, which would require a heightmap with a total size of 120992 (this was slightly off by a few pixels, but it didn’t really make a difference). I tried to create each landscape separately, in it’s own sub-level in W.C., but I kept running into a common problem. I was getting very small cracks between the individual landscapes. At first I thought it was due to the heightmaps having ever-so-slight differences in their color values along these seams. That was not the case. You can do any color corrections that you want, but it won’t get rid of those seams. Worse, you can’t use the sculpting tools to fix these seams because they are due to a separation of two different landscape objects. You can’t ‘paint’ across the boundaries, because in fact, the landscapes don’t share any boundaries. They just happen to sit next to one another. After watching the live-stream several times to try to get a hint of how to fix this, I noticed a really nice feature that is mentioned around the 47:36 time mark. Add Adjacent Landscape Level. Those are currently my favorite four words in the English language (that is why I highlighted them the way I did…I love those four words).

There are two approaches to using heightmaps in Unreal. The first is during landscape creation, via the ‘Import from File’ option. This approach will create the landscape based on the file chosen, generating presets for the number of components, section size, as well as the other settings for a landscape. This is nice if you have a single landscape in your level that isn’t larger than the limit for a landscape object. However, if you are attempting to create a map on the scale of an open world, this option won’t work. You inevitably get those seems between the landscapes.

Image 1: The first approach that you might take to creating a landscape using a heightmap.

The second approach to using a heightmap is to first create your landscape object, defining all of the various settings in advance, and creating a totally flat landscape (see Image 2 below). Then, once the landscape has already been created, you can load in a heightmap after-the-fact in the “Target Layers” rollout of the sculpting settings (see Image 3).

Image 2: Creating a landscape manually, by defining all of its parameters and pressing the create button. This results in a flat landscape with the dimensions that you defined in the rollout above.
Image 3: Using the orange ‘Heightmap’ button to load in a heightmap to be applied to your landscape.

Normally, when you are loading in a heightmap for a single landscape, there is little difference between the two techniques. With either technique, you can set your landscape’s component count, section size, etc. But no matter what you do, using these techniques alone, you can’t create a single landscape large enough to be used for an open world. That is where “Add Adjacent Landscape Level” finally comes into play and all of this makes more sense.

If you first define one of your landscape levels in W.C. via the ‘Create New’ menu in the W.C. tab and create your landscape tile using the “Create New” option, you can define your first landscape with all of the settings that you would need for a single tile in your open world map. In my case, that was the settings that you can see in Image 1 above. This is just one of nine tiles that make up my open world map, though, and I needed to add eight more to make the entire map. But make sure to keep in mind that I did not create that first tile via a heightmap. I predefined the landscape with the settings that I knew I wanted, and I created a flat landscape. Once that landscape was created, I used the “Add Adjacent Landscape Level” feature to create the other eight landscape tiles that I needed. You can see in the live-stream that when you use this feature, the landscape created does in fact go into it’s own level. But, it is sharing the vertices along the boundary with other landscapes created in this way. So, while each landscape is in it’s own level and benefits from all of the tools in W.C. that apply, the landscape objects themselves are seamless. You can sculpt or paint across the boundaries without issues.

That wraps up this rather long post, and in the next post I will discuss some of the issues that I ran into while working with W.C.’s LOD system that you can use. You can see the effects of that LOD system in the main image for this post (which I am also including below). Thanks for sticking with me on this post, and I hope that it helps you build the open world of your dreams!

Image 4: The open world test map. Some of the mountains in the background are over 3km away! The actual mountains are mesh proxies being used in World Compositions LOD system. More on that in the next post…that is called a teaser, and I am a jerk for doing that. But, that didn’t stop me, did it? 😀