Before we start creating a texture atlas, let's first find out what this technique is actually good for. A texture atlas (also known as a sprite sheet) is just an ordinary image file that can be rendered to the screen like any other image. So what makes it so special? It is used as a container image that holds several smaller subimages arranged in such a way that they do not overlap each other and it still fits into the size of the texture atlas. This way, we can greatly reduce the amount of textures that are sent to the graphics processor, which will significantly improve the overall render performance. The texture atlases are especially useful for games where a lot of small and different images are rendered at once. The reason for this is that switching between different textures is a very costly process. Each time you change textures while rendering, new data needs to be sent to the video memory. If you use the same texture for everything, this can be avoided.
The texture atlases will not only increase the frame rate of the game significantly, but will also allow us to use subimages as Non-Power-Of-Two (NPOT) textures. The reason why our subimages can be of arbitrary size is that the power-of-two rule only applies to textures that are loaded into the video memory. Therefore, when we actually render a subimage, we are still using the texture atlas, which is a power-of-two texture as our pixel source; however, we will only use a certain part of it as our final texture to draw something.
Due to the default support of OpenGL ES 2.0, LibGDX will support NPOT textures or images; however, it will take more time to render than a POT texture, depending on the underlying hardware. Nevertheless, it is more efficient to store the subimages in a texture atlas, which is treated as a single unit by the graphics hardware. Also, it can be faster to bind one large texture once than to bind many smaller images.
Take a look at the following screenshot that shows all the images of our game objects in separate image files:
You might wonder why the cloud and mountain images in the preceding screenshot are filled with plain white color. This is because the images contain only white and transparent pixels, so it is indeed hard or rather impossible to make out the actual image information on a white background, as it usually appears in print media. Therefore, all the images of our game objects that follow will be shown with an added gray background to rectify this small display issue. However, the actual image files still remain unchanged, as shown in the preceding screenshot.
LibGDX has a built-in TexturePacker that we will use to automate the process of creating and refreshing the texture atlas for Canyon Bunny. We will put all the game's object images shown in the preceding screenshot into the atlas to get the following result:
The images have been nicely arranged on the atlas without any overlap. The purple border around each image is a debugging feature of LibGDX's TexturePacker that can be toggled on and off. It can be used to visualize the true size of your subimages, which otherwise can be difficult to see whether the subimages use transparency. Good examples of these are the cloud and mountain images in our texture atlas. Also, when padding is enabled, which is the default by using two pixels for each direction, you would barely see the difference without the enabled debugging lines.
Padding your images inside a texture atlas helps you avoid an issue called texture bleeding (also known as pixel bleeding) while texture filtering and/or mipmapping are enabled.
The texture filter mode can be set to smooth pixels of a texture. This is basically done by looking for the pixel information that is next to the current pixel that is to be smoothened. The problem here is that if there is a pixel of a neighboring subimage, its pixels can also be taken into account, which results in an unwanted effect of pixels bleeding from one subimage to another.
For the TexturePacker, the following preparations need to be done beforehand as it is a so-called extension to LibGDX that is not a part of the core functionality:
C:libgdx
and extract extensions/gdx-tools.jar
from the libgdx-1.2.0.zip
file you downloaded earlier in Chapter 1, Introduction to LibGDX and Project Setup.gdx-tools.jar
file in the CanyonBunny-desktop/libs
subfolder. Next, the extension has to be added to Build Path in Eclipse.CanyonBunny-desktop
project and navigate to Build Path | Configure Build Path | Libraries.CanyonBunny-desktop
project and expand it until you reach the libs
subfolder.gdx-tools.jar
extension and confirm each opened window by clicking on their OK buttons.For Gradle users, adding gdx-tools
is easy; we just need to add the following highlighted line to the build.gradle
file in C:/libgdx
:
project(":desktop") { … compile "com.badlogic.gdx:gdx-tools:$gdxVersion"
Make sure that you are editing under the section project(":desktop")
. After editing, we need to refresh our dependencies. To do this, right-click on the CanyonBunny-desktop
project and go to the Refresh All option in the Gradle menu. Make sure that you are connected to the Internet because Eclipse will download the relevant dependencies.
We will now add the code to automate the generation process of the texture atlas. Perform the following steps:
assets-raw
under CanyonBunny-desktop
. Also, add a subfolder named assets-raw/images
. This is where we put our image files to be included in the texture atlas.CanyonBunny-desktop
and add the following two lines of code to import the TexturePacker
and its Settings
class:import com.badlogic.gdx.tools.texturepacker.TexturePacker; import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings;
Main.java
in the CanyonBunny-desktop
project:public class Main { private static boolean rebuildAtlas = true; private static boolean drawDebugOutline = true; public static void main(String[] args) { if (rebuildAtlas) { Settings settings = new Settings(); settings.maxWidth = 1024; settings.maxHeight = 1024; settings.duplicatePadding = false; settings.debug = drawDebugOutline; TexturePacker.process(settings, "assets-raw/images", "../CanyonBunny-android/assets/images", "canyonbunny.pack"); } LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); cfg.title = "CanyonBunny"; cfg.width = 800; cfg.height = 480; new LwjglApplication(new CanyonBunnyMain(), cfg); } }
The added code provides a convenient way to rebuild the texture atlas every time the game is run on the desktop. The rebuildAtlas
variable controls whether the atlas is rebuilt on startup or not by setting it to true
or false
. Using the TexturePacker
class to create the texture atlas is pretty straightforward. It contains a static method called process()
that takes an optional settings
object to configure the way the texture atlas will be generated as well as the three parameters that are mandatory. The first mandatory parameter is the source folder that contains our image files. The second one is the destination folder where the generated texture atlas should be created. Finally, the third parameter is the name of the description file that is needed to load and use the texture atlas.
The source folder (in our example, assets-raw/images
) is specified relative to the desktop
project as the TexturePacker code is executed from here. The destination folder (in our example, ../CanyonBunny-android/assets/images
) is also specified relative to the desktop
project. However, the resulting texture atlas has to be put into the assets
folder of the Android project so that it becomes available to all platform-specific projects. The description file (in our example, canyonbunny.pack
) will be created by TexturePacker
and will contain all the information about all the subimages, such as their location in the texture atlas, their size, and offsets.
However, for projects generated from Gradle, the project folders will have different names, refer to the gdx-setup
versus gdx-setup-ui
section in Chapter 1, Introduction to LibGDX and Project Setup. Hence, for targeting the assets
folder inside the Android project folder in a Gradle-based project, the destination path is ../android/assets/images
.
The maxWidth
and maxHeight
variables of the Settings
instance define the maximum dimensions (in pixels) for the texture atlas. Always make sure that a single subimage does not exceed the maximum size of the atlas either in the width or height or both dimensions. Padding the subimages in the atlas will reduce the available size a little bit more, so make sure to take this factor into account too. The debug
variable controls whether the debug lines should be added to the atlas or not. We use the drawDebugOutline
variable to set the value to debug. The static variables rebuildAtlas
and drawDebugOutline
are there just for our convenience to make these two behavior controls stand out a bit more because we usually change these variables every now and then while debugging our game.
If the TexturePacker cannot fit all the subimages into a single texture, it will automatically split them up into several texture atlases. However, there is a chance that the subimages are distributed in an unfavorable way between these atlases if it creates two textures that will be switched between frequently, which in turn could have an impact on render performance.
LibGDX's TexturePacker has a very smart feature to tackle this type of problem. All you need to do is group the subimages in their own subfolder in assets-raw
. This way TexturePacker will create one image file per subfolder that belongs to the texture atlas. You have to use the full path to the subimage if you want to use this functionality; for example, a subimage assets-raw/items/gold_coin.png
would be referenced as items/gold_coin
.
Now you know how to create texture atlases in code. This approach mostly works very well, but it is not very user friendly in terms of seeing the outcome directly when a setting or an image is changed in the atlas. Fortunately, there is already a nice tool called TexturePacker-GUI
that has been developed by Aurélien Ribon. This tool is directly designed for LibGDX to work with its TexturePacker.
Check out the official project website at https://code.google.com/p/libgdx-texturepacker-gui/. You can also find more about the offline TexturePacker at https://github.com/libgdx/libgdx/wiki/Texture-packer.
The following screenshot is taken from the project's website that shows the tool in action:
There is also a popular commercial tool called TexturePacker to create texture atlases. This tool has been developed by Andreas Löw and is available for all three major platforms. For more information, check out the official website at http://www.codeandweb.com/texturepacker.