2
\$\begingroup\$

I've realized that I don't have a clear understanding of some of the technical specifics of overdraw in Unity. I often work on mobile, where overdraw is much more of a concern than it would be on a computer or console.

I understand the basics: Overdraw occurs when pixels on the screen are drawn multiple times; this consumes more GPU time and causes the frame to take longer to render. The more pixels on-screen that are suffering from overdraw, and the more times those pixels are redrawn, the greater the performance impact. One of the primary causes of overdraw is transparent sprites, particularly with overlapping particle effects. Post-processing effects also cause overdraw.

The Unity team doesn't provide a lot of information about overdraw in their documentation, and the visualization mode in the Editor does not accurately reflect what happens on the GPU. There are a handful of articles from third parties, but they rely on clumsy real-world analogies and are frustratingly vague on the technical specifics.

Question 1

Say that we have two partially overlapping sprites that are fully opaque (no alpha channel):

Partially overlapping fully opaque sprites

Here, the yellow sprite is partially obscured by the red sprite.

I can think of two possible outcomes:

1.a) Because the yellow sprite is partially visible, the engine draws the whole sprite, then draws the red sprite on top of it. Overdraw occurs where the two sprites overlap.

1.a) The engine is able to determine that some of the yellow square is fully obscured by the red square, and only draws the pixels that aren't obscured. No overdraw occurs.

Which is the case here? Would the behavior be the same if the two squares were rendered by Mesh Renderer with a fully opaque material? I'm assuming it would, since sprites are internally rendered with polygons just like a regular mesh.

Question 2

Say we have a partially transparent sprite overlapping another sprite:

Overlapping sprites with partial transparency

Here, the blue sprite has an alpha channel. The edges are feathered and partially transparent, while the pixels in the inner portion are fully opaque (alpha of 1).

I can think of three possible outcomes:

2.a) Because the green sprite is partially visible, the engine draws the whole green sprite, then draws the blue sprite on top of it. Overdraw occurs where the two sprites overlap.

2.b) Because the blue sprite is partially transparent, the engine must treat the whole sprite as transparent (even the fully opaque pixels). The engine draws the whole green sprite, then draws the blue sprite on top of it. Overdraw occurs where the two sprites overlap.

2.c) The engine is able to determine that some of the pixels of the blue sprite are fully opaque, and not draw the green sprite underneath those pixels. Overdraw occurs, but only where the feathered edges of the blue sprite overlap the green sprite.

Which is the case here?

\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

The question to ask when measuring overdraw is "what would prevent a pixel from getting drawn to a second time?"

The only part of the pipeline that does this kind of per-pixel clipping is the depth & stencil buffer testing.

Stencil tests are only active when you ask for them, so unless you've deliberately written shaders to set and check stencil values, assume by default the stencil buffer is not doing anything for you.

Depth tests can reject a fragment either before the fragment shader has run (early z rejection - available only if you don't write modified depth values from your fragment shader) or between the fragment shader output and blending. To do this, there needs to already be a depth value in the depth buffer at that fragment's screen position, closer to the camera than the fragment you're trying to render (or father, or no farther, etc., depending on how you've configured the depth test). So:

  • the closer polygon must have rendered before the farther polygon (ie. in an earlier queue, earlier in the depth sorting order, or just earlier by chance within a single batch)
  • the closer polygon must have used a material that writes to the depth buffer
  • the farther polygon must use a material that tests against the depth buffer

Materials that include the possibility of transparency/translucency, even if you're only drawing with 100% opaque alpha, typically do not write to the depth buffer, to prevent faded parts of the object from "punching holes" in content that renders behind them later.

That includes the default sprite material. So if you're using sprites, and have not made a custom "fully opaque sprite" shader that writes to the depth buffer, then both of these cases will cause maximum overdraw: every overlapped pixel will get drawn to twice.

If the red diamond in this image is using an opaque or alpha-test material (such as the default lit or unlit color materials on a MeshRenderer), then there's a chance it will prevent overdraw in the region where it overlaps the yellow square:

Red diamond overlapping yellow square

If they're both rendered in the same opaque queue ("Geometry" or "AlphaTest"), then it's up to which batch gets submitted to the GPU first, or which polygon gets processed first if they're both in a single batch. But if the yellow square is in a later queue (like "Trasparent"), then you can be sure the red diamond will block overdraw.

This is part of why the engine draws opaque objects first, to minimize the cost of transparent content by clipping as much of it as possible.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for this comprehensive answer! \$\endgroup\$
    – Kevin
    Commented Oct 28, 2022 at 17:27

You must log in to answer this question.

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