/* * Created by jiadong chen * https://jiadong-chen.medium.com/ * 用来烘焙动作贴图。烘焙对象使用Animation组件,并且在导入时设置Rig为Legacy */ using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.Linq; using System.IO; /// /// 保存需要烘焙的动画的相关数据 /// public struct AnimData { #region FIELDS private int _vertexCount; private int _mapWidth; private readonly List _animClips; private string _name; private Animation _animation; private SkinnedMeshRenderer _skin; public List AnimationClips => _animClips; public int MapWidth => _mapWidth; public string Name => _name; #endregion public AnimData(Animation anim, SkinnedMeshRenderer smr, string goName) { _vertexCount = smr.sharedMesh.vertexCount; _mapWidth = Mathf.NextPowerOfTwo(_vertexCount); _animClips = new List(anim.Cast()); _animation = anim; _skin = smr; _name = goName; } #region METHODS public void AnimationPlay(string animName) { _animation.Play(animName); } public void SampleAnimAndBakeMesh(ref Mesh m) { SampleAnim(); BakeMesh(ref m); } private void SampleAnim() { if (_animation == null) { Debug.LogError("animation is null!!"); return; } _animation.Sample(); } private void BakeMesh(ref Mesh m) { if (_skin == null) { Debug.LogError("skin is null!!"); return; } _skin.BakeMesh(m); } #endregion } /// /// 烘焙后的数据 /// public struct BakedData { #region FIELDS private readonly string _name; private readonly float _animLen; private readonly byte[] _rawAnimMap; private readonly int _animMapWidth; private readonly int _animMapHeight; #endregion public BakedData(string name, float animLen, Texture2D animMap) { _name = name; _animLen = animLen; _animMapHeight = animMap.height; _animMapWidth = animMap.width; _rawAnimMap = animMap.GetRawTextureData(); } public int AnimMapWidth => _animMapWidth; public string Name => _name; public float AnimLen => _animLen; public byte[] RawAnimMap => _rawAnimMap; public int AnimMapHeight => _animMapHeight; } /// /// 烘焙器 /// public class AnimMapBaker{ #region FIELDS private AnimData? _animData = null; private Mesh _bakedMesh; private readonly List _vertices = new List(); private readonly List _bakedDataList = new List(); #endregion #region METHODS public void SetAnimData(GameObject go) { if(go == null) { Debug.LogError("go is null!!"); return; } var anim = go.GetComponent(); var smr = go.GetComponentInChildren(); if(anim == null || smr == null) { Debug.LogError("anim or smr is null!!"); return; } _bakedMesh = new Mesh(); _animData = new AnimData(anim, smr, go.name); } public List Bake() { if(_animData == null) { Debug.LogError("bake data is null!!"); return _bakedDataList; } //每一个动作都生成一个动作图 foreach (var t in _animData.Value.AnimationClips) { if(!t.clip.legacy) { Debug.LogError(string.Format($"{t.clip.name} is not legacy!!")); continue; } BakePerAnimClip(t); } return _bakedDataList; } private void BakePerAnimClip(AnimationState curAnim) { var curClipFrame = 0; float sampleTime = 0; float perFrameTime = 0; curClipFrame = Mathf.ClosestPowerOfTwo((int)(curAnim.clip.frameRate * curAnim.length)); perFrameTime = curAnim.length / curClipFrame; ; var animMap = new Texture2D(_animData.Value.MapWidth, curClipFrame, TextureFormat.RGBAHalf, true); animMap.name = string.Format($"{_animData.Value.Name}_{curAnim.name}.animMap"); _animData.Value.AnimationPlay(curAnim.name); for (var i = 0; i < curClipFrame; i++) { curAnim.time = sampleTime; _animData.Value.SampleAnimAndBakeMesh(ref _bakedMesh); for(var j = 0; j < _bakedMesh.vertexCount; j++) { var vertex = _bakedMesh.vertices[j]; animMap.SetPixel(j, i, new Color(vertex.x, vertex.y, vertex.z)); } sampleTime += perFrameTime; } animMap.Apply(); _bakedDataList.Add(new BakedData(animMap.name, curAnim.clip.length, animMap)); } #endregion }