Animation takes up too much memory

Started by
27 comments, last by comfy chair 8 years, 10 months ago

I am using a library called XNA Animation Component to play animations in my XNA game. I am trying to compress the animations, which consist of arrays of this struct:


/// <summary>
    /// Represents a keyframe in an animation track.
    /// </summary>
    public struct BoneKeyframe
    {
        /// <summary>
        /// Creats a new BoneKeyframe.
        /// </summary>
        /// <param name="transform">The transform for the keyframe.</param>
        /// <param name="time">The time in ticks for the keyframe.</param>
        public BoneKeyframe(Matrix transform, long time)
        {
            this.Transform = transform;
            this.Time = time;
        }
        /// <summary>
        /// The transform for the keyframe.
        /// </summary>
        public readonly Matrix Transform;
        /// <summary>
        /// The time for the keyframe.
        /// </summary>
        public readonly long Time;

    }
Advertisement

Eliminate redundant key frames from the animation data. This operation is trivial and can eliminate 90% of key frames in many cases where animation data is densely packed (such as those captured via motion capture).

Each character instance needs its own copy of its skeleton and a time within the animation (plural when blending), not a whole new copy of the animation data itself, which should be shared when possible (obviously not possible when characters each need completely unique animation data).


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

In addition to L. Spiro's advise, exporters often have a setting for sampling rates. Higher sampling rates often results in higher fidelity during playback vs how it looks in the modelling application (specifically important when doing IK or other procedural animation); while lower sampling rates use far less memory.

I was wrong. I have looked at the memory profile and it says that the animation data is not copied for each character instance as I thought.
However, the game still uses over 200 mb of memory for animations alone, and I would like to reduce that if possible.

When we were setting up the pipeline, we found that there was only one specific way to get the animations to play properly in the game. It involved baking all the keyframes. I am not sure if we will be able to tweak the export procedure very much without it breaking.


It involved baking all the keyframes. I am not sure if we will be able to tweak the export procedure very much without it breaking.

What do mean by "baking" all the keyframes? And do you mean "keyframes" or just time-indexed data?

From your comments, it sounds like the animation code is broken. You could revise the runtime animation calculations and greatly reduce the amount of animation data needed. For instance, a simple animation for walking would require only 3 keyframes per bone. The vast majority of animation data needs only rotation data (i.e., no scaling or translation). If that's stored as a quaternion, that's 4 float values. Say 30 bones, 3 quaternions each, 4 floats per quaternion, 4 bytes per float value = 1.5K per animation, with the possibility that could be shared among any characters using the same frame hierarchy.

As L. Spiro mentions, eliminate redundant frame data from the data you're importing (not at runtime). If you have 20 frames of animation data that are equivalent (or close enough) to LERP'ing or SLERP'ing between frames 0 and 19, you only need 2 keyframes per bone. If that means revising the way you generate animations, the way you import data into your app, and the way you implement animations at run-time, then you'll have to make a choice.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

It sounds like this component you use might just be a bit wasteful with memory.

Is there any reason you dont just do animations yourself? It should only take a few hours to figure it out and put something together. Think of sequences of animations as simple instructions, like in turtle graphics and logo, and see if you can come up with something. It'll be worth it!

You could write some simple routines to interpolate movement of parts of your sprites, or of the joints in a 3D skeletal animation, for 2D split the sprites up into discrete bits, e.g. arms, legs, weapons, faces, eyes etc. You can then move and animate only the moving elements.

I am looking at the code that pre-compiles the animation data coming from the exporter.

It looks like it is storing 60 keys per second, regardless of what it receives from the exporter.

Here is the code:



                // Step through time until the time passes the animation duration
                while (time <= animationDuration)
                {
                    AnimationKeyframe keyframe;
                    // Clamp the time to the duration of the animation and make this 
                    // keyframe equal to the last animation frame.
                    if (time >= animationDuration)
                    {
                        time = animationDuration;
                        keyframe = new AnimationKeyframe(new TimeSpan(time),
                            channel[channel.Count - 1].Transform);
                    }
                    else
                    {
                        // If the channel only has one keyframe, set the transform for the current time
                        // to that keyframes transform
                        if (channel.Count == 1 || time < channel[0].Time.Ticks)
                        {
                            keyframe = new AnimationKeyframe(new TimeSpan(time), channel[0].Transform);
                        }
                        // If the current track duration is less than the animation duration,
                        // use the last transform in the track once the time surpasses the duration
                        else if (channel[channel.Count - 1].Time.Ticks <= time)
                        {
                            keyframe = new AnimationKeyframe(new TimeSpan(time), channel[channel.Count - 1].Transform);
                        }
                        else // proceed as normal
                        {
                            // Go to the next frame that is less than the current time
                            while (channel[currentFrame + 1].Time.Ticks < time)
                            {
                                currentFrame++;
                            }
                            // Numerator of the interpolation factor
                            double interpNumerator = (double)(time - channel[currentFrame].Time.Ticks);
                            // Denominator of the interpolation factor
                            double interpDenom = (double)(channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks);
                            // The interpolation factor, or amount to interpolate between the current
                            // and next frame
                            double interpAmount = interpNumerator / interpDenom;
                            
                            // If the frames are roughly 60 frames per second apart, use linear interpolation
                            if (channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks
                                <= ContentUtil.TICKS_PER_60FPS * 1.05)
                            {
                              //  context.Logger.LogImportantMessage("Lerp between frames {0} and {1}, interpAmount: {2}", currentFrame.ToString(), (currentFrame + 1).ToString(), interpAmount.ToString()); //  input.Duration.Ticks.ToString(), ((long)(durationFactor * input.Duration.Ticks)).ToString());

                                keyframe = new AnimationKeyframe(new TimeSpan(time),
                                    Matrix.Lerp(
                                    channel[currentFrame].Transform,
                                    channel[currentFrame + 1].Transform,
                                    (float)interpAmount));
                            }
                            else // else if the transforms between the current frame and the next aren't identical
                                 // decompose the matrix and interpolate the rotation separately
                                if (channel[currentFrame].Transform != channel[currentFrame + 1].Transform)
                            {
                            //    context.Logger.LogImportantMessage("Slerp between frames {0} and {1}, interpAmount: {2}", currentFrame.ToString(), (currentFrame + 1).ToString(), interpAmount.ToString()); //  input.Duration.Ticks.ToString(), ((long)(durationFactor * input.Duration.Ticks)).ToString());

                                keyframe = new AnimationKeyframe(new TimeSpan(time),
                                    ContentUtil.SlerpMatrix(
                                    channel[currentFrame].Transform,
                                    channel[currentFrame + 1].Transform,
                                    (float)interpAmount));
                            }
                            else // Else the adjacent frames have identical transforms and we can use
                                    // the current frames transform for the current keyframe.
                            {
                                keyframe = new AnimationKeyframe(new TimeSpan(time),
                                    channel[currentFrame].Transform);
                            }

                        }
                    }
                    // Add the interpolated keyframe to the new channel.
                    outChannel.Add(keyframe);
                    // Step the time forward by 1/60th of a second
                    time += ContentUtil.TICKS_PER_60FPS;
                }

But at runtime, interpolation is performed again. So as some of you mentioned, I may be able to reduce this data and still get smooth playback.

As a quick hack, I'd setup that the animation to take just 10% of the time and play it 10 times slower. If that reduces the size and still looks good, then there is really a sampling issue with the exporter as you're assuming.

As a quick hack, I'd setup that the animation to take just 10% of the time and play it 10 times slower. If that reduces the size and still looks good, then there is really a sampling issue with the exporter as you're assuming.

Yes, but the code i posted above suggests that everything that gets exported is overridden in a resampling loop.

This topic is closed to new replies.

Advertisement