Relatively small textures are huge in a build

I was noticing that the size of my build is already quite large, despite not having all that much in it. It turns out a whole bunch of my textures are being blown up to exactly 5.3 mb during the build process. Here’s some entries from the build log:

 Used Assets and files from the Resources folder, sorted by uncompressed size:
 5.3 mb    0.7% Assets/Models/Doors/AutomaticDoor2/Textures/AutomaticDoor2_AutomaticDoor_MetallicSmoothness.png
 5.3 mb    0.7% Assets/Models/Doors/AutomaticDoor2/Textures/AutomaticDoor2_AutomaticDoor_Normal.png

Yet, on disk, these texture are nowhere near that size:

I have about 50 other relatively small textures all getting blown up to 5.3 mb during the build. They’re all 2K textures, and I haven’t done anything to them after importing them into Unity:

3524751--282175--upload_2018-6-7_14-35-24.png

Could someone explain what’s going on here?

-Dan

3 Likes

The texture files you import to Unity are not the same format as what the game needs. GPUs can’t decode PNG files*, it’s not a format the hardware knows what to do with. So when you import a texture into Unity it converts it into a format that GPUs do know how to use.

In this case your MetallicSmoothness texture is an RGBA texture. For desktop computers and most consoles the default GPU native texture format for that is going to be a DXT5 texture which has a 4:1 compression ratio over an uncompressed image. A 2048x2048 DXT5 is roughly 4 mb. Mip maps increase the memory usage by ~33%, which brings you to 5.3 mb.

The reason for this is most image formats people are familiar with today are designed for the web. Many of the compression formats designed for web use employ run length compression or other variable compression ratio techniques, that is to say the size of the file changes with the contents of the image. This is great for web use because it means much better compression is possible, but has the side effect of you cannot know where in the image file’s data you need to read to know the color of a specific pixel. Instead you have to decompress the entire image to use it, or at the very least spend time searching through the image to figure out where the data is. So that means to use a PNG (or JPEG, or GIF, or WebP) image you have to decode the entire image into an uncompressed format before displaying it.

GPU formats have fixed compression ratios. No matter if the image is random noise, or a solid color, or anything in between, a texture is the same size in memory. The benefit is if you need to know the color of X pixel you know exactly where the data for that is. Why is that useful? When you’re rendering an object with a texture on it, only a small portion of that texture may be visible. Additionally, each pixel on screen needs to know what color a particular texel is. In the PNG use case the GPU would have to decompress the entire image into memory then pass the small bit of uncompressed data to each of those screen pixels. With fixed compression ratio image since you know where the data is you only need to pass the chunk of data needed to each on screen pixel. This means the texture can remain compressed in memory and the GPU doesn’t need to keep two copies of it.

  • You can use GPUs to decode PNGs, or really any image format. Indeed many web browsers use GPU accelerated image decoding. The difference is GPUs don’t have built in hardware for decoding them, where as they do for DXT5 and several other GPU specific image formats.
10 Likes

As a side note, since Unity does not use source image files in builds, this means that there’s no reason to go out of your way to keep small textures in the project. Forget PNG, go nuts with multi-layer PSD files. (Also, that way you can be happy that textures in builds are way smaller than they are in the project.)

Also, do those textures really need to be 2K? Just for a door? Change the import size in Unity to 1K or even 512 and see if it actually looks worse.

–Eric

6 Likes

Thanks for the detailed write-up. That makes sense, and it doesn’t sound like there’s anything I can do about it in that case.

Probably not, in the long run. At this point I’m texturing my models using Substance Painter, and exporting everything at 2K for now. But I’ve run everything at 512x512 as a proof of concept, and it pretty much looks fine that way too. So at some point I’ll make a pass of all my textures and determine which really don’t need to be 2K.

Yep, PNG files are kind of terrible for real time textures. The format itself is fine, but since most art tools assume the end goal is for web use they can do bad things when writing and reading them, specifically when the alpha channel is involved. Most paint programs will clear color data from areas a 100% transparency, setting it to solid black or some other pattern that optimizes compression. If you’ve got a metallic smoothness texture with a lot of areas of 0% smoothness, those areas may loose the metallness data. Opening that image up in something like Photoshop or Gimp will also delete all of that color data even if it was saved properly as it’ll open it as a layer with transparency rather than a separate channel, and again it’ll assume the color data isn’t useful in the fully transparent areas and delete it.

Substance is fine and will handle saving / opening PNGs properly for this use case, but I prefer using PSD or TGA files instead.

1 Like

what about reading speed for android target for non-transparent textures? that’s important because my goal is for android. what is faster between psd and jpg?
@bgolus
@Eric5h5

If you read the posts in this thread, they’ve already explained that it makes no difference. Unity will automatically convert the file to what it needs. There will be no actual PSD or JPEG in your build.