3
\$\begingroup\$

I'm planning on creating a first-person shooter for mobile. Now I know that fps games usually have a lot of meshes in the scene (trees, buildings, terrains, etc.). So I've been looking at a lot of Unity optimization tips. One thing I've noticed is that they always say "If you have a lot of objects in your scene, then each mesh must have low polygon count".

But how exactly does this help? If I have a lot of houses in my scene and I ensure that each house has very few polygons, wouldn't the total number of polygons in my scene be affecting the performance anyways? Or will my scene be rendered differently now that I've broken up the total polygon count into multiple parts?

modern combat 5 screenshot

\$\endgroup\$
1

3 Answers 3

7
\$\begingroup\$

There are two main components to the time it takes to process a draw call:

  1. The time it takes to calculate all the results for every work item in the batch

  2. The time it takes to upload info / switch states / ready the GPU to start working on it

As GPUs have gotten more powerful, most of the leaps and bounds have helped with point 1. They can now run hundreds of work items in parallel at blazing speeds, chewing through huge batches of geometry and shaded fragments.

They've gotten faster at point 2. as well, but the gains there are less dramatic because the work is often serial. If two draw calls issued back-to-back need different global state, in some cases the GPU has to wait for the previous call to finish completely before it can safely change that state and start working on the next call - even if it has hundreds of threads sitting idle in the meantime. This is called a pipeline flush.

So generally, we want to go wide as much as we can - give the GPU big jobs that can fill up all its threads, so it's never stuck waiting on work. Most games try to avoid unnecessary draw calls, and send their drawing instructions in big batches whenever they can.

So usually, one 100 k poly mesh is likely to render faster than 1000 100-poly meshes.

But wait, isn't that at odds with this advice?

"If you have a lot of objects in your scene, then each mesh must have low polygon count"

Lots of models (ie. lots of separate draw calls) each needing very little work (few polygons) is the opposite of what modern GPUs are good at!

But Unity knows that GPUs prefer to work on big batches (and also that manually combining models together into a batch is a pain for developers), so it has some strategies to handle this for you.

If the engine notices that you have a lot of objects that use the same material (ie. same shader, parameters, and other pipeline state), then it will try to pack them together into a batch to send to the GPU as though they were one big mesh. It can do this both statically - combining meshes marked "static" in your scenes when you build your game - and dynamically - analyzing your dynamic meshes each frame and packing them as best it can to save draw calls at runtime.

This can be expensive to do optimally, so Unity has some strict limits in how much geometry it will try to process this way. It will only try to batch an object if it has no more than 300 vertices, and no more than 900 vertex attributes in total (eg. vertex position, normal, uv)

So, this is what the advice is telling you about:

  • If you have a lot of models in your scene, the draw call switching costs can be high
  • You can reduce the number of draw calls by combining the models into batches
  • Unity will do this for you, but only if your models are quite small
\$\endgroup\$
3
  • \$\begingroup\$ Thanks. I added an image to my question above. It's a screenshot from the game Modern Combat 5 (for mobile). You can see that there are several buildings in the scene, along with some boxes, lamp posts, the weapons, and the human characters themselves. Now these are likely to have thousands and thousands of polygons and jugding by the looks, I'm guessing that they're all using different materials (hence a LOT of draw calls). How do you think a scene like this is being optimized? Why doesn't a game like this stutter when I play it on my phone? \$\endgroup\$
    – TheSaviour
    Dec 3, 2019 at 2:50
  • \$\begingroup\$ That's a different question, but for general advice: "There are no contradictions. If you perceive one, check your assumptions." Games like this will commonly use texture atlases to combine lots of building and set dressing textures into a single material so all these meshes can be rendered as a single batch. \$\endgroup\$
    – DMGregory
    Dec 3, 2019 at 2:54
  • \$\begingroup\$ @TheSaviour Keep in mind that even if you see "different objects" while playing the game, behind the scenes they are more likely merged together. This is always true for static items. So when you see two trees and a bench on top of the street, that are static, not animating, and not interactable in any way, it's almost guaranteed that they are merged together, and are being drawn in the same call. Merged meshes does not in any way mean the meshes touch each other. \$\endgroup\$
    – TomTsagk
    Dec 3, 2019 at 10:02
4
\$\begingroup\$

Using more meshes with fewer polygons per mesh can improve performance when it allows the renderer to cull more objects. Culling refers to skipping objects during the render process, which improves performance. There are two main reasons why an object might get culled:

  • View-frustum culling: Objects that are entirely outside the field of view of the camera may be culled.

  • Occlusion culling: Objects that are entirely occluded (obscured from view) by closer objects can be culled. This is a complex process and requires you to have configured the scene correctly.

When you use very large meshes in your scene, they are less likely to be culled. If any part of a mesh is visible, the engine ends up rendering the entire mesh. Using a large number of smaller meshes makes it easier for the engine to determine objects to cull, because it's easier for them to be occluded or out of the view frustum.

I worked on a racing game for mobile where the 3D artist had created the wall around a track as a single 200,000 polygon mesh. At least some part of the wall was visible from all angles everywhere on the track, which meant all 200,000k polygons were always getting rendered. We solved this by breaking the wall into many small pieces, so that only the pieces that were actually in front of the player needed to be rendered.

Finding the right balance (of more small meshes vs. fewer large meshes) is a mixture of art and science, and what works best will depend on the specific needs of your game.

\$\endgroup\$
1
\$\begingroup\$

Lets say initiating a draw call takes 1 millisecond. Now lets say that it takes 10 milliseconds to draw 100 polygons.

A single call to draw 100 polygons costs 11 milliseconds

11ms * 1000 = 11 seconds.

For 100k polygons:

100,000 / 100 = 1000 * 10ms = 10 seconds.

Add on the 1ms for initiating the draw call = 10.001 seconds.

Drawing the 100k polygons once is 0.999 seconds (just over 10%) faster.

This is because state switching (task set up) on the gpu has a non-zero cost, which must be factored in when computing how long it will take for a task to be completed.

This is true of all computation, regardless of hardware or task specification.

It is this truism that led to Amdahl's Law.

\$\endgroup\$

You must log in to answer this question.

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