| SimonDev.io | https://simondev.io/ |
| Youtube | https://www.youtube.com/channel/UCEwhtpXrg5MmwlH04ANpL8A |
| https://www.instagram.com/beer_and_code/ |
| SimonDev.io | https://simondev.io/ |
| Youtube | https://www.youtube.com/channel/UCEwhtpXrg5MmwlH04ANpL8A |
| https://www.instagram.com/beer_and_code/ |

Once you've made it through all these steps
• Reuse materials
• Batch/instance
• Optimize data
• Cull
• LOD/imposters
We're hitting 1 million+ trees, for very little CPU/GPU cost.
Octahedral imposters are a powerful technique, where we render the object from many angles into an atlas texture, and then just show a billboard in the world.
The key detail, it responds to camera movement and lighting, but it's just smoke and mirrors.
TSL makes it easy to hook into the lighting system, making it seemless.
At this point, the last lever left is reducing quality, but it’s a powerful one.
LOD (level-of-detail) works by dropping detail with distance. As an object gets further away, you swap meshes (LOD0 -> LOD1 -> LOD2) and nobody notices (hopefully)
With instancing, you'll have to do this manually with an InstancedMesh for each level.
At some point it’s hard to “draw faster”. So stop drawing stuff you can’t see.
Frustum culling removes anything offscreen.
This isn't automatic with InstancedMesh, so you can either:
• Instance within a chunk, then cull by chunk.
• Cull manually per-instance
We’re at 250k+ trees now.
(sidenote: occlusion culling, scene-dependent but huge when it applies)
You can quantize way further than most people think.
It’s possible to squeeze a ~56B vertex down to ~16B with packing + quantization.
TSL makes unpacking clean (override attributes via node API).
Source: https://x.com/SebAaltonen/status/1515735247928930311
This gets us to 50k+ trees.
Now take a look at your data.
You want GPU-friendly assets, not just smaller downloads.
Meshes: weld verts, simplify, quantize
Textures: Use GPU compressed formats (like ETC1S/ UASTC)
I use my in-browser GLB optimizer to do most of this: https://gltf-optimizer.simondev.io
Instancing allows us to tell the GPU in a single draw call: "hey, draw this thing a zillion times".
No need for the CPU to constantly submit draw commands, which alleviates the load on the CPU and shifts the bottleneck to the GPU.
We're hitting 30k+ trees now.
To draw a lot of stuff, you want to reduce materials. Collapse different materials into a single material by packing textures into atlases.
Then you can collapse draw calls with:
• InstancedMesh (same geo)
• BatchedMesh (different geo)
Low-hanging fruit: stop duplicating assets.
Share geometry & materials across instances and you'll see immediate improvements in the framerate.
This change alone gets us to ~700–800 trees at 60fps.