0
\$\begingroup\$

My game uses 2D sprites in a 3D environment, and I'm really struggling with my sprites' quality.

I have high resolution vector art with clean linework created for my sprite. I'm exporting it as a .PNG for Unity, but no matter what anti-aliasing, filter, or MipMap options I select, the quality is much worse in Unity when the camera is zoomed out. :(

The quality looks fine up close. Turning on MipMaps for the sprite does an 'OK' job at reducing the jaggedness, but I really hate how blurry and dull it makes it look...

My game is in 1920 x 1080 format and the character is designed similar to this Mario example (also from a 1920 x 1080 format). So, it "should" be possible to make it look this good:

Mario sprite

I've tried reviewing as much information on this as I can, but I can't seem to find a solution. Any kind of guidance or resolution would mean the world to me. It's probably worth mentioning that I am using URP with anti-aliasing also on in the settings. If I want my characters quality to look as good as the Mario example above, what specifically do I need to do?

\$\endgroup\$
1
  • \$\begingroup\$ I'm not exactly sure how this would be done with your pipeline, but texture formats like DDS let you define the mipmap levels yourself in the same file. Presumably, once you load such a texture, Unity is smart enough to use them instead of generating them for you. \$\endgroup\$
    – Vaillancourt
    Sep 18, 2022 at 13:05

1 Answer 1

4
\$\begingroup\$

First, make sure your sprite/texture import format is set to RGBA 32-bit. Any other import settings apply some compression algorithms to save video memory/loading time, at the cost of image quality. It's usually a good trade-off for natural-looking noisy textures, but for crisp vector art or pixel graphics, the compression artifacts can be quite noticeable.

Next, if you're using Unity URP with default settings, you may also want to disable the FXAA anti-aliasing setting on the camera. This antialias pass is a good choice for 3D scenes with a lot of geometry aliasing, but if you're using sprites that won't be a problem, and the FXAA algorithm can be a bit over-eager about blurring crisp edges it sees in sprites.

PayasoPrince and I connected over Discord to troubleshoot this issue, and in this case it turned out that the two points above were responsible for almost all of the quality impacts. Once we imported the sprite uncompressed and without post-process AA, the default mipmap generation and filtering was enough to get quite good quality - even pixel-perfect to the source when looking at the highest-resolution mip with the right camera settings.

I wrote the rest of this answer before thinking of compression or AA filters as a culprit, so it goes into the weeds of how to get full control over your mipmaps. That turned out to be overkill for this case, but I'll leave it up in case it's helpful to others - just make sure you try the quick wins above first before going all the way down this rabbit hole!


Some background on why this happens:

Without mipmaps, as you shrink your texture down, the tiny fringe of half-coloured texels giving you a nice anti-aliased border between two coloured regions shrinks to almost nothing. The chance that any particular pixel's texture sample lands right on that razor edge goes toward zero, and so most pixels end up looking up their texture from one side or the other, 100% the colour on the left or 100% the colour on the right, resulting in that harsh stairstep transition with no blending.

Auto-generated mipmaps help with this by pre-generating smaller versions of the asset that are pre-blended for fast lookups (and better cache coherency which is good for performance). But when they only have a raster image to go off of, they have to make guesses about the true continuous colour field they're supposed to be approximating. Usually they'll use a simple box filter or some slightly more sophisticated ways of averaging the colours already baked into your raster. That averaging can smear out your borders more than a true supersample of the source vector would, contributing to the slight fuzziness you see. Unity also offers a Kaiser filter which is somewhat sharper - that might be enough, depending on your images.

You can take full control of this process by making your own mipmaps directly from your vector source. Your vector editing software is able to apply supersampling to the ground truth continuous image to get a very high-quality downsample, which anything working from just the raster can only approximate.

So instead of exporting just a single high-res sprite, you can export a whole sequence, each half the size of the previous. You can combine these images into a mipmap chain in DDS texture format — there are plugins for Photoshop and other image tools to help with this, or you can use DirectX utilities to do it manually.

Importing this into Unity, it will use your mipmaps instead of guessing at its own with recursive filters. Then anytime your sprite is displayed at exactly one of the resolutions you exported, and aligned with the pixel grid of the screen, you'll get crisp edges just like your direct vector output - pixel for pixel exact. So for best quality, try to export your sprite at a resolution such that the typical sizes it appears in your game each closely match the resolution of one of the source images in the mip chain. You can even swap between different versions of the sprites for closeup and far shots, if the sizes you need don't align well with a power of 2 relationship.

But in-between those sizes you can still run into trouble. By default, using trilinear filtering, the GPU will average two nearby mip levels to estimate the right colour for each pixel at an in-between size. This can again introduce a little fuzziness, both from the averaging, and because one of those levels is too low-res/blurred for the size you're drawing at. You can force the filtering to bilinear / choose just the nearest mipmap to sample from, which will introduce a little upscaling fuzz when scaled up and a little aliasing when scaled down from the closest native size you saved — and a visible pop during scaling as it switches sources discretely. Or you can apply a mipmapping bias to shift the lookup a little more strongly toward the more detailed mip, trading a little extra aliasing for crispness, while keeping the transitions smooth and pop-free.

\$\endgroup\$
2
  • \$\begingroup\$ I just want to make sure I understand the workflow: In the native vector software, Export multiple rasterized resolutions all half the size of the previous > Combine these images into a mipmap chain in DDS texture format > Import into Unity and decide between the available filtering options, is that right? I was under the impression that sprites do not follow the power of 2 relationship, am I following this because they are now DDS textures? \$\endgroup\$ Sep 19, 2022 at 18:13
  • \$\begingroup\$ Workflow sounds right. Sprites do not have to be power-of-2 sizes, but mipmapped textures generally do. In particular, each mipmap level needs to be half the size of the level before, making a power-of-2 relationship between mips, even if the largest texture is not power-of-2 sized. Under the hood, Unity can atlas together multiple non-PoT sprites into a single PoT texture. Making your own manual mips might prevent this — I haven't checked — so you might need to set up your own atlas manually, using the spare space left over from one non-PoT sprite to fit others into the same texture. \$\endgroup\$
    – DMGregory
    Sep 19, 2022 at 19:00

You must log in to answer this question.

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