I don't undertand TextureAtlas

I’ve been playing around with TextureAtlas, trying to understand what it can be used for, and what it can’t be used for. However, what I thought it could do doesn’t seem to be working, so, I need some help.

I’ve looked at the example (https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/tools/TestTextureAtlas.java), and I understand what that does. It takes many geometries, each with different textures, and replaces them with a single geometry and a single texture, which is an amalgamation of all those different textures. Creating the same scene without using TextureAtlas results in slightly lower FPS, so, it certainly seems to make a difference.

This seems to be like GeometryBatchFactory.optimize, except rather than create a separate geometry to replace all the geometries that have the same texture, TextureAtlas creates a single geometry to replace all the geometries.

However, am I right in thinking that you can also add new geometries to the game and then use the TextureAtlas to give them their texture? Something like this:

// Create a TextureAtlas
Spatial sinbad0 = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
TextureAtlas atlas = TextureAtlas.createAtlas(sinbad0, 2048);

// Use TextureAtlas on a new model
Spatial sinbad1 = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
List<Geometry> geometries1 = new ArrayList<>();
GeometryBatchFactory.gatherGeoms(sinbad1, geometries1);

for (Geometry geom : geometries1) {
    atlas.applyCoords(geom);
}

Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
material.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap"));
sinbad1.setMaterial(material);

rootNode.attachChild(sinbad1);

Or, is this not what TextureAtlas is supposed to be used for? I know in this example TextureAtlas would only have one texture in it, which would be a bit pointless, but, I’m just doing that to keep the code simple.

The above snippit works (adds a Sinbad that looks fine), until I try to add a second Sinbad:

Spatial sinbad2 = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
List<Geometry> geometries2 = new ArrayList<>();
GeometryBatchFactory.gatherGeoms(sinbad2, geometries2);

for (Geometry geom : geometries2) {
    atlas.applyCoords(geom);
}

sinbad2.setMaterial(material);

rootNode.attachChild(sinbad2);

Then this happens (the texture is screwed up):

Why is that happening?

It happens because your second geometry isn’t known by the texture atlas and so cannot change its texture coordinates to the correct texture. You created a new geometry by batching sinbad first. A texture atlas is nothing more than a big texture with separate textures on it (see the second SDK UseCase tutorial on why that is a good thing).

The only thing the TextureAtlas class does (if you use it with geometries instead of textures directly) is put the textures of a bunch of geometries onto one texture and then if you want you can change the texture coordinates of a geometry that you previously used to create that atlas to the new coordinates on that atlas.

OK. I searched the forum for some more examples of using TextureAtlas, and I came across this topic, which has a nice example of how to load several textures into a TextureAtlas, and then use TextureAtlasTile.transformTextureCoords to assign a particular texture to a geometry. So, I’ve been playing around with this quite a bit.

I extended that example to create a wall of 400 tiles, each with one of 1,024 random textures, each 32 by 32 pixels, and then update the texture of a random tile 500 times per second. If the wind is blowing in the right direction, I get about 1500 FPS running this.

private TextureAtlas atlas;
private Texture[] texture = new Texture[1024];
private Material material;
private Random random = new Random();
private Geometry[][] tiles = new Geometry[20][20];
private float time;

@Override
public void simpleInitApp() {
    atlas = new TextureAtlas(1024, 1024);
    
    String[] files = {
        "Textures/red.png",
        "Textures/green.png",
        "Textures/blue.png",
        "Textures/yellow.png"
    };
    
    for (int i = 0; i < 1024; i++) {
        texture[i] = assetManager.loadTexture(files[i % 4]);
        
        atlas.addTexture(texture[i], "ColorMap");
    }
    
    material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", atlas.getAtlasTexture("ColorMap"));
    
    for (int x = 0; x < 20; x++) {
        for (int y = 0; y < 20; y++) {
            addTile(x, y);
        }
    }
}

@Override
public void simpleUpdate(float tpf) {
    time += tpf;
    
    if (time > 0.002f) {
        time -= 0.002f;
        
        int x = random.nextInt(20);
        int y = random.nextInt(20);

        tiles[x][y].removeFromParent();

        addTile(x, y);
    }
}

private void addTile(int x, int y) {
    Quad quad = new Quad(1, 1);
    
    tiles[x][y] = new Geometry("Tile", quad);
    tiles[x][y].setLocalTranslation(x - 10, y - 10, -10);
    tiles[x][y].setMaterial(material);
    
    FloatBuffer buf = quad.getFloatBuffer(Type.TexCoord);
    atlas.getAtlasTile(texture[random.nextInt(1024)]).transformTextureCoords(buf, 0, buf);
    
    rootNode.attachChild(tiles[x][y]);
}

I’m actually only using four images, but, I’m loading them 256 times each. I don’t think this will affect the results.

I then did the same thing without using TextureAtlas, and typically get about 800 FPS running this:

private Texture[] texture = new Texture[1024];
private Random random = new Random();
private Geometry[][] tiles = new Geometry[20][20];
private float time;

@Override
public void simpleInitApp() {
    String[] files = {
        "Textures/red.png",
        "Textures/green.png",
        "Textures/blue.png",
        "Textures/yellow.png"
    };
    
    for (int i = 0; i < 1024; i++) {
        texture[i] = assetManager.loadTexture(files[i % 4]);
    }
    
    for (int x = 0; x < 20; x++) {
        for (int y = 0; y < 20; y++) {
            addTile(x, y);
        }
    }
}

@Override
public void simpleUpdate(float tpf) {
    time += tpf;
    
    if (time > 0.002f) {
        time -= 0.002f;
        
        int x = random.nextInt(20);
        int y = random.nextInt(20);

        tiles[x][y].removeFromParent();

        addTile(x, y);
    }
}

private void addTile(int x, int y) {
    Quad quad = new Quad(1, 1);
    
    tiles[x][y] = new Geometry("Tile", quad);
    tiles[x][y].setLocalTranslation(x - 10, y - 10, -10);
    
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", texture[random.nextInt(1024)]);
    tiles[x][y].setMaterial(material);
    
    rootNode.attachChild(tiles[x][y]);
}

Results vary greatly from one run to the next, however. In both cases, FPS will start well over 1000 for several seconds, but is more likely to stabilize well below 1000 when not using TextureAtlas. However, sometimes I run the version without TextureAtlas, and FPS will stabilize at over 1000 FPS, and then, when I run the version with TextureAtlas, FPS will stabilize well below 1000, meaning that, occasionally, the version without TextureAtlas actually runs faster.

On my PC, updating tiles 500 times per second causes the version with TextureAtlas to (tend to) greatly out-perform the version without TextureAtlas. Updating the tiles much more or less than 500 times per second, and the difference in FPS is less pronounced. In these cases, the version with TextureAtlas does still tend to do better than the version without though. For example, with TextureAtlas, I’ll get about 850 FPS, and without TextureAtlas, about 775 FPS.

If the scene isn’t updated, then TextureAtlas doesn’t make any difference to FPS. So, when used like this, TextureAtlas only seems to make a difference when the scene is getting updated a lot.

1,024 textures seems quite a lot. However, using just four textures instead, TextureAtlas doesn’t make any difference. I also tried using four textures, but each one being 2048 by 2048 pixels, but, TextureAtlas still didn’t make any difference.

Hope all this matches up with what is expected when using TextureAtlas, and that I haven’t gone off in completely the wrong direction with it. I’m just trying to understand how to use it, and in what situations it can make a difference.

In the first case, you reuse the material. In the second case, you create a new material every time. The second case is going to be way more ‘garbagy’ over time.

As per the second SDK UseCase tutorial video - its best to use some kind of texture atlas in the first place instead of batching the textures in code with TextureAtlas.

The asset manager has a cache memory.

You can try to call System.gc() to ask the garbage collector to garbage the field, before assigning a new texture.

However, I don’t understand what you are trying to do/demonstrate.

Good point. That could well explain the difference in FPS, rather than the TextureAtlas. :chimpanzee_facepalm:

OK, watched it. Quite informative. I’ve been learning about creating models, and UV mapping in Blender, but, the idea of a texture atlas, and then having the models you create use bits of that texture atlas, is something I hadn’t thought of. When I come to creating model buildings for my game, I’ll certainly consider doing it that way, as, although they’ll have different meshes, I probably won’t need the textures to look all that different between them.

I was trying to put together a simple use-case for the TextureAtlas class so that I could understand it better. Something beyond just calling makeAtlasBatch, as there are a lot of methods there, that are presumably designed to be used in certain scenarios. As TextureAtlas is all about efficiency, I was trying to put together a with and without TextureAtlas example, that demonstrates how TextureAtlas makes a positive difference.

I think I’ll probably leave it for now, though, and, perhaps, look into texture atlases again when I actually get to the point of putting models in my game. I was trying to learn about the TextureAtlas class ahead of time, as I try to spend a bit of time each day learning about something. However, in this case, it might be easier for me to understand once I have a bit more experience of creating models and putting them into my game, and seeing what happens.

Thanks for your help though. :chimpanzee_smile:

Depending on how many atlases you have and how many separate textures are on them you can get vast differences…