What is texture packing, and why is important in video game development?

Think of a scene from the last video game you played. How many objects were in the scene? If you were playing an FPS game set in World War II, there would be gun textures, environment textures, and textures used for the objects in that scene. To create visually pleasing results, we use numerous textures for every material we create. Each material is packed with albedo, roughness, cavity, metal, normal, displacement, and in some cases, opacity, among others. Additionally, imagine that you use additional roughness maps to generate imperfections. Texture count can quickly pile up, and even though we are living in the golden age of PC hardware, this many textures are still too demanding for our home PCs. So, to avoid this texture overload, we found a way to combine multiple textures and squeeze them into one texture. For example, instead of using four separate textures for one material (Normal, AO, Cavity, Displacement), we pack them into one texture and use that texture only to obtain the values we need. But how do we pack textures? That’s what I am going to talk about in this tutorial.

Note: If you are tired of watching video tutorials and would like to see more text-based tutorials like this one from me, you can support me on Patreon so I can dedicate more time to creating them.

How to pack textures in Photoshop?

Here, I have downloaded a brick texture from Quixel that has the ID number “brick_rough_xertbj1” if you’d like to follow along. In the folder I downloaded, we see that Quixel provided us with the following textures:

  • Albedo
  • AO
  • Cavity (I’ll generate a specular map from my cavity map. I’ve listed the reasons why I prefer the cavity map and generating specular from it instead of using the specular map directly in the second part of this tutorial where we discuss how we can unpack these textures in UE5.)
  • Displacement
  • Normal
  • Roughness

Note: You can navigate to Bridge’s settings and specify which additional texture files you wish to download. By default, cavity textures are not included.

Note: If you are using a bump map, one would typically pack a bump map as a separate texture from other maps since this allows for better organization and management of textures within the material workflow.

If you’re ready, let’s begin!

  • 1. Open your albedo map in Photoshop.
  • 2. Go to the channels tab and click the + button at the bottom right of the screen to create a new alpha channel.
Lazy Loaded Image

Switch to the ‘Channels’ panel.

Lazy Loaded Image

Click on the ‘+’ button to create a new alpha channel.

Take your roughness map and paste it into the alpha channel we just created.

Note: Dragging and dropping the texture directly from a folder to the channel won’t work. You need to open the texture in Photoshop, select it there, and paste it.

Lazy Loaded Image

Our texture is ready.

And we’re already done with our first texture! In the RGB channel, we store our albedo values, while the alpha channel contains the roughness info. Now, we can move on to the next step!

4. Take your normal map, go to the channels, and create an alpha again.

Lazy Loaded Image

Bring your normal texture into Photoshop.

Lazy Loaded Image

Create a new alpha channel.

5. Take your height/displacement map and paste it into the alpha channel.

Lazy Loaded Image

Take your height/displacement map and paste it into the alpha channel.

Now, before we move on, we need to combine our ambient occlusion (AO) and cavity maps to paste them into the blue channel of our normal texture.

6. Open your AO map and cavity texture in Photoshop. Place your cavity texture on top of the AO texture as a new layer. The cavity layer should be above the AO layer.

Lazy Loaded Image

The cavity layer should be above the AO layer.

7. Set the blend mode to multiply.

Lazy Loaded Image

8. Select your cavity and ambient occlusion maps in the layer panel, and merge them into one texture.

Lazy Loaded Image

Select your cavity and ambient occlusion maps in the layer panel.

Lazy Loaded Image

Your advertisement can be placed here for $200 permanently. Suggested resolutions: 1800x663 or 1400x1400. Contact us.

Lazy Loaded Image

Merge them into one texture.

Now we have an ambient occlusion map multiplied by the cavity map.

Lazy Loaded Image

9. Now, take this newly created image and paste it into the blue channel of the normal map.

Lazy Loaded Image

Now, the red and green channels of this texture contain normal values, while the blue channel contains ambient occlusion and cavity together. The alpha channel contains bump/displacement.

Finally, save your texture as a 32-bit TGA (Targa) file.

Lazy Loaded Image

Ensure that the alpha channel option at the bottom of the saving window is checked so that it preserves the alpha channel.

Lazy Loaded Image

And that’s it! If you’d also like to learn how to unpack your textures in Unreal, you can check out part two of this tutorial.

Note: Please note that if your mesh is metallic and you have a metallic texture available, you may consider using the ORM approach where you take your AO, roughness and metallic textures, stack them on top of each other and use isolate the following channels to store their values:

  • Occlusion (Red Channel)
  • Roughness (Green Channel)
  • Metallic (Blue Channel)

But I don’t recommend that approach. Rather than using this method, simply input the metallic value (0–1) directly into the master shader (If you are not using an atlas, of course.). There’s no “in-between” state for the metallic property of a material, so inside of Unreal, the ORM approach looks less appealing to me. However, it is also understandable to use it this approach if one needs it in their workflow, or wants to have flexibility for variation, artistic control or faces with shader compatibility issues in other engines. Other commonly used packing methods include:

  • SORM (Specular, Occlusion, Roughness, Metallic)
  • ROM (Roughness, Occlusion, Metallic)
  • Etc.