5

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.

  • using ThreeJS loaders for the json and image and assigning new textures with different repeat and offset values.
  • using WebGLRenderTarget buffers to crop the source image into
  • using Canvas buffers to crop the source image into

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.

adevart
  • 184
  • 7
  • This issue seems to have been noted on the following pages with some suggested workarounds: https://github.com/mrdoob/three.js/issues/5821 https://stackoverflow.com/questions/25514730/cloning-textures-without-causing-duplicate-card-memory-in-three-js I can't get those workarounds to work in the latest version of ThreeJS. – adevart Aug 09 '19 at 12:07

1 Answers1

4

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.

adevart
  • 184
  • 7
  • 1
    This is great! Thank you. I ran into the problem of tons of textures loading when I was trying to add random offset to the texture drawn on several objects. I didn't want the texture to start in the same place on each one and this worked perfectly for eliminating that problem. – aecend May 01 '20 at 06:13
  • I eventually ended up using an alternative method, which I found simpler to use. This was the function Object3D.onBeforeRender call. If you run this function on every object you draw that has different texture offset, you can set the texture offset before each object render. You can setup a helper function to make it easier to assign the offsets and store the offsets against a texture id. – adevart May 10 '20 at 08:27