THREE.ComposedTexture - play GIF, APNG etc as texture

This is a texture class which allows to use multi-frame images such as GIF or APNG.

Other than most plain GIF players it doesn’t decode every pixel per frame on the fly, or uses putImageData (for larger GIFs this is too expensive), instead if no images provided, the decoded frame buffers will be converted to compressed images.

As GIF and other animated image formats get optimized not every frame is necessarily a full frame, most might only contain changing regions and pixels, so a frame needs to be composed across multiple frames. It also ensures the animation is rendered on a PO2 composition canvas.

This isn’t a GIF decoder, the example uses gifuct-js to decode a gif, you can use any multi-frame format as well as custom frames. A GIF and APNG loader example is included. The structure for the container object which holds the frames is the following.

Usage

In you render loop, you need to call texture.update( deltaTime ), or you control which frame is rendered yourself by calling texture.compose( frameIndex ).

Container structure

Container

downscale : Boolean optional
Canvas needs to be power of 2, by default size is upscaled (false).

width : Number
height : Number
frames : Array

Frame
Either patch or image should be provided, if a buffer is provided it will be converted to a image.

patch : Uint8Array
image : Image
dims : Object
With left, top, width, height dimensions.
delay : Number
How long the frame is display in milliseconds.
disposalType : Numer
1 of the 3 methods, see GIF specification for more details.

API

Constructor

THREE.ComposedTexture( container, ... )
Container object as described above. Other parameters as usual, you can also assign the container later by calling texture.assign( container ).

Properties

autoplay : Boolean
If playback automatically starts when loaded, default is true.

auto: Boolean
If playback should be handled globally, call THREE.ComposedTexture.update( delta ) in your render loop to handle all, default is true.

loop: Boolean
If the playback will be looped, default is true.

ready: Boolean
Will be true once all frames are ready to play.

isPlaying: Boolean
Indicates if the animation is currently playing.

time: Number
The current time in ms.

duration: Number
Total duration in ms.

Methods

async assign( container )
To assign a container object, if buffers are provided frames will be converted to images first.

update( deltaTime )
For automatic playback of the animation.

play()
Start playback.

pause()
Pause playback.

resume()
Resume playback.

stop()
Stop playback, first frame is shown.

Enjoy cat GIFs in the third dimension :cat:

Demo

Version 2

Show details
  • Fixed: for ^141 releases (needsUpdate)

  • Added: SpriteTexture for using sprite-sheet textures and playback just like ComposedTexture

  • Added: toSheet method on ComposedTexture create a baked sprite-sheet of the frameset
    Creating a sprite-sheet from ComposedTexture will automatically use a ideal layout stripe or atlas and if exceeding a given max resolution scale down to fit

  • Added: timeScale for ComposedTexture and SpriteTexture to alter general playback speed (default 1.0).

THREE.SpriteTexture

This new texture class uses a sprite-sheet (atlas or stripe). The animation interface is the same as on ComposedTexture. There are cases to use either this or ComposedTexture. The sprite-sheet can be derived from a ComposedTexture. via simple call .toSheet or provided from a regular texture.

Benefits

  • Only 1 initial texture upload (asked here)
  • Texture can be shared for a massive number of instances with individual animation state (related question)
  • Can be more memory efficient on JS side by it’s resource being a single texture and the frames not being needed anymore
  • You can load an assembled sprite-sheet texture and assign it directly, either you provide the sprite-sheet meta info or let it generate it:
const sprite = new THREE.SpriteTexture( {
	texture: spriteSheetTexture,
	padding: 0,
	columns: 3,
	count: 7,
	delay: 80
} );

Notice: the benefits of sharing the spritesheet texture are only available for THREE release R138 and higher, since a concept for sharing textures has been added.

When to use SpriteTexture

  • You got a smaller amount of frames or/and resolution, typically like 2D game asset sprite-sheets
  • You want to render a huge amount of it with different animation states
  • You don’t need to use repeat wrapping of the texture or texture transformations

When to use ComposedTexture

  • Your resource files for your project/use case are GIF, APNG etc
  • You got a lot / larger frames that are too many / too large (or unpredictable in that regard) for a sprite-sheet
  • Your main concern is GPU memory, ComposedTexture has fixed memory usaged of 1 frame on GPU
  • You need repeat wrapping of the texture or texture transformations

The outer left showcases a GIF turned to spritesheet and used with SpriteTexture then.

Things you may want to configure

// May be set by renderer.capabilities.maxTextureSize (recommend default unless needed), sheets going above will be scaled down to it
THREE.ComposedTexture.MaxSpriteSheetResolution = 4096;
// Sprite-sheets below this resolution will be a stripe which is more reasonable for uneven number of frames
THREE.ComposedTexture.MaxStripSize.MaxStripResolution= 2048;
12 Likes

Pretty cool !
Did you notice what happens when you switch of browser’s tab for some time and come back later ? Seems that play speed get accelerated somehow.

1 Like

That’s probably the delta from THREE.Clock in the demo, animation frame callbacks get paused or throttled in inactive tabs, especially without audio. I’ll check tomorrow :+1: :unicorn:

I’ve made an update now to prevent this, before i considered the overshoot with delta, THREE.Clock doesn’t take into account that requestAnimationFrame is throttled or paused and will give a huge number when running normally again.

Edit: You can also call now THREE.ComposedTexture.update( delta ) in order to automatically handle playback of all composed textures, if you want to handle it yourself you can set auto to false in the container object.

Changed the example, it has a GIF and APNG loader example included now.

1 Like

Just what I need :slight_smile: However, the demo (and the download received after paying at gumroad) fails with a
Cannot set property 'value' of undefined at ComposedTexture.html:46
error. Help?

Sorry for the trouble, as mentioned in the PM the issue was related to the updated skybox plugin used for the demo, i fixed the line and sent out the updated files.

Thank you very much for the support :heart:

1 Like

Very very useful! Thank you!

1 Like

sweet! thanks!

1 Like

Very excited to use this, however I think it’s out of date now… I keep getting

ComposedTexture.js:22 Uncaught (in promise) TypeError: Class constructor CanvasTexture cannot be invoked without 'new'
at new ComposedTexture

Tried changing the source to make this work but can’t quite seem to get there

Just integrated this into my project and it’s working for me.

Are you sure you’re instantiating with new THREE.ComposedTexture?

Thank you for reporting this. This is exactly why i’m not a fan of the ES6 classes change they applied now, while they technically don’t offer more but adding syntactical sugar, they restrict compatibility with the regular approach.

It works fine till THREE 126 but since 127 CanvasTexture is wrapped into ES6 class syntax, where constructor.call isn’t allowed anymore.

I will look into if this can be fixed without making 2 versions now, in order to stay compatible with older versions, once i updated it you will receive a mail, sorry for the trouble.

3 Likes

I fixed it and you should have received a email with the update, it works now with older versions as well as newer with ES6 syntax.

It didn’t require 2 separate files/implementations, just a patch override for releases later than 126.

Great thank you! Will try now. Just a quick note – I downloaded directly from the email and i still the line in question

THREE.CanvasTexture.call( this, this.canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );

edit: nevermind!

Yes the patch below turns it into a ES6 class so it won’t be an issue, the regular constructor is for older versions.

1 Like

Quick question, do you know if the packages at https://mevedia.com/share/gif.js and https://mevedia.com/share/upng.js available via npm?

I think this is the upng package: @syncfusion/ej2-angular-calendars - npm

UPNG: @syncfusion/ej2-angular-calendars - npm
GIF: gif-decode - npm

For GIF i’m not sure if it is the same but it should do the exact same, as you see in the example any format can be used as long as they serve frames, also if they’re just partial frames as GIF and UPNG can have.

Hey Hey,

I just bought this class from gumroad. It looks amazing, but for a noob there is really a lack of information.

What is a container object? What are these frames? How can I load a simple .gif into a three-js Sprite or PlaneGeometry? that’s what I want to accomplish.

Thank you

Thank you for your support, the description of what a container and frames are is in the first post, they are just the simple universal object to feed the ComposedTexture since this plugin is not bound to a specific decoder and can be used with any.

The included example shows how to use it with a GIF and APNG decoder.

I am really new to Three.js. Could someone explain to me how to import this to my project?
Do i have to place it in a specific folder and then try to write my code?

Thank you in advance.

1 Like

Yes, and welcome to the community :wave: place it in your projects folder and include it like any other script, just like the example index.html does. However if your project uses a bundler it needs to be imported the way the specific bundler works.

1 Like