Main concept
Mesh instancing is a good way to optimize draw calls, in theory at least. I won’t go too deep into explaining what draw calls really are but the easy way to understand is that the CPU is sending “letters” to the GPU about what to do. It can send lots of small letters that will increase traffic or it can send a few larger ones that might be more optimal. This is an approximation of course but it’s one of the reasons why we want to instance meshes.
This screenshot shows one example where instancing has been heavily used in almost everywhere from buildings to grass and trees. Performance is still very smooth and various systems generate elements automatically. In this article, I want to focus on how to handle those instances in a handy way.
Benefits
Like mentioned above, instancing will usually reduce the amount of draw calls that is needed to render meshes. In theory it will just be one component with multiple instances inside so that it can be packed into one “letter”. This will help to optimize CPU performance and help to reduce wait time in the GPU side too. It’s perfect for things like grass, trees, rocks, debris but you can also use it with pretty much anything else too where you have lots of the same meshes.
Instancing can also give some benefits that you can use in materials too. Unreal can give each instance an unique id that can be then used with materials to have things like color variation and such. This is perfect when creating blueprints that add random meshes so we can also randomly change each instance appearance.
Drawbacks
Because instanced meshes are stored inside a component it also means that you can’t really change per instance settings like shadows, collision etc. like you would be able to do with basic static mesh components. You can only change the component settings that will affect each instance that is in that component. Instances are also existing inside that component space so you can’t really move a single instance manually by hand like you can move basic static meshes, unless you use plug-ins. This also means that component bounds are related to those instances and that’s why bounds can end up being very large in some cases.
This will lead to another issue that is lighting. Because that component can hold lots of instances that can also be large and complex and far away from each other, it can also create large bounds that makes it more expensive for dynamic shadows. For static lighting that can mean very large lightmaps because it’s one component that holds lots of meshes inside. That’s why mesh instancing is something I’m not recommending when using static lighting.
As you can see, it’s not that trivial to say that you should always use instanced meshes so it’s important to think when to really use instancing. Most of the time benefits will outrun these drawbacks.
Automatic HISM functions
I’ve been working with this very generic blueprint function that makes it super easy to use HISM components. It’s using hierarchical instanced static mesh components (HISM). That way these instances can also handle LODs. You can also use basic instance static mesh components (ISM) as well. Main idea is to have a system that uses a static mesh array and it will then choose random meshes from that, create HISM components for each unique static mesh and after that, figures out where to put those meshes to keep the amount of HIMS components as low as possible. That will also mean fewer draw calls. It does all this automatically so the end user only needs to add meshes in that static mesh array and function will handle the rest.
I will walk you through this function. In order to fully understand this, you should have a basic knowledge about blueprints first.
Function needs few input variables in order to work. First one is the actual HISM Components array that will be holding all of the HISM components the function needs to create based on the static meshes. Transform input is controlling where the instance will be placed in the world. Static Meshes array is holding all of the meshes you want to instance. Instance to World Space boolean is there to control different scenarios. Culling Distance will control when those instances will be culled out.
First thing the function is doing is it will check how many static meshes the array will have and based on that, it will get a random int value. This value is then stored into a local variable that is being used to get random mesh out of the Static Mesh array. This static mesh is stored into a local variable too so it’s easier to use it later on.
After this the main logic really kicks in. It will use the input HIMS Components array to check if that already includes a HISM component that is using the same mesh that we have in our Local Static Mesh variable. For example, that static mesh can be a ball. If any of those HISM components in that array uses that ball then we know that we can store that mesh instance into that HISM component. Then we can simply run our Create Mesh function that I will describe more later.
If that loop doesn’t find any HISM component with that mesh then Local Is Found boolean will be false. In that case we need to create a new HISM component because a HISM component can only store one mesh type or otherwise the instancing won’t simply work. In that case it will add a new hierarchical instanced static mesh component, set some collision and cull settings and then add that into the HISM Components array. This way that array will have a HISM component that is using ball mesh so we don’t have to create a new component next time. After that it will create a material instance (if we need to change colors etc.) and run Create Mesh function. This is pretty much how the function handles instancing.
So, Create HISM function is handling component creation but we still need to actually add instances into those HISM components. That’s what the Create Mesh function is doing. Basically it will use a HISM component (described previously), set material, static mesh and then add an instance to it. There is also that option so that instance can be in world space or relative space, depending on your needs. This function also returns an instance id that can be handy if you need to get a specific instance later on.
This is a very generic solution so you can have lots of meshes that you want to randomly add somewhere and this system will make sure those meshes will be instanced correctly so we'll always have a minimal amount of HISM components and draw calls. Maybe you want to scatter debris along the spline, or you want to create a procedural building system that will be as optimized as possible. You simply need to figure out the transform and what meshes you want to use and this system will do the rest.
Until next time,
Kimmo K.
You can also find me in the following places too:
UE Marketplace Artstation Twitter Facebook Youtube