godot icon indicating copy to clipboard operation
godot copied to clipboard

Implement a Texture2D to support Lottie animation

Open beicause opened this issue 9 months ago • 45 comments

Related proposals:

  • godotengine/godot-proposals/issues/9104
  • Bugsquad edit, closes: godotengine/godot-proposals/issues/9104

Edit: Currently my implemention is a Texture2D that generates a sprite sheet, configured through the properties frame_begin, frame_end, frame_count and rows, and scale to change image size.

Also, this PR implement a resource loader for lottie json. Although editor import plugin may be better, it breaks the built-in json editor.

Here are two ways to use the LottieTexture2D:

  1. Set frame_count to you want to generate a sprite sheet which and be used directly in Spriteframes, which requires more memory.
  2. Set frame_count to 1 and attach this texture to Sprite or TextureRect, then update frame_begin in _process() to play the animation, which Increases CPU load for frame processing.

beicause avatar May 05 '24 10:05 beicause

I'll put this on my list to review for the pipeline import.

fire avatar May 05 '24 13:05 fire

  1. Run the godot engine binary with --doctool to update the documentation
  2. I'll try to give you guidance for the "json" property being set as the default value

Here are the changes: https://github.com/beicause/godot/compare/lottie...V-Sekai:godot:vsk-lottie-4.3

  1. Thoughts on extending Ref<Image> directly? You could also extend Ref<Texture2D>.
  2. There's a system to convert ".json" to "LottieSheet" that is missing that goes through the ResourceImporter.

fire avatar May 05 '24 13:05 fire

Thanks for your guide. I have changed it to inherit Texture2D. Now in editor it display Lottie normally, but when running the game it's empty. I don't find the reason.

beicause avatar May 06 '24 04:05 beicause

I was stuck, I suspect we need to use the resource importer system that takes json and gives a LottieTexture2D

fire avatar May 06 '24 04:05 fire

I was stuck, I suspect we need to use the resource importer system that takes json and gives a LottieTexture2D

Don't open json directly. Create a LottieTexture2D and add the json as subresource.

beicause avatar May 06 '24 05:05 beicause

I'm not very familiar with custom resource. I still can't understan why using code to set this texture works:

texture=LottieTexture2D.create_from_string(FileAccess.get_file_as_string("res://lottie.json"),1.0)

~~But setting the texture in editor doesn't work in games.~~ ( Update: create_from_json doesn't work too. Fixed by disabling JSON::stringify sort_keys )

For resource importer, since lottie extension name is also .json and will be recognized as JSON, we have to customize one, for example .lottie. Or don't implement resource importer for Lottie, just create it manually and use JSON as property.

beicause avatar May 06 '24 10:05 beicause

So there's a way to create a resource importer plugin that scans for "*.json" as lottie.

I'll try to give you instructions as soon as I can or you can try it.

fire avatar May 06 '24 13:05 fire

This implementation is simple and just works for me, by generating animation frame on-demand.

While this is a simpler approach, note that it'll result in less smooth animations that no longer look crisp when zoomed in (e.g. due to Camera2D zoom or canvas_items stretch mode).

I think Lottie's biggest use case is that it allows for vector-based 2D animation with an arbitrary amount of frames (in other words, things can be freely interpolated). While you could choose to import at a high framerate (which doesn't seem to be adjustable in this importer right now), targeting 60 FPS will require a lot of memory.

This might be a dealbreaker for some use cases, so it's worth considering whether a true vector-based approach is more suitable first.

Performance is also an important caveat, similar to AnimatedTexture. Updating texture data often is slow, and it can also be inflexible (e.g. it might not obey pausing as expected).

On the bright side, this texture-based approach has some upsides, such as being easier to use in 3D (e.g. with Sprite3D). A vector-based solution wouldn't be usable in 3D unless rendered to a texture via ViewportTexture, in which case it would also lose its vector-based nature.

Calinou avatar May 06 '24 17:05 Calinou

The common use case of Lottie is UI or other short duration animation, where AnimatedTexture is convenient. We can add a resource loader to import Lottie as AnimatedTexture ( I just find AnimatedTexture is deprecated, so it's not a good solution. )

beicause avatar May 06 '24 19:05 beicause

Hello, Texture (or AnimatedTexture) methods have several advantages.

Primarily, GPU with direct drawing is not always good & faster since the following factors:

  • When using Lottie as a static resource like SVG, or when animations are paused, the result from one rendering can be quickly reused in subsequent frames. This is particularly evident when using Lottie with UI assets that rarely change resolution.
  • Lottie requires more composition steps than expected to complete a scene. This process involves using a temporary framebuffer to create intermediate scenes, and then extracting pixel information from the framebuffer for blending. These requirements can be quite burdensome on a GPU-based graphics pipeline compared to current software rendering & texturing method.
  • Sometimes, the GPU might have too many burdens to complete real-time game graphics.

On the other hand, directly outputting vector drawings without going through textures can be more efficient in terms of memory use & performance in situations such as:

  • Super high resolution for Lottie Scenes.
  • Outputting very simple Lottie design resources (such as icons) without any composition.

Obviously it could be more beneficial to unify the vector drawing primitives as a single form in the Godot, however, implementing complex rendering logic like Lottie directly in Godot can be costly in terms of development resources and maintenance. Therefore, using third-party libraries may be advisable. In such architectures, typically using FBOs or textures can provide a stable integration structure during the main rendering and thorvg rendering.

Moving forward, ThorVG would be able to attempt hardware acceleration through its own hw rendering backend in order to integrate with Godot's main rendering pipeline, targeting FBOs for drawing. Nevertheless, if performance issues arise, attempting an aggressive integration structure based on direct drawing methods and sharing rendering contexts might be considered, although it will be comparatively challenging.

hermet avatar May 07 '24 07:05 hermet

Godot editor gets stuck sometimes, especially when frame_count is high or there are multiple lottie files in the project, may because of ThorVG 0.13.2. Tried ThorVG 0.12.10, it's stuck too, although less frequently. Call stack: 2024-05-09 15-30-32 screen

beicause avatar May 09 '24 07:05 beicause

@beicause @hermet Is there a test project I can send to help ThorVG debug this?

fire avatar May 09 '24 15:05 fire

@beicause @hermet Is there a test project I can send to help ThorVG debug this?

thorvg_test.zip

beicause avatar May 09 '24 15:05 beicause

Hmm... it looks threading dead lock. let me review the patch first.

hermet avatar May 10 '24 07:05 hermet

I think it's easier to use it in AnimationSprite2D/SpriteFrames by generating multiple frames at the same time. And if you just want one frame, you can set frame_count to 1 and set frame_begin. Playing the animation by dynamic updating the texture needs more codes for users.

Another option is implementing a import plugin to import Lottie as a image texture, like SVG. This avoids run-time cost, but increases app size.

beicause avatar May 10 '24 09:05 beicause

Another option is to treat this like theora like a video.

fire avatar May 10 '24 09:05 fire

@hermet Hello, I made the change https://github.com/godotengine/godot/pull/91580/commits/6e56a6cfa517bb1ab214e7534229031861168301 according to your guidance, but still get stuck sometimes.

beicause avatar May 11 '24 06:05 beicause

@beicause It looks data corruption or overflow @_@. Could you please share me the size of picture here? image

hermet avatar May 13 '24 13:05 hermet

@beicause It looks data corruption or overflow @_@. Could you please share me the size of picture here? image

The size of picture is correct and this call returns SUCCESS. _update_image is only called once at startup. It's strange that the render behavior is inconsistent ( not every time it gets stuck ). I also try some other Lottie files, but don't reproduce the issue.

beicause avatar May 14 '24 06:05 beicause

May I ask which version Lottie Texture2D will be released in? Will it come out in 4.3 dev 7? I'm in a hurry waiting for this. thanks

bestvcboy avatar May 21 '24 09:05 bestvcboy

This is still a work in progress (draft) and we are in feature freeze for 4.3, so at the earliest this could be merged for 4.4, if it's ready and approved by the import team.

akien-mga avatar May 21 '24 09:05 akien-mga

Boss, can you compile a godot 4.3 dev6 c# version for me to use first? Thank you in advance.

bestvcboy avatar May 27 '24 07:05 bestvcboy

@bestvcboy You can download it from Github Action Artifacts. I also create a lottie-next branch that sync to godot master and thorvg main.

It's recommended to fetch this pr and compile Godot if you really want to try it, which is not very difficult.

beicause avatar May 27 '24 07:05 beicause

I have compiled it and used it, but my JSON has been loaded in and won't move. It will be displayed as an image. robothelp.json

bestvcboy avatar May 27 '24 12:05 bestvcboy

@bestvcboy Please see the class doc. The texture won't update automatically. You should use it in SpriteFrames or update frame_begin in script.

beicause avatar May 27 '24 12:05 beicause

 LottieTexture2D d1 = GD.Load<LottieTexture2D>("res://robothelp.json");
 var animatedSprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
 for (int i = 0; i < 200; i++)
 {
     d1.FrameBegin = i;
     animatedSprite.SpriteFrames.AddFrame("run", d1);
 }
 animatedSprite.Play("run");
  It will be displayed as an image.

bestvcboy avatar May 27 '24 13:05 bestvcboy

@bestvcboy You add the same texture to every frame so it don't play. Here are two ways to use.

  1. Set frame_count to you want and generate a sprite sheet then add it to Spriteframes with animation editor( or write code that adds AtlasTexture, so does the editor ).
  2. Set frame_count to 1 and attach this texture to Sprite or TextureRect, then update the texture in _process() to play animation.

beicause avatar May 27 '24 14:05 beicause

Thank you very much, it's already possible. The speed of solving the problem is really exciting and I want to shed tears.

bestvcboy avatar May 27 '24 14:05 bestvcboy

Encountered another problem. It's completely normal in edit mode, but after running, The scale of AnimatedSprite2D will change on its own, and once I change it back, it will still change back on its own. Very unstable. The method I use is to add frame_count to AnimatedSprite2D.

bestvcboy avatar May 27 '24 14:05 bestvcboy

@bestvcboy Counld you please describe your problem more detail?

beicause avatar May 27 '24 14:05 beicause