目前使用的方法有一个很大缺陷在于基于顶点生成的动画占用的空间很大,一个理想的情况是基于骨骼数据,本文权当抛砖引玉,后续有时间考虑尝试一下基于骨骼数据生成动画。
本文内容大量参考自白菊花瓣丶的视频,感谢!
生成动画数据需要用到ComputeShader来提高运行的效率,首先在Resources下创建这样一个compute shader,在这里我将其命名为"AnimVertices"。
#pragma kernel CSMain
RWTexture2D<float4> Result;
StructuredBuffer<float3> vertices;
int time;
[numthreads(8,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Result[uint2(id.x, time)]= float4(vertices[id.x], 1.0);
}
在前面的介绍里提到该方法对顶点和动画帧数有要求,是因为顶点和动画帧数实实在在的影响了生成的动画数据大小,首先使用顶点数作为宽,动画帧数作为高,可以确定所需的图片大小为顶点数*动画帧数*16,16是因为这里使用的颜色格式为ARGBHalf。
int animLength = (int)(60 * animation.length);
//animLength = Mathf.NextPowerOfTwo(animLength);
int texwidth = mesh.sharedMesh.vertexCount;
texwidth = Mathf.NextPowerOfTwo(texwidth);
Texture2D tex = new Texture2D(texwidth, animLength, TextureFormat.RGBAHalf, false);
RenderTexture rt = new RenderTexture(texwidth, animLength, 0, RenderTextureFormat.ARGBHalf);
动画帧数直接使用动画长度*运行帧数来获得,同时直接通过Mesh获取顶点数(Mathf.NextPowerOfTwo可以获得大于等于传入参数的2的幂的数,据说利于压缩,这里未做求证),格式使用ARGBHalf是因为顶点位置可正可负,实际使用中精度可能不足,可视情况调整。
cs.SetTexture(0, "Result", rt);
for (int i = 0; i < animLength; i++)
{
float time = i / 60f;
animation.SampleAnimation(gameObject, time);
Mesh bakeMesh = Instantiate<Mesh>(mesh.sharedMesh);
mesh.BakeMesh(bakeMesh);
Vector3[] vertices = bakeMesh.vertices;
int size = sizeof(int) * 3;
//Debug.LogError(i + " " + size + " " + time);
ComputeBuffer cb = new ComputeBuffer(vertices.Length, 12);
cb.SetData(vertices);
cs.SetInt("time", i);
cs.SetBuffer(0, "vertices", cb);
cs.Dispatch(0, Mathf.CeilToInt(vertices.Length / 8.0f), 1, 1);
cb.Release();
}
创建render texture之后,赋值给到compute shader的Result属性,每帧动画就是图片数据里的一行,采样动画数据,烘焙获得Mesh之后,将烘焙后的顶点直接传入cs。
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, texwidth, animLength), 0, 0);
tex.Apply();
AssetDatabase.CreateAsset(tex, $"Assets/Resources/{AnimTexName}.asset");
最后同样使用AssetDatabase.CreateAsset将动画数据保存下来,这里我们拿到如下的一个数据。