Texture Atlas Loader

During a recent project I needed the ability to load a texture atlas but was unable to find a way to split that into Textures, which led to the creation of this utility.

Not sure if I just missed something obvious, or if this is a poor way to handle assets, but I’d figured I’d share since I found nothing like it.

3 Likes

I like the idea, although I wonder about performance.

Isn’t it faster to just load a bunch of 512x512 images instead of extracting them using canvas operations?

I know this will mess up the ability to use offsets/repeats afterward, but instead of using canvas images use texture repeat & offsets to “extract” images, wouldn’t it be a lot faster?

Now I think about it, imagine having an atlas of 8192x8192 pixels in size. Using repeats/offset would mean that for every material in your scene that uses the atlas, will have this entire atlas “in memory”, which is probably even worse :smile:

Why would each use of the atlas require the source asset of the atlas to be unique in memory? The source isn’t even stored after having been processed into smaller textures. Unless I’ve missed something, it’s functionally the same end result as loading each texture individually (since the code uses the same loaders and method of creation of a texture as a TextureLoader does), with the benefit of only requiring a single web call and the drawback of a tiny performance loss when initially creating the textures.
EDIT: Ignore the above, I read your post wrong when I was tired!

Yes it would be better to use offsets and repeat (assuming shared memory), but that’s not always possible, and I’m not entirely a fan of magic numbers being used for texture assets. and no, I don’t think loading n images is faster than extracting them. I didn’t do a lot of testing, but the requests took longer from my pc to my web server than the splitting did for an image with 64 16x16 tiles in an 8x8 grid.

Can anybody show an example of how to use a sprite animation using a texture atlas using the gist you posted above?

Thank You!

I posted this sprite animation mixer some time ago, it works like Rustywolf tool, by offsetting frames on one texture.

Thank you @felixmariotto! :slight_smile:

Hi, Rustywolf! I looked for method to undestand how to combine several textures to big one, and your utility helped me much. But I found a bottle neck in your util.
this thing is expensive
let result = document.createElement(“img”);
result.width = width;
result.height = height;
result.src = canvas.toDataURL(isJPEG ? “image/jpeg” : “image/png”);

and I changed it to

texture = new CanvasTexture(extractCanvas)

here is my implementation

import {
  Texture,
  ImageLoader,
  RGBFormat,
  CanvasTexture,
} from "../build/three.module.js";

export const makeAtlas = (images, size, width, height) => {
  return new Promise((resolve) => {
    loadAllImages(images).then((data) =>
      composeToOneImage(data, size, width, height).then((texture) =>
        resolve(texture)
      )
    );
  });
};

const composeToOneImage = (images, size, width, height) => {
  return new Promise((resolve) => {
    let texture
    const extractCanvas = document.createElement("canvas");
    extractCanvas.width = width;
    extractCanvas.height = height;

    const graphics = extractCanvas.getContext("2d");

    const columnsCount = width / size;

    let x = 0;
    let y = 0;
    images.map((img) => {
      if (x > columnsCount - 1) {
        x = 0;
        y++;
      }
      graphics.drawImage(img, x * size, y * size, size, size);
      x++;
    });
    texture = new CanvasTexture(extractCanvas);
    texture.format = RGBFormat;
    texture.needsUpdate = true;
    texture.flipY = false;
    resolve(texture);
  });
};

const loadAllImages = (array) => {
  return Promise.all(array.map((item) => imageLoader(item)));
};

const imageLoader = (url) => {
  return new Promise((resolve, reject) => {
    let loader = new ImageLoader();
    loader.load(
      url,
      (img) => {
        resolve(img);
      },
      () => {
        reject();
      }
    );
  });
};

on GitHub