Optimize lots of instanced meshes for a voxel game

Ok so I’ve been making a Minecraft like game in Unreal Engine 4, it looks really good right now but I’m using instanced meshes for the blocks. There’s a really big amount of blocks (like 161664) per chunk so the game lags a lot when generating chunks around the player and when saving them. Everything so far was done in 100% blueprint. Is there a way to make thousands of instanced meshes more optimal or if there’s a better alternative to instanced meshes for a Voxel game. I made a couple of videos on my project on my channel.

Are you logging much to the Console? Looks like you do with your Position display. I had a similar issue and i was logging much to the Console, but the next Problem was that everything ran on the GameThread. Maybe you should try to get your game to C++ so you can make use of Threads.

Well I just added the position display for fun but the only thing really running on event tick is updating the players position to a variable. It only adds chunks when its changed. Everything else like destroying and placing blocks are all done by input actions.

Also the way I save chunks is when the actor is destroyed, I store each individual instanced mesh’s position and name inside the gamestate and also the whole actor position, and then when a chunk is spawned it sees if that chunk’s location matches one inside the game state, and if it does it uses the data from it and spawns the instanced meshes. That works perfectly but it’s just really really laggy.

Have you profiled to see if your CPU or GPU bound. That’s always the first thing to check as it might not be the meshes themselves but rather the blueprints attached to them (if anything) or some other process you have which is managing the blocks (if that’s looping through them all every tick and allocating memory, that’ll slow you right down)

Do your meshes use a single material per type? If you use more than one material per mesh it can break the instancing.

Are you using dynamic lighting? If so then it might be the lighting / shadowing calculations that are slowing you down.

Even when not using dynamic lighting I still get tonnes of lag, also each instanced mesh uses a different material instance but they all have the same base material. I don’t know how to profile my CPU or GPU but I have this:

If you expand the ‘scene’ option in your screenshot does that give you any idea what the expensive part is?

You can find out about GPU profiling here: GPU Profiling | Unreal Engine Documentation

And CPU Profiling: CPU Profiling | Unreal Engine Documentation

With regards to the the materials: Open up your static mesh and look at how many material slots are used. If it’s more than one slot then this might be causing problems with instancing. Probably the easiset way to see how effective instancing is being is to enter ‘stat scenerender’ in the console (whilst playing the game), then look at the number of mesh draw calls. If this is a very high number (as in around the same as the number of blocks or higher) then instancing isn’t working. If it’s a small number then you’re probably OK.

Also, look in your materials and check what the number of instructions is (Look in the ‘Stats’ panel). If this is unusually high then you might want to look at decreasing the complxity of your materials.

One of the problems with instancing is that if you draw one member of the instanced group, you draw all members. This means that if you have a dirt block burried underground where you can’t see it, you might still be drawing it and calculating lighting.

Another useful command is to enter ‘freezerender’ into the console (whilst playing). This will prevent the render loop from updating so you can fly around and check if occlusion etc is working correctly. In your case you probably will be drawing a lot of blocks that aren’t necessarily visible (due to instancing) but it’s worth looking at the results.

Also look at the new render pipeline in 4.22. This will effectivly manage instancing for you at runtime. I don’t think it’ll be as efficient as manual groups but in your case where you have a lot of instanced objects, the majoirty of which should be occluded, it might work better. You will need take all of your objects out of instance groups for it work.

Also look at using Distance Field Shadows in place of dynamic shadows. They might be faster and more suitable for your game.

I increased the chunk dimensions a bit before taking this snippet so it’s a bit different from the last one. Imgur: The magic of the Internet I really don’t know what those numbers are supposed to mean. The above link also has the material stats.When i freeze rendering only the bedrock at the bottom stops showing but all the stone and dirt is still visible. I also typed stat SceneRendering in the image above. My project was made on version 4.21.1 because that’s what I had downloaded at the time and I can’t really update at the moment because I’m nearly out of storage space.

I’m taking a look at distance field shadows now.

The material stats don’t look too bad. You might be able to get them lower by optimising the material. It’s probably worth making sure that your textures are optimised for the game too (ie. no point having each texture as a 1024x1024 in this case, so make it smaller and set the texture filter / mip-maps as needed). If you want to go futher, you could put all your possible block textures in a single sheet and use the materials / instances to select which part of the sheet you’ll be using for a given block.

FreezeRendering does I expected. As I said, if you can see one element in an instance group then all elements will be drawn. Because the bedrock is entirely hidden from the player (i.e. the player can’t see a bedrock cube) then there is no need to draw it. However, if even a single pixel of one bedrock were visible then it’ll draw all the bedrock items in that instance group.

Unfortunatly when it comes to optimisation there isn’t really a one-solution-for-all approach so you do need to dig into the technical details a bit and work out exactly what your game is doing, then try and understand if there is a better way or what other changes you can make to improvement. In most cases it’s a combination of many thing rather than one quick change.

Each block texture is 512*512 and the filter is set to nearest. I set the mipmaps to from texture group and the texture group is the default option.

I’ll keep looking into it.