adevart
adevart

Reputation: 156

How to load Texturepacker spritesheets in ThreeJS?

I'm trying to load spritesheets from Texturepacker in ThreeJS, which comprises an image and json. The image has a bunch of small sprites packed together and the json defines the location and size of the small sprites in the image.

I have tried 3 methods for loading.

The method using multiple texture instances with different offsets should work ok as I'm not copying the source image but when I run an animation by switching a material's texture, it uses a crazy amount of RAM as if it's copying the entire source spritesheet into memory for each one. If I change the texture offsets for the animation instead of using texture copies, it works ok but an offset change would be applied to every object using the same source spritesheet.

The WebGLRenderTarget method needs a camera and scene for cropping the textures and a sprite added to the scene. The output from this is unusable as it doesn't generate a 1:1 crop of the original texture and it's really slow to load. Is there a way to render textures 1:1 to smaller buffers in ThreeJS?

The Canvas method worked best where I create a canvas element for each sprite and crop the spritesheet into each. This is 1:1 and good quality but the point of using a spritesheet is that the GPU only has a single image to address and this needs an HTML loader process. Ideally I don't want to crop the spritesheet to smaller texture buffers.

Why does using the same large source image with multiple THREE.Texture objects use so much memory? I expected it would only need to keep a single texture in memory and the Texture objects would just display the same texture with different offsets.

Upvotes: 5

Views: 1502

Answers (1)

adevart
adevart

Reputation: 156

I found a way that works.

First, I load the texture by making a WebGLTexture from the spritesheet image loaded via a ThreeJS ImageLoader, which gets stored in _spritesheets[textureID].texture.

let texture = this._spritesheets[textureID].texture;
let gl = this._renderer.getContext();
let webGLTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, webGLTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

Then I set the webGL texture parameter of the texture object to this and set its webglInit value to true so it doesn't create a new buffer.

let frames = textureJSON.frames;
for (let frameID of Object.keys(frames)) {
    let frame = frames[frameID];
    let t = new THREE.Texture(texture);

    let data = frame.frame;
    t.repeat.set(data.w / texture.width, data.h / texture.height);
    t.offset.x = data.x / texture.width;
    t.offset.y = 1 - data.h / texture.height - data.y / texture.height;

    let textureProperties = this._renderer.properties.get(t);
    textureProperties.__webglTexture = webGLTexture;
    textureProperties.__webglInit = true;

    this._textures[frameID] = {};
    this._textures[frameID].texture = t;
    this._textures[frameID].settings = { wrapS: 1, wrapT: 1, magFilter: THREE.LinearFilter, minFilter: THREE.NearestFilter };
}

The spritesheet JSON is loaded via a ThreeJS FileLoader. I then store the sprites by frame id in a _textures object and can assign those to a material's map property.

Upvotes: 4

Related Questions