27
\$\begingroup\$

I'm making a low poly stylized kind of game. I have a terrain with some water, and I want lots and lots of trees; I have 10,000 trees mass placed, at the moment. Each tree consists of no more than 200 triangles, so they aren't too taxing.

The main problem is that there are lakes, and the lakes are quite large. You can't actually see any trees on the other side of the lake, and that looks really bad, especially when you walk there and trees suddenly appear.

To fix this, I have to increase the tree distance so that you can see a decent amount of trees on the other side of the lake, but that reduces performance to 40-50fps, and there is hardly anything else in the game yet. I'm using a GTX 1080, if that helps.

What can I do to make my game run faster with more trees?

\$\endgroup\$
6
  • \$\begingroup\$ IIRC, Silent Hill used fog to hide the cutoff at the far clipping plane, which allowed them to start dynamically loading things just beyond where the fog cuts off. You might benefit from a change in the atmosphere of the game. \$\endgroup\$
    – Cody
    Mar 13, 2017 at 18:03
  • \$\begingroup\$ The trees are drawing you to look out the window, thus lowering your performance. \$\endgroup\$
    – mbomb007
    Mar 13, 2017 at 19:39
  • \$\begingroup\$ Have you tried running the profiler? If so, where's the bottleneck? \$\endgroup\$ Mar 15, 2017 at 15:13
  • \$\begingroup\$ Are you doing any kind of frustum culling? \$\endgroup\$
    – Krythic
    Apr 6, 2017 at 20:44
  • \$\begingroup\$ What is frustum culling? \$\endgroup\$
    – mr-matt
    Apr 7, 2017 at 3:34

2 Answers 2

45
\$\begingroup\$

There are couple of things you can do to increase drawing performance.

  1. You said they were pretty far away. You could use LOD to decrease the vertex count of those trees, and thus decreasing time required to go through all the vertices being drawn. Even though this is most likely not the issue at hand (GTX1080 with just 10k trees with 200 tris each, puny numbers for the gpu) I still included it. Billboarding is an effective tool for the lowest LOD level, as it is essentially a flat plane always facing the camera with a rendered image of the tree on it. It loses sense of depth, which is why it is good for the lowest level as the player most likely won't notice the difference.

  2. Have you enabled batching? Dynamic batching is usually done automatically if the vertex counts of the meshes are fairly low. Static batching can also be tried by making the trees static by checking the checkbox in the unity editor on the parent gameobject. This does not work well with animated objects. You need the objects to have shared material to make this work.

  3. Custom batching lets you control the rendering by generating the chunks yourself instead of letting unity handle it, and it also enables batching for larger meshes. This is done easily by Mesh.CombineMeshes. This also does not work well with animated objects. You need the objects to have shared material to make this work. You probably want to divide your world in to some kind of chunks and create batches from those. How those chunks should be generated is really up to how your camera moves in the world.

  4. Enable instancing on the shaders. Instancing enables the engine to draw multiple objects (with same mesh) with just one draw call. You need the objects to have shared mesh and shared shader for this to work. The material can vary, but the shader must support all the different varying properties.

To make the engine create instanced rendering batches better, you probably want to group the same meshes together in scene. Also playing with material render queue will give you good results if one mesh has always the same material. During the development the mobile game I currently am working on, I used this to reduce drawcalls by more than half in my testing scene. Also since Unity 5.6 make sure you check the Enable Instancing checkbox in the material.

  1. Keep your drawcalls and SetPass calls down in general. These are the raw calls for your GPU to draw stuff, and they have a large overhead. Reducing drawcalls (which is what batching and instancing is doing) will increase the overall performance your CPU can provide as it is required to do so much less waiting. SetPass calls are changes to your current shaders, so if you have many different materials you will have multiple SetPass calls, which are also causing the CPU to wait a little bit.

  2. If your scene is huge and your CPU time goes in to going through all the objects in the scene, try to reduce the objects in the scene. Group up some trees instead of placing them invidually and have them as a single object. Also make sure you're not moving the trees or the parent objects, as that makes Unity to discard cached transforms and to recalculate the whole scene tree.

  3. If your scene is huge and your CPU time still goes mostly in to Unity walking through the scene tree to make lists to render all the objects, one thing you could do is to not let Unity to handle the rendering. If you have a better way to track drawable objects you could use CommandBuffer.DrawMeshInstanced or Graphics.DrawMeshInstanced to draw them by hand. I won't go in much detail about this as it's a lot more advanced and involves culling obects yourself and whatnot.

  4. Dynamic lighting is not as fast to render as static lighting. You can pre-render your static lights to a lightmap. You should also reduce the number of dynamic lights if possible to make rendering the frame take less time. Forward lighting is harder to pull off if you have multiple light sources, where deferred lighting works a little bit better in that regard as it is able to reduce drawn pixels per light.

In case if the static or dynamic batching is not working properly (which you can see by checking the frame debugger), you need to make sure that you are indeed using shared material, and are not making copies of the material by accident with calling meshRenderer.material. Calling the .material will make copies of your materials and break batching. Use .sharedMaterial instead.

Since Unity 5.6 you can use the Frame Debugger to determine why certain drawcalls did not batch with the previous drawcalls. This will be really helpful while trying to reduce the drawcalls of your game.

Instancing has the following advantages over static/dynamic/custom batching:

  • Uses less memory as the mesh does not have to be duplicated in memory
  • Can use multiple materials, only shared shader is required
  • Objects can be animated

Also as a downside it is rather new feature in Unity and might be a bit unstable. Also older or mobile device GPUs do not necessarily support instancing.

\$\endgroup\$
17
  • \$\begingroup\$ 1. Yes, I have tried LODs, and much to my surprise, it actually made it worse. I had 3 variations of the trees each with a slightly lower vertex count, and one flat plane with a rendered image of the tree on it. \$\endgroup\$
    – mr-matt
    Mar 12, 2017 at 8:01
  • \$\begingroup\$ 2. All my trees are marked as static. As far as I know that enables batching? Is that correct? And when you say shared material, do you simply mean, the trees have the same material? \$\endgroup\$
    – mr-matt
    Mar 12, 2017 at 8:02
  • \$\begingroup\$ 3. What sort of performance improvement would this actually make? Is it worth trying? \$\endgroup\$
    – mr-matt
    Mar 12, 2017 at 8:03
  • 1
    \$\begingroup\$ 2/3: In my experience Unity does not do very good job at dynamic batching and static batching increases the build size, so I implemented the batching myself and did the mesh combining load-time. Using this technique I managed to make 2500 drawcalls to compress into roughly 300 drawcalls which was crucial for our mobile game where drawcalls are important. It made little bit of unnecessary vertices to be calculated (offscreen) but it was worth it. \$\endgroup\$
    – Lasse
    Mar 12, 2017 at 8:13
  • 2
    \$\begingroup\$ tfw when you read a question about poor performance with trees and not a single word about billboarding is lost \$\endgroup\$
    – Num Lock
    Mar 13, 2017 at 7:34
13
\$\begingroup\$

Ok, so the problem was simply that I wasn't using pre-computed realtime GI. I check that on a little while ago but it didn't have an immediate effect so I left it and forgot about it, and the lighting processing time was so long too. However, it just finished processing it, and my word, my fps has jumped up by 3x. So for now, I'll leave it at that and in the future make sure I'm always using precomputer realtime GI!

If there still is anything else that I could be doing to further improve performacnce, please let me know, I would be most grateful!

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Use Occlusion Culling, also if your scene is big - consider dividing it in chunks, for example many terrains and load and unload them. \$\endgroup\$ Mar 12, 2017 at 10:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .