State of the Game: Video Devlog 6.0 | Performance Improvements
Hi, dungeon-crawling fans!
It's time for another project update! This month's video is a little more technical since we are talking about performance improvements. There have been a few problems with the audio recording at the beginning - sorry for that! Anyway, please, enjoy:
As always you can find the transcript below.
Best wishes,
Michael
-------------------------
Hi, dungeon-crawling fans!
Another month, another update! There has been a lot of progress during the last few weeks, especially in terms of performance. As such this update will be a little more technical and I hope you don’t mind. I think it is worth documenting a few of these things and letting you get an impression of what the current workload looks like. Basically, I decided to pull some tasks from the polishing phase into beta update 4. Next time we will get back to level design, game systems, and similar. So let’s take a look at it.
The current problem with performance could be described as the legacy of prototyping. It’s not uncommon in this phase of development. Basically, I am trying to bring the environments into their final shape while cleaning up unnecessary and inefficient structures.
The main focus here is on draw calls and lightmap memory.
For draw calls, you need to understand that for every frame that’s shown on your screen, every visible element has to be “drawn” by the engine, or rather your hardware. More precisely, what is drawn is a so-called material - a material is simply a surface, consisting of different texture maps and shader functions.
So for example, if there is a mesh - a 3D object - with both a rocky and a metallic surface, split into two materials - that’s one object with two draw calls.
For lightmaps, you need to understand that a lot of lighting is statically baked into the environment. The result of this process is called a lightmap, that stores the lighting information that is applied to static surfaces. The more statically lit surfaces you have in a level the bigger that lightmap becomes. And the bigger that lightmap becomes, the more memory the level eats, the longer it needs to load, and so on.
So much for the basics. There are a few little oddities here and there but I’ll skip those for the sake of simplicity.
So why are lightmaps and draw calls an issue? If you have read some of my older project updates, you know that Monomyth’s level design is very strongly driven by modular pieces. This is nothing exactly novel. If you have ever opened Bethesda’s Creation Kit you are probably familiar with this technique. Basically, every part of a dungeon, like a wall, a door, a corner, and so on, is like a small building block you put together on a grid system - which is also very intuitive if you plan your levels on graph paper or in software that allows a similar approach.
So, Monomyth basically does the same thing, however, most building blocks are themselves constructed from smaller building blocks and unified in a so-called blueprint object, which, in this case, is simply a collection of different static meshes.
So far so good, however, those minor building blocks all require an individual series of draw calls. And here the problems start.
Now if you, let us say, construct a hallway, you take a simple plane for the floor and the ceiling and two walls - which are actually separator walls, but I will come back to that later. So those are four different minor building blocks unified in a blueprint object.
This has the advantage that you can very easily create a lot of different modular parts by re-using all kinds of building blocks, however, it also means - in the case of our hallway - at least four times the draw calls of what is necessary. So once you enter the polishing phase and you know how things are laid out, this is a primary target for performance optimization.
You need to merge those four meshes into one and replace the respective blueprint objects. Luckily Unreal Engine provides an actor merging tool and replacement mechanisms that can do all of that relatively quickly. It takes a while but it’s manageable and the results are pretty satisfying.
Now another way of optimization here is not only merging the meshes, but also merging the materials. I won’t go into much detail here, but a very simple and effective way to do this for static environments is vertex painting. Essentially, what you are doing here is applying a mask to the mesh, that tells the material on which surfaces to display which textures and how strongly to blend the two.
And now instead of having two draw calls for let’s say a ground material and a wall material, you only have one material including ground and wall textures, and as such one draw call. And that is quite significant because that’s at least half the draw calls for an object. You can expand on this technique by using RGB and alpha channels for vertex painting as well, giving you access to four different surfaces in a single material. So optimizing your materials and meshes in this way is like a gold mine in terms of draw call improvements.
Now, the other problem, the lightmap sizes, can largely be solved hand in hand with the aforementioned problem. I mentioned separator walls before: Most minor building blocks were built to be used on the edges of the grid system. So for example, most walls were not just one-sided objects, but actual two sides walls. This is great for prototyping, but not so great for performance. You have an easy time blocking out environments fast, but a lot of the walls’ surfaces will end up facing out-of-bounds areas.
That’s a waste of lightmap space. So not only would I replace multiple meshes with singular merged meshes, but I would also, wherever possible, remove these two-sided walls. Depending on the level this brings down the lightmap size significantly.
So far I have done this whole optimization procedure for the Heartlands and the results are showing. The average draw calls now sit at a convenient 2000 per frame, which is, if I remember correctly, roughly in line with what’s recommended in Unreal Engine’s documentation. I still need to optimize the later areas in the game but with the new meshes, a lot of work has already been done in this regard.
I also tested these new, optimized meshes by creating a few mini-dungeons. One of these was expanded into a new tutorial section, which will also be included as a starting area in the next patch.
This change went hand in hand with a minor restructuring of the general area progression that I wanted to do for a while. The demo area you know was moved to a later point in the game.
Besides these changes in level design and performance, I also made sure that most of the character voices are done by now. One thing that should maybe be highlighted here are the wastelander voices. These guys now have their own language, including a legitimate vocabulary, which can be, if you are attentive enough, translated throughout the game.
Some new music is also in the works and should be done before the next patch.
Originally I thought that Beta Update 4 would be a little smaller, since it’s technically just the main hub and the main quest, but I think that preponing some performance polishing tasks was a good idea, because it makes the beta testers' job easier as well. Otherwise, I would have started this process directly after Beta Update 4 so practically no harm done timewise. Performance polishing should actually happen continuously, so the sooner and more often the better.
Anyway, I’ll go back to finalizing this patch now. It shouldn’t take long anymore. Out of all patches, I’m probably the most excited about this one so far, because the full experience is now coming together really well and certain new elements, like the voices and combat music, really help immersing yourself in the setting.
So, I’ll keep you updated and see you soon!