I have been determined to squeeze as much performance out of Unity as possible so I just wanted to share my nightmare journey with texture maps so some other poor soul might not experience the same issues that I have so far.
Why Use Texture Atlases?
Why not? Anything that can be used to increase performance is worth it, right? Well maybe not. Whilst there are many benefits to using multiple atlases (less draw calls, less changes to the render state, no calls to get/bind textures to objects, etc)
Why You Might Not Use Texture Atlases
-
You can’t tile textures.
-
You need a good idea of the scene/section your are building to minimise wasted UV space (I’ve watched a lot of AAA talks and read a lot of papers that seem to hold a standard of 70 - 85% (so only 15% - 30% space wastage).
-
Authoring new gameobjects can be a hassle due to the requirement to expand your texture atlases.
-
Basic stuff just doesn’t work anymore. Normal maps? needs custom work every time. Terrible mip map island bleeding throughout the mipmap chain.
-
You can’t have a metal texture and a wood texture on the same atlas if you want decent results as they react differently to light and require different material settings.
-
Edge cases… ie - is it worth even putting on a texture atlas? (a single box that appears once in an entire level) etc.
-
You need to be very specific about your texel/pixel ratio for assets/textures which can be laborious and time-consuming.
Issue #1: Mysterious Seams
A common issue I experienced is seams showing up on “seamless” textures in the scene view. This was quite confusing as I was 100% sure the UV mapping was correct in Blender (using snap to corner).
The solution to this is quite simple. In the inspector, “Alpha is Transparency” must be enabled for the texture.
Ok, great. No more seams! Let me just zoom out and get a better view.
Issue #2: You Got Your Concrete Texture In My Wood Texture!
This was another issue for me that took a while to reach the solution. The problem is mip maps and how they work (or specifically how Unity implements them).
The texture atlas is 4096 x 4096. This means there should be 11(?) mipmaps in the chain. You will never be able to stop bleeding at certain levels since mipmaps go all the way to 1x1. However, I think the “important” chain sections are the next two iterations from the “main” texture.
So, for a 4k atlas the only concern is the mipmap quality at 2k and 1k (I’m sure there is a reason to care about even lower tiers but I think two levels down is a good start).
How do I figure out how much space to use between islands? I spent a lot of time researching this issue and ended up watching a lot of GDC talks by AAA studios, and also read some papers on texture atlases which led me to a paper that used a very specific (yet simple) equation.
Texture Atlas Size / 215 = Island Space
4096 x 4096 = 19 pixels between each island
2048 x 2048 = 9 pixels between each island
(Note: This was specifically in reference to a texel density/pixel ratio of 512 pixels/meter. I will talk about this further down the post).
Issue #3 Texel Density/Pixel Ratio
For a long time I was used to just scaling a texture on an object until it “looked right” because it was practical and simple. Trying to transition to using an atlas presented a new challenge… mainly, how do I know what size textures to use?
The solution to this is to designate a specific density/ratio and then stick with it. For example, 512 pixels per meter. This would mean a 2m x 3m door would require a texture that is 1024 x 1536. For a 2m x 2m floor you would need a 1024 x 1024 texture.
This works better for modular sets. I’m still unsure how you would go about using an atlas for a huge object (like a ship that is 100 meters long) but not modular.
The logical conclusion would be that you can fit 16 floor textures onto a single atlas. However, that is not correct. You need 19 pixels space each side (95 pixels per row), which means you only have 4001 pixels to work with… not 4096.
Just because you have less space doesn’t mean you can’t get value for money. What I ended up doing was, for each 1024 x 1024 seamless texture, was to just resize the image to 1000 x 1000 pixels. This results in still being able to fit 16 textures within a single atlas whilst providing adequate space between islands.
Issue #4: Normal Maps & Ambient Occlusion Maps
A huge issue for texture atlases (specifically when using tilable/seamless stuff) is most generated normal maps will cause the seams to show. Why does this happen?
In my experience what happens is the normal map is never flat around the edges of a texture. This is fine for non-repeating objects (tables, a cabinet, a door) but looks terrible for modular assets. How to fix it? I have not found any method quicker than manually modifying the normal map and AO map to essentially set the weight to 0 around the edges, essentially skipping the normal map process for all seams/edges.
Final Result (using a different texture atlas than the one posted above)
I would be happy for anyone to point out something I may have missed or contribute as to how it’s possible to get better results using atlases and how they fixed the problems I mention above.
Hopefully this might help someone else as it has been quite a headache to get to this stage.