Hello everyone,
I'm working on a procedural infinite terrain generation, and I've run into a significant performance issues regarding the instantiation of foliage, specifically trees (rocks etc.). I'm looking for advice on the most optimised and scalable approach to handle this.
Current Situation:
My world is procedurally generated and divided into chunks. As the player moves, new chunks are generated around them, and distant ones are unloaded. The terrain mesh is generated on the fly.
After a chunk's terrain is created, I have the data (positions, rotations, scales) for every tree that should exist within that chunk. The number of trees can be quite large, sometimes up to 5000 objects in the loaded area around the player.
The key challenge is that these trees are not just decorative. They are resources that the player can interact with (e.g., chop down for wood). This means each tree needs:
A Collider for physics interaction.
A script attached (e.g., TreeResource.cs) to manage its health, what it drops, etc.
The Problem:
My initial, naive approach of calling Instantiate() for every tree causes massive lag spikes and is completely unworkable. Even if I were to use a simple object pool, managing and activating/deactivating tens of thousands of GameObjects seems like it would still be a huge performance bottleneck, both for the CPU and memory. Besides that, there are 30+ different types of trees.
I understand that for purely visual objects, GPU Instancing (Graphics.DrawMeshInstanced) is the way to go, as it avoids creating GameObjects and significantly reduces draw calls. However, this method doesn't support Colliders or scripts.
TLDR;
What is the most efficient and scalable architecture to handle "spawning" a massive number of interactable objects on an infinite procedural terrain?
Any general direction/optimisation ideas are more than welcome 😄
#Procedural Terrain Generation and object spawning
1 messages · Page 1 of 1 (latest)
Well, if you're object pooling then spawning shouldnt be a problem, but if you're talking about how to you keep gameobjects/colliders to a minimum on a large level then you should segment out your level into sectors. If your character is in sector [1, 1], well we should populate this sector and all the neighboring sectors like [1, 0] with trees
Probably the most simplest way about it
- use render batching
- use DrawMeshInstanced for everything outside of your reach (more than a chunk/distance away)
- consider using a central data storage for tree data so your data does not vanish when unloading and switching to the instanced mesh, bonus points for using ECS for that
- use LOD meshes for greater distances
- consider even switching to impostors for very high distances
Hey thanks for the reply, if by sectors you mean chunks, I have that implemented but still, spawning objects in neighboring chunks causes lag spike. I am still not sure if pooling is the right way to go here, because there are plenty of tree/rock variations and some area can have up to 500 of same interactable trees/rocks. By my calculation, lets say I have 30 unique rocks/trees and inside of pool I need to have around 500 for each of them, that's 15k objects. Not sure if that's the right approach. I've only worked with object pooling for one-same prefab.
I am now thinking out laud, but can I reduce lag by having ~5k objects in pool and change its mesh/material and attach scripts at runtime for each different tree/rock (when needed)?
Are you using all 15k objects? You can probably generalize a lot of your pooling prefab such that all you have to do is attach some SO data to it to give it identity
And as far as the data struct for the pool, you can do something like a circular buffer if you don't want to dynamically shrink it
And yeah the SO can include material/mesh info
so what you ultimately are dealing with is what's loaded in the shader
Thank you, that's incredibly helpful! I'm now in the process of implementing the features you've outlined. I'll post updates on my progress in this thread for anyone who might find it useful in the future
Not really, as far as I've tested 5k is max number of interactables in neighboring chunks
So you think that by keeping needed info in SO's I can swap mesh/material + attach different scripts at runtime and still benefit from pooling? Either way It sounds like it's worth trying
Pooling is more of a GC optimization
materials and meshes should be batched anyway
only time you need to create a new material is say if you do interact with a tree and want to chop away pieces
so technically you'll always be using these shared meshes and materials pooled or not
so even if you do pool by SOs, say you do have 1000 different SOs all loaded with material references using 8k textures, well that's a lot that's being loaded even if those gameobjects aren't being rendered currently.
I would expect Unity would probably help reduce what's loaded if it notices renders not using those materials, but not something I've looked into