Texture mapping takes a 2D surface image and maps it onto a 3D polygon.
This guide covers several texture optimizations that can help your games to run more smoothly and look better.
At the end of this guide you can Check your knowledge. You will have learned about subjects including texture atlases, mipmapping, and normal maps.
A texture atlas is an image that contains data from several smaller images that have been packed together. Instead of having one texture for one mesh, you have a larger texture that several meshes share.
A texture atlas can be created before making the asset, which means that the asset is UV unwrapped according to the texture atlas. This requires some early planning when creating the texture.
The texture atlas can also be created after the asset is finished by merging textures in painting software. However, this also means that the UV islands must be rearranged according to the texture.
A UV island is a connected group of polygons in a texture map.
The following image highlights several 3D objects using one texture set:
Texture atlasing enables batching for several static objects that share this texture atlas and the same material. Batching reduces the number of draw calls. Fewer draw calls results in better performance when the game is CPU-bound.
The Unity game engine has a feature that does batching when objects are marked static. This is done without having to manually merge the objects. Further information on this can be found on Unity's website.
Texture atlasing also requires fewer textures inside the game as they are packed together. With compression, this helps to keep the memory cost of textures down.
Texture filtering is a method that is used to improve the texture quality in a scene. Without texture filtering, artifacts like aliasing generally look worse.
Texture filtering makes textures look better and less blocky. Usually, this makes the game look better.
However, texture filtering can also degrade performance. This is because better quality often means that more processing is required. Texture filtering can account for up to half of the GPU energy consumption. Choosing simpler texture filters can reduce the energy demands of an application.
There are several options available in Unity for texture filtering:
- Nearest, or Point filtering
When seen up close, nearest filtering makes the texture looks blocky. This is the simplest and cheapest texture filtering.
- Bilinear filtering
The texture is blurrier up close with bilinear filtering. The four nearest texels are sampled and then averaged to color the main pixel. Unlike nearest filtering, bilinear filtering results in less blocky pixels as the pixels have a smooth gradient, as shown in the following image:
- Trilinear filtering
Trilinear filtering is like bilinear filtering, but with added blend between mipmap levels. Trilinear filtering removes noticeable changes between mipmaps by smoothing the transition between mipmaps, as shown in the following image:
Note: Bilinear and trilinear filtering requires sampling more pixels and, therefore, more computation.
- Anisotropic filtering
Anisotropic filtering makes textures look better when viewed at an angle. It is good for ground level textures, for example, as shown in the following image:
Texture filtering best practices
Arm recommends that you try the following texture filtering tips:
- Use bilinear filtering for a balance between performance and visual quality.
- Use trilinear filtering selectively. This is because trilinear filtering requires more memory bandwidth than bilinear filtering.
- Use bilinear and 2x anisotropic filtering instead of trilinear and 1x anisotropic. This is because this combination of filtering techniques can both look and perform better.
- Keep the anisotropic level low. Only use a level higher than two for critical game assets.
Mipmaps are copies of the original texture that are saved at lower resolutions. You can think of mipmapping like the equivalent of Level of Detail (LOD), but for textures.
Based on how much texture-space a fragment occupies, you can select an appropriate level for sampling. When an object is further from the camera, a lower resolution texture is applied. A higher resolution texture is applied when an object is closer the camera. The following image shows a mipmap collection containing the same texture at different resolutions:
Make sure that you use mipmapping. This is because mipmapping improves both performance and quality. Mipmapping improves the performance of the GPU. This is because the GPU does not need to render the full resolution texture on an object that is far away from the camera.
Mipmapping also reduces the texture aliasing and improves the final image quality. Texture aliasing causes a flickering effect on areas that are further from the camera. The following image shows texture aliasing on the same texture both with and without mipmapping:
Unity automatically creates mipmaps as they are needed on import. Unity then rescales textures that are not power of two. More information on this can be found on Unity's website.
Texture size, color space, and compression can all have an impact on the performance of your game.
Only create textures that are large enough to meet the required quality, but no bigger. It is also best practice to use a large-size texture atlas that contains several textures that are shared between many meshes.
Textures can be different sizes. Reducing the size of certain textures that require less detail helps to reduce bandwidth levels. For example, the diffuse texture can be set to 1024x1024 pixels and the roughness, or metallic, map to 512x512 pixels.
Selectively reduce the texture size and check whether any of the visuals have been degraded afterwards.
Texture color space
Most texturing software, for example Adobe Photoshop or Substance Painter, works and exports using the sRGB color space.
We recommend that you try the following:
- Use diffuse textures in the sRGB color space.
- Textures that are not processed as color must not be in the sRGB color space. Examples include metallic, roughness, and normal maps. This is because of the following reasons:
- Maps are used as data, and are not used as color.
- Using sRGB in these maps results in the wrong look, or visual, on the material.
Note: The sRGB (Color Texture) setting in the Inspector window for the texture must not be ticked for roughness, specular, normal maps, or similar.
The following screenshot shows you what happens when sRGB is incorrectly applied to such a texture:
Texture compression is an image compression that is applied to reduce texture data size, while minimizing the loss in visual quality. In development, we export textures using a common format, like TGA, or PNG. These formats are more convenient to use, and major image software programs support them.
These formats must not be used in final rendering. This is because they are slower to both access and sample, when compared to specialized image formats. For Android, there are several options, like Adaptive Scalable Texture Compression (ASTC), Ericsson Texture Compression (ETC) 1, or ETC2.
Texture compression best practices
We recommend that you use the ASTC technology that Arm created. Here several reasons to use ASTC:
- ASTC gives better quality with the same memory size as ETC.
- ASTC produces the same quality with a smaller memory size than ETC.
- ASTC takes longer to encode than ETC. This means that game packaging process can take more time. If this is an issue, then it is better to use ASTC on the final packaging of the game.
- ASTC allows for more control in terms of quality, because ASTC allows the block size to be set. While there is no single best default for block size, setting the block size to 5x5 or 6x6 pixels is a good starting point.
Sometimes, it might be faster to use ETC for development if you have to quickly deploy your game on a device. You can use ASTC with fast compression settings to get around the increase in deployment time. When encoding, there are options to trade off speed against both quality and size. For the final build, ASTC is the best option in terms of balance between visual quality and file size.
The game engine handles the texture compression when you package the game. However, you can choose which compression technique to use. You must choose the format to use, so it is difficult to skip this step.
The following screenshot shows how to select ASTC when building an Android package in Unity:
The following image shows both the difference in quality, and the respective file size between ETC and ASTC compression:
UV unwrapping is the process of creating a UV map. A UV map projects 2D textures onto the surface of a 3D model.
It is best practice to keep UV islands as straight as possible.
Note: A UV island is a connected group of polygons in a texture map.
The reasons for keeping UV islands as straight as possible are:
- It makes packing UV islands easier and less space is wasted.
- A straight UV helps to reduce the staircase effect happening on textures.
- On mobile platforms, texture space is limited. This is because the texture size is usually smaller on a mobile platform than on a games console or a PC. Good UV packing ensures that you get the best resolution from your texture.
- You might consider having a slightly distorted UV by keeping the UV straight, in order to have better quality texture overall.
Place UV seams in places that make them not too visible, for visual quality purposes. This is because the texture seam can look bad on a model. Therefore, split UV islands where the edges are sharp and have a small space between the UV islands. This helps later on to create better normal maps through the baking process.
The following image shows an example of how to use UV unwrapping to maximize texture space:
The detail you include when creating textures should be proportionate to the visual impact of that detail. Make sure that you only create details that can be seen.
Phone screens are small, therefore fine-grained detail is not visible. Take this into account when creating textures. For example, you do not need a 4K texture with lots of details for a chair that is barely visible in the corner of the room.
The following screenshot shows an example. The small 256x256 pixel texture on the left has a low level of detail to be used on soldier characters. The larger image on the right shows the entire scene, and shows that the low level of texture detail is sufficient for the required amount of detail:
In certain cases, you need to exaggerate and highlight edges and shading to improve shape readability. Because mobile platforms generally use smaller textures, it might be hard to capture all of the detail that is needed within this small texture.
Use fewer textures and bake in any extra details into one texture. This is important because:
- Phone screens are small, and some details are better to be baked onto the diffuse texture itself to make sure that those details are visible.
- Elements like ambient occlusion and small specular highlights can be baked in and then added to the diffuse texture.
This approach means that you do not have to rely too much on shader and engine features to get specular and ambient occlusion.
The following screenshot shows an example of details that have been baked into a texture, for example the highlights on roof tiles:
When possible, use grayscale textures that allow color tinting in the shader. This saves texture memory at the cost of creating a custom shader to perform the tinting.
Be selective with this technique, because not all objects look good using this method. It is easier to apply this technique to an object that has a uniform, or similar, color.
You can also use RGB masks and then apply textures that are based on the color range of the mask to achieve this effect.
The following image shows an example of a grayscale texture that is being applied to a tinted pillar:
Use texture channels to pack multiple textures into one.
Arm recommends texture channel packing because packing helps to save texture memory. Packing saves memory because you can get three maps into one texture using this technique. This means that fewer texture samplers are required.
This texture packing technique is commonly used to pack roughness, or smoothness, and metallic into one texture. But it can be applied for any texture mask.
Use the green channel to store the more important mask. The green channel usually has more bits. This is because our eyes are more sensitive to green and less sensitive to blue.
The following image shows an example of texture packing:
Further reading on the color sensitive cones can be found here.
In this section of the guide, we look at some more best practices that you can try if you want to use alpha channels in your game.
Be selective when adding an alpha channel to a texture. Adding transparency often makes the texture larger in file size because images are converted to a 32-bit format, increasing the overall bandwidth use.
Another way to store an alpha channel is by using the extra channel in roughness, or metallic textures. In Unity, this texture sometimes uses two channels out of three, roughness (G) and metallic (B), leaving the (R) channel free for you to use.
By using the free channel to store the alpha mask, you can keep the diffuse texture at 16-bit, halving the file size. An ambient occlusion map usually can be baked in the diffuse map.
The following image shows an example of how you could store an opacity map in the red channel:
A normal map is a good way to make a 3D object appear to have more detail. Normal mapping is best used to add smaller details like wrinkles, bolts, and other details that need lots of triangles to model.
The usage of normal mapping can depend on the type and art direction of a game.
In most of our internal projects, we use normal mapping with no noticeable degradation in performance. Because we target high-end devices for most of our demos, low-end devices might have different results.
Using normal mapping does come with a cost, even if the cost is small. Remember:
- A normal map is an extra texture. This means more texture fetches, which results in more bandwidth being used.
- Use normal maps sparingly when targeting lower-end devices.
However, performance can be improved if geometry has fewer triangles as a result of normal map use.
The following image shows an example of how you could use a normal map and textures for the smaller details:
Using a cage is a great way to get a high-quality normal map, regardless of the surface that you are baking.
Most normal mapping software can make your cage automatically. However, you can make one from copying your low polygon model and then increasing its scale slightly.
The purpose of using the cage is for the program to change the direction that is used to calculate the normal when baking. This produces far better results on split-normal and hard edges, as you can see in the following image:
The cage is basically a larger, or pushed out, version of a low polygon count model. Encompass the high polygon count model for the baking to work well.
A mesh cage is used to limit the ray cast distance that is used during normal map baking. A cage can also solve problems with split-normal seams on the normal map, as demonstrated in the following image:
If the baking software supports it, bake by matching the mesh names to mitigate the problem of creating a wrong normal map projection. When objects are too close to each other, they can unexpectedly project the normal map onto the wrong face. Matching the mesh names ensures that baking is only done on the right surface, with a matching name.
A separate bake for ambient occlusion can sometimes be required with this solution. This means that you should split UVs on hard edges, because a continuous UV on hard edges causes visible seams. The general rule is to keep the angle less than 90 degrees, or set it as a different smoothing group. Coincide UV seams with different smoothing groups on the triangles.
The following image shows an example of how a break UV looks on hard edges:
You can customize texture settings to provide fine-grained control over how Unity uses a texture.
Unity has made editing texture settings easy. Select the texture that you want edit, then the Inspector window displays the texture settings.
Here are some tips for when you are editing texture settings:
- Texture Type lets you control the type of texture, causing the texture to be used in different ways within the engine.
- Texture Shape allows you to choose cubemaps instead of 2D for some texture types.
- Depending on the Texture Type that is selected, there are two sections that appear:
- Texture Settings has specific controls for the texture to work as you need.
- Advanced Settings covers: sRGB, Alpha Source, Alpha Is Transparent, Read/Write Enabled, and Generate Mip Maps.
- Wrap Mode controls how the texture wraps your UVs. Here are the options that are available:
- Repeat tiles the texture, and is used for repeating patterns.
- Clamp locks the texture to the last pixel at the edge.
- Mirror works like Repeat, but mirrors every other repeat of the texture.
- Mirror Once mirrors the texture a single time, before locking the texture to the edge pixels.
- Filter Mode controls what texture filter is used on the texture.
- The Texture compression box gives you control over: Max Size, Resize Algorithm, Format, Compression, and Use Crunch Compression.
Here are some resources related to material in this guide:
This guide covers texture optimizations that can help your games to run more smoothly and look better.
You can continue learning about best practices for game artists and how to get the best out of your game on mobile by reading the other guides in our series: