Looking for version 1.x documentation? View it here!
Version 2.0 of Mesh Animator introduced sweeping changes that may not be compatible with previous versions. It is recommended to delete the existing MeshAnimator folder from the project, import the new package, and then re-bake any previously baked assets.
Version 2.0 dropped support for older Unity versions and is being built for Unity 2018.3+. If example asset meshes look stretched or corrupt, it's most likely just a version difference issue. Rebake the included example prefabs. Snapshot mode may still work with older Unity versions, but it will not be officially supported.
Mesh Animator is a toolset for converting and fully animating characters, animated assets, or any other kind of skinned mesh.
Find resources below on how to use Mesh Animator and all it's features. To report any bugs, or for questions not covered in the documentation, please e-mail jschieck@gmail.com.
The following window will appear
This section provides general information about the currently selected bake mode.
Setup which animations will be baked.
Setup meshes to be baked. All meshes will be combined into a single mesh and lose bone skinning information as part of the baking process.
The baking process removes all bones and transforms from the prefab or asset. To attach an object, for instance, attach a sword to a hand bone. Expose the transform and it will move with the animation. This adds a slight amount of overhead as it needs to update each transform position each time the animation frame changes.
Bake settings to control accuracy and asset size.
Accuracy = 0.001. Good for most animations. Use 0.0001 for even less compression.
Accuracy = 0.01. Good for larger models or models not viewed close up.
Accuracy = 0.1. A very low accuracy can be used to create animated models that look pixelated.
Once the settings are correct, click Bake Animations. This will create animation assets and prefabs using the specified settings.
**NOTE** - In snapshot mode, if the skinned mesh's pivot is not facing Y up, it's rotation may appear off until playback begins. This is because all weight information is lost during baking.
Once baking is complete, the following assets will be created.
Each animation will get a MeshAnimation asset and a MeshAnimator prefab will be created. Rebaking the same asset will update existing MeshAnimation files and create a new prefab, so it's good practice not to use the generated prefab as the main asset, but rather a child of a top level prefab.
The Mesh Animation is the asset that stores the animation info. In shader mode, textures will be sub-assets of the main MeshAnimation asset.
The Mesh Animator is the main script that animates and swaps frames during gameplay. It functions similarly to the legacy Animation component.
Mesh Animator functions much like the legacy Animation component. In it's simplest form, animations are played by calling the Play() method.
public MeshAnimatorBase meshAnimator; void Start() { meshAnimator.Play("idle"); }
The Mesh Animator component has a few callbacks that can be used.
public MeshAnimatorBase meshAnimator; void Start() { // called when an animation completes meshAnimator.OnAnimationFinished += (anim) => { Debug.Log(anim + " finished."); }; // called when a new frame of animation is displayed meshAnimator.OnFrameUpdated += () => { Debug.Log("Animation frame changed."); }; // called when a MeshAnimator enters or leaves the screen meshAnimator.OnVisibilityChanged += (isVisible) => { Debug.Log(isVisible); }; }
With the convience of Mecanim's state machines, included is an example MecanimMeshAnimatorController.cs script. This script looks at the current state of a Mecanim animator, and plays the appropriate animation on the Mesh Animator. State names MUST match the name of the MeshAnimation and blend trees are not supported.
Since running an animator with it's own controller when not needed is excessive and hurts performance, included is an example state machine that achieves the same result as the Mecanim controller but more efficiently.
using UnityEngine; using System.Collections; public class AnimatorStateMachine : MonoBehaviour { public MeshAnimatorBase meshAnimator; public bool crossFade = false; void Start() { meshAnimator.Play(); meshAnimator.OnAnimationFinished += OnAnimationFinished; } void OnAnimationFinished(string anim) { string newAnim = string.Empty; switch (anim) { case "idle": newAnim = "run_forward"; break; case "run_forward": newAnim = "run_backward"; break; case "run_backward": newAnim = "run_left"; break; case "run_left": newAnim = "run_right"; break; case "run_right": newAnim = "idle"; break; } if (crossFade) meshAnimator.Crossfade(newAnim); else meshAnimator.Play(newAnim); } }
With Mesh Animator, depending on the number of vertices, and number of frames in the animation, it is going to take up more memory than a traditional SkinnedMeshRenderer and Animator. However, the performance gains occur on the CPU. Finding a good balance between the two is important. If a character has 5k vertices and 20 animations each a few seconds long, it's not practical to use Snapshot mode.
The best use of Mesh Animator is with low-polygon models with short animations. Each frame of animation is generated and copied from the base, which is why so much memory is needed for larger meshes and longer animations.
A 4k triangle character (like in the example) takes up 500KB of memory. To display a 1 second idle animation baked at 30fps, the system would need approximately 15MB of memory. Memory usage is the same whether displaying 1 character or 1000 characters. A character that has only 400 triangles takes 32KB of memory per mesh, so the same length animation would only take 960KB of memory. Use this information to plan accordingly in the project.
Crossfading animations requires a temporary mesh to be created for the interpolation and adds a little CPU overhead for interpolating the vertex positions. This overhead grows larger the more vertices the mesh has. The temporary meshes are pooled automatically so that Mesh Animators sharing the same base mesh can use it. Crossfading a mesh requires at least 1 extra copy of the base mesh.
Shader mode uses animation textures to display animation on a mesh. In order to accomplish this and support GPU instancing, custom shaders are needed.
Included in the asset package are the most commonly used built-in shader provided by Unity, converted to work with Mesh Animator. These include:
If wanting to convert existing shaders to use with Mesh Animator, the process to convert is relatively easy for both fragment and surface shaders.
Shader "Mesh Animator/My Custom Shader" { Properties { // OTHER PROPERTIES HERE // start MeshAnimator [PerRendererData] _AnimTimeInfo ("Animation Time Info", Vector) = (0.0, 0.0, 0.0, 0.0) [PerRendererData] _AnimTextures ("Animation Textures", 2DArray) = "" {} [PerRendererData] _AnimTextureIndex ("Animation Texture Index", float) = -1.0 [PerRendererData] _AnimInfo ("Animation Info", Vector) = (0.0, 0.0, 0.0, 0.0) [PerRendererData] _AnimScalar ("Animation Scalar", Vector) = (1.0, 1.0, 1.0, 0.0) [PerRendererData] _CrossfadeAnimTextureIndex ("Crossfade Texture Index", float) = -1.0 [PerRendererData] _CrossfadeAnimInfo ("Crossfade Animation Info", Vector) = (0.0, 0.0, 0.0, 0.0) [PerRendererData] _CrossfadeAnimScalar ("Crossfade Animation Scalar", Vector) = (1.0, 1.0, 1.0, 0.0) [PerRendererData] _CrossfadeStartTime ("Crossfade Start Time", float) = -1.0 [PerRendererData] _CrossfadeEndTime ("Crossfade End Time", float) = -1.0 // end MeshAnimator }
#pragma surface surf Lambert noforwardadd addshadowor
#pragma fragment frag addshadow
#pragma multi_compile_instancing
// start MeshAnimator #include "MeshAnimator.cginc" #pragma vertex vert struct appdata_ma { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; uint vertexId : SV_VertexID; UNITY_VERTEX_INPUT_INSTANCE_ID }; v2f vert (appdata_ma v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.vertex = UnityObjectToClipPos(ApplyMeshAnimation(v.vertex, v.vertexId)); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); UNITY_TRANSFER_DEPTH(v); UNITY_TRANSFER_FOG(o, o.vertex); return o; } // end MeshAnimator
#include "MeshAnimator.cginc"Include the MeshAnimator shader code. This path is relative to the location of the shader. By default it's located in the MeshAnimator/Shaders folder.
uint vertexId : SV_VertexID;Provides the function with a vertex index of the mesh.
o.vertex = ApplyMeshAnimation(v.vertex, v.vertexId);Fragment shaders:
o.vertex = UnityObjectToClipPos(ApplyMeshAnimation(v.vertex, v.vertexId));This will move the vertex using the properties provided by MeshAnimator.
Shader Mode ONLY No special action is required for URP to work with Mesh Animator in Snapshot mode.
Included with the asset are converted versions of the default Universal Render Pipeline shaders. They include:
Set mesh materials to use these shader versions when using Shader mode. They can be found under the Mesh Animator/Universal Render Pipeline/... shader directory.
Shader Mode ONLY No special action is required for HDRP to work with Mesh Animator in Snapshot mode.
HDRP shaders have not yet been converted. This is a WIP and will be released as soon as it's ready.
Shader Mode ONLY No special action is required for URP and HDRP to work with Mesh Animator in Snapshot mode.
Disclaimer: Shader graphs do not support instanced properties. GPU instancing will not occur when using these shader alongside Mesh Animator.
Included in the MeshAnimator/ShaderGraph folder is a sub graph and example shader that can be used to implement Mesh Animator into shader graphs.
Please follow the below steps to add the Mesh Animator Sub Graph.
All Mesh Animator scripts reside in the FSG.MeshAnimator namespace.
using FSG.MeshAnimator;
// The mesh that will be animated public Mesh baseMesh; // The default animation that will be played if playAutomatically = true public MeshAnimation defaultAnimation; // Array of animations on the animator public MeshAnimation[] animations; // Playback speed of the animator public float speed; // Continue playing animation when the animator is off screen public bool updateWhenOffscreen; // Should animation begin playing when the animator is enabled public bool playAutomatically; // Reset the animator OnEnable and play the default animation public bool resetOnEnable; // The target object to recieve AnimationEvents public GameObject eventReciever; // Get the current animation playing public MeshAnimation currentAnimation { get; } // The FPS the animations were baked at public int FPS; // Callback for when an animation has finished playing public ActionOnAnimationFinished; // Callback for when the mesh has changed frames public Action OnFrameUpdated; // Callback for when the object has entered or left the viewable screen public ActionOnVisibilityChanged;
// Crossfade an animation by index public void Crossfade(int index, float speed = 0.1f); meshAnimator.Crossfade(0, 0.25f); // Crossfade an animation by name public void Crossfade(string anim, float speed = 0.1f); meshAnimator.Crossfade("idle", 0.25f); // Play the default animation, or resume playing a paused animator public void Play(); meshAnimator.Play(); // Play an animation by name public void Play(string anim); meshAnimator.Play("idle"); // Play an animation by index public void Play(int index); meshAnimator.Play(0); // Play a random animation public void PlayRandom(params string[] anim); meshAnimator.Play("idle", "idle_bored", "dance", "sit"); // Play an animation after the previous one has finished public void PlayQueued(string anim); meshAnimator.Play("wave"); meshAnimator.PlayQueued("idle"); // Pause an animator, disabling the component also has the same effect public void Pause(); meshAnimator.Pause(); // Restart the current animation from the beginning public void RestartAnim(); meshAnimator.RestartAnim(); // Get the MeshAnimation by name public MeshAnimation GetClip(string clipname); MeshAnimation clip = meshAnimator.GetClip("idle");