skinMesh用贴图实现

参考:
https://connect.unity.com/p/render-crowd-of-animated-characters?signup=true
https://github.com/chenjd/Render-Crowd-Of-Animated-Characters

注意几点:
mesh不太像例子里面一样有scale,这样导致选中物体却找不到物体,因为被缩放为0.01;
动画需要是legacy
gpu instance需要opengl es3.0;
注意在shader里面勾选enable instance选项;
贴图格式为rgbhalf,每个通道16位,增加精度以保存坐标;
shader里面从贴图中采样要用tex2dLod确保采样的是mipmap第0级,因为这里的贴图不是普通意义上的贴图,而主要作用是存放信息。

另外,这个代码有些问题,color里面直接存放坐标,坐标为负数怎么办
所以博主对其进行了修改,先获取所有帧,所有顶点的最小值和最大值,然后每个顶点存放相应的0到1之间的比值。同时,需要把shader里面传入顶点坐标的最大值、最小值。
存放顶点信息的代码如下:

        Vector3 minPos = new Vector3(0, 0, 0);
        Vector3 maxPos = new Vector3(0, 0, 0);
        //所有帧,所有坐标,最大最小值
        for(int i = 0; i < curClipFrame; i++)
        {
            curAnim.time = sampleTime;
            this.animData.Value.SampleAnimAndBakeMesh(ref this.bakedMesh);
            for (int j = 0; j < this.bakedMesh.vertexCount; j++)
            {
                Vector3 vertex = this.bakedMesh.vertices[j];
                if (vertex.x > maxPos.x)
                    maxPos.x = vertex.x;
                else if (vertex.x < minPos.x)
                    minPos.x = vertex.x;

                if (vertex.y > maxPos.y)
                    maxPos.y = vertex.y;
                else if (vertex.y < minPos.y)
                    minPos.y = vertex.y;

                if (vertex.z > maxPos.z)
                    maxPos.z = vertex.z;
                else if (vertex.z < minPos.z)
                    minPos.z = vertex.z;
            }
            sampleTime += perFrameTime;
        }
        sampleTime = 0;
        for (int i = 0; i < curClipFrame; i++)
        {
            curAnim.time = sampleTime;

            this.animData.Value.SampleAnimAndBakeMesh(ref this.bakedMesh);
            //x轴,顶点,y轴帧
            for (int j = 0; j < this.bakedMesh.vertexCount; j++)
            {
                Vector3 vertex = this.bakedMesh.vertices[j];
                Vector3 diff = new Vector3(0.0f, 0.0f, 0.0f);
                diff.x = (vertex.x - minPos.x) / (maxPos.x - minPos.x);
                diff.y = (vertex.y - minPos.y) / (maxPos.y - minPos.y);
                diff.z = (vertex.z - minPos.z) / (maxPos.z - minPos.z);
                animMap.SetPixel(j, i, new Color(diff.x, diff.y, diff.z));
            }
            sampleTime += perFrameTime;
        }
        animMap.Apply();

shader里面:

v2f vert (appdata v, uint vid : SV_VertexID)
            {
                UNITY_SETUP_INSTANCE_ID(v);

                float f = _Time.y / _AnimLen;

                fmod(f, 1.0);

                float animMap_x = (vid + 0.5) * _AnimMap_TexelSize.x;
                float animMap_y = f;

                //这里贴图里包含的坐标而不是普通的贴图,所以要用lod0
                float4 pos = tex2Dlod(_AnimMap, float4(animMap_x, animMap_y, 0, 0));
                float3 diff = float3(0, 0, 0);
                diff.x = (_MaxPos.x - _MinPos.x) * pos.x;
                diff.y = (_MaxPos.y - _MinPos.y) * pos.y;
                diff.z = (_MaxPos.z - _MinPos.z) * pos.z;
                pos.xyz = _MinPos.xyz + diff;

                v2f o;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.vertex = UnityObjectToClipPos(pos);
                /*v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);*/
                return o;
            }

完整代码如下:
AnimMapBakerWindow.cs

/*
 * Created by jiadong chen
 * http://www.chenjd.me
 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class AnimMapBakerWindow : EditorWindow {

    private enum SaveStrategy
    {
        AnimMap,//only anim map
        Mat,//with shader
        Prefab//prefab with mat
    }

    #region 字段

    public static GameObject targetGo;
    private static AnimMapBaker baker;
    private static string path = "DefaultPath";
    private static string subPath = "SubPath";
    private static SaveStrategy stratege = SaveStrategy.AnimMap;
    private static Shader animMapShader;

    #endregion


    #region  方法

    [MenuItem("Window/AnimMapBaker")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(AnimMapBakerWindow));
        baker = new AnimMapBaker();
        animMapShader = Shader.Find("chenjd/AnimMapShader");
    }

    void OnGUI()
    {
        targetGo = (GameObject)EditorGUILayout.ObjectField(targetGo, typeof(GameObject), true);
        subPath = targetGo == null ? subPath : targetGo.name;
        EditorGUILayout.LabelField(string.Format("保存路径output path:{0}", Path.Combine(path, subPath)));
        path = EditorGUILayout.TextField(path);
        subPath = EditorGUILayout.TextField(subPath);

        stratege = (SaveStrategy)EditorGUILayout.EnumPopup("保存策略output type:", stratege);


        if (GUILayout.Button("Bake"))
        {
            if(targetGo == null)
            {
                EditorUtility.DisplayDialog("err", "targetGo is null!", "OK");
                return;
            }

            if(baker == null)
            {
                baker = new AnimMapBaker();
            }

            baker.SetAnimData(targetGo);

            List<BakedData> list = baker.Bake();

            if(list != null)
            {
                for(int i = 0; i < list.Count; i++)
                {
                    BakedData data = list[i];
                    Save(ref data);
                }
            }
        }
    }


    private void Save(ref BakedData data)
    {
        switch(stratege)
        {
            case SaveStrategy.AnimMap:
                SaveAsAsset(ref data);
                break;
            case SaveStrategy.Mat:
                SaveAsMat(ref data);
                break;
            case SaveStrategy.Prefab:
                SaveAsPrefab(ref data);
                break;
        }
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    private Texture2D SaveAsAsset(ref BakedData data)
    {
        string folderPath = CreateFolder();
        Texture2D animMap = new Texture2D(data.animMapWidth, data.animMapHeight, TextureFormat.RGBAHalf, false);

        animMap.LoadRawTextureData(data.rawAnimMap);
        AssetDatabase.CreateAsset(animMap, Path.Combine(folderPath, data.name + ".asset"));
        return animMap;
    }

    private Material SaveAsMat(ref BakedData data)
    {
        if(animMapShader == null)
        {
            EditorUtility.DisplayDialog("err", "shader is null!!", "OK");
            return null;
        }

        if(targetGo == null || !targetGo.GetComponentInChildren<SkinnedMeshRenderer>())
        {
            EditorUtility.DisplayDialog("err", "SkinnedMeshRender is null!!", "OK");
            return null;
        }

        SkinnedMeshRenderer smr = targetGo.GetComponentInChildren<SkinnedMeshRenderer>();
        Material mat = new Material(animMapShader);
        Texture2D animMap = SaveAsAsset(ref data);
        mat.SetTexture("_MainTex", smr.sharedMaterial.mainTexture);
        mat.SetTexture("_AnimMap", animMap);
        mat.SetFloat("_AnimLen", data.animLen);
        Vector4 minPos = new Vector4(data.minPos.x, data.minPos.y, data.minPos.z, 0.0f);
        Vector4 maxPos = new Vector4(data.maxPos.x, data.maxPos.y, data.maxPos.z, 0.0f);
        mat.SetVector("_MinPos", minPos);
        mat.SetVector("_MaxPos", maxPos);

        string folderPath = CreateFolder();
        AssetDatabase.CreateAsset(mat, Path.Combine(folderPath, data.name + ".mat"));

        return mat;
    }

    private void SaveAsPrefab(ref BakedData data)
    {
        Material mat = SaveAsMat(ref data);

        if(mat == null)
        {
            EditorUtility.DisplayDialog("err", "mat is null!!", "OK");
            return;
        }

        GameObject go = new GameObject();
        go.AddComponent<MeshRenderer>().sharedMaterial = mat;
        go.AddComponent<MeshFilter>().sharedMesh = targetGo.GetComponentInChildren<SkinnedMeshRenderer>().sharedMesh;
        string folderPath = CreateFolder();
        PrefabUtility.CreatePrefab(Path.Combine(folderPath, data.name + ".prefab").Replace("\\", "/"), go);
    }

    private string CreateFolder()
    {
        string folderPath = Path.Combine("Assets/" + path,  subPath);
        if (!AssetDatabase.IsValidFolder(folderPath))
        {
            AssetDatabase.CreateFolder("Assets/" + path, subPath);
        }
        return folderPath;
    }

    #endregion


}

AnimMapBaker

/*
 * Created by jiadong chen
 * http://www.chenjd.me
 * 
 * 用来烘焙动作贴图。烘焙对象使用animation组件,并且在导入时设置Rig为Legacy
 */
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

/// <summary>
/// 保存需要烘焙的动画的相关数据
/// </summary>
public struct AnimData
{
    #region 字段

    public int vertexCount;
    public int mapWidth;
    public List<AnimationState> animClips;
    public string name;

    private  Animation animation;
    private SkinnedMeshRenderer skin;

    #endregion

    public AnimData(Animation anim, SkinnedMeshRenderer smr, string goName)
    {
        vertexCount = smr.sharedMesh.vertexCount;
        mapWidth = Mathf.NextPowerOfTwo(vertexCount);
        animClips = new List<AnimationState>(anim.Cast<AnimationState>());
        animation = anim;
        skin = smr;
        name = goName;
    }

    #region 方法

    public void AnimationPlay(string animName)
    {
        this.animation.Play(animName);
    }

    public void SampleAnimAndBakeMesh(ref Mesh m)
    {
        this.SampleAnim();
        this.BakeMesh(ref m);
    }

    private void SampleAnim()
    {
        if (this.animation == null)
        {
            Debug.LogError("animation is null!!");
            return;
        }

        this.animation.Sample();
    }

    private void BakeMesh(ref Mesh m)
    {
        if (this.skin == null)
        {
            Debug.LogError("skin is null!!");
            return;
        }

        this.skin.BakeMesh(m);
    }


    #endregion

}

/// <summary>
/// 烘焙后的数据
/// </summary>
public struct BakedData
{
    #region 字段

    public string name;
    public float animLen;
    public byte[] rawAnimMap;
    public int animMapWidth;
    public int animMapHeight;
    public Vector3 minPos;
    public Vector3 maxPos;

    #endregion

    public BakedData(string name, float animLen, Texture2D animMap, Vector3 minPos, Vector3 maxPos)
    {
        this.name = name;
        this.animLen = animLen;
        this.animMapHeight = animMap.height;
        this.animMapWidth = animMap.width;
        this.rawAnimMap = animMap.GetRawTextureData();
        this.maxPos = maxPos;
        this.minPos = minPos;
    }
}

/// <summary>
/// 烘焙器
/// </summary>
public class AnimMapBaker{

    #region 字段

    private AnimData? animData = null;
    private List<Vector3> vertices = new List<Vector3>();
    private Mesh bakedMesh;

    private List<BakedData> bakedDataList = new List<BakedData>();

    #endregion

    #region 方法

    public void SetAnimData(GameObject go)
    {
        if(go == null)
        {
            Debug.LogError("go is null!!");
            return;
        }

        Animation anim = go.GetComponent<Animation>();
        SkinnedMeshRenderer smr = go.GetComponentInChildren<SkinnedMeshRenderer>();

        if(anim == null || smr == null)
        {
            Debug.LogError("anim or smr is null!!");
            return;
        }
        this.bakedMesh = new Mesh();
        this.animData = new AnimData(anim, smr, go.name);
    }

    public List<BakedData> Bake()
    {
        if(this.animData == null)
        {
            Debug.LogError("bake data is null!!");
            return this.bakedDataList;
        }

        //每一个动作都生成一个动作图
        for(int i = 0; i < this.animData.Value.animClips.Count; i++)
        {
            if(!this.animData.Value.animClips[i].clip.legacy)
            {
                Debug.LogError(string.Format("{0} is not legacy!!", this.animData.Value.animClips[i].clip.name));
                continue;
            }

            BakePerAnimClip(this.animData.Value.animClips[i]);
        }

        return this.bakedDataList;
    }

    private void BakePerAnimClip(AnimationState curAnim)
    {
        int curClipFrame = 0;
        float sampleTime = 0;
        float perFrameTime = 0;

        //获取总帧数(帧率乘以秒数) 转换成2的幂
        curClipFrame = Mathf.ClosestPowerOfTwo((int)(curAnim.clip.frameRate * curAnim.length));
        //总秒数/总帧数 获得每帧的时间(s)
        perFrameTime = curAnim.length / curClipFrame;

        //mapWidth是顶点数 大的最小的2的幂
        Texture2D animMap = new Texture2D(this.animData.Value.mapWidth, curClipFrame, TextureFormat.RGBAHalf, false);

        animMap.name = string.Format("{0}_{1}.animMap", this.animData.Value.name, curAnim.name);
        this.animData.Value.AnimationPlay(curAnim.name);
        Vector3 minPos = new Vector3(0, 0, 0);
        Vector3 maxPos = new Vector3(0, 0, 0);
        //所有帧,所有坐标,最大最小值
        for(int i = 0; i < curClipFrame; i++)
        {
            curAnim.time = sampleTime;
            this.animData.Value.SampleAnimAndBakeMesh(ref this.bakedMesh);
            for (int j = 0; j < this.bakedMesh.vertexCount; j++)
            {
                Vector3 vertex = this.bakedMesh.vertices[j];
                if (vertex.x > maxPos.x)
                    maxPos.x = vertex.x;
                else if (vertex.x < minPos.x)
                    minPos.x = vertex.x;

                if (vertex.y > maxPos.y)
                    maxPos.y = vertex.y;
                else if (vertex.y < minPos.y)
                    minPos.y = vertex.y;

                if (vertex.z > maxPos.z)
                    maxPos.z = vertex.z;
                else if (vertex.z < minPos.z)
                    minPos.z = vertex.z;
            }
            sampleTime += perFrameTime;
        }
        sampleTime = 0;
        for (int i = 0; i < curClipFrame; i++)
        {
            curAnim.time = sampleTime;

            this.animData.Value.SampleAnimAndBakeMesh(ref this.bakedMesh);
            //x轴,顶点,y轴帧
            for (int j = 0; j < this.bakedMesh.vertexCount; j++)
            {
                Vector3 vertex = this.bakedMesh.vertices[j];
                Vector3 diff = new Vector3(0.0f, 0.0f, 0.0f);
                diff.x = (vertex.x - minPos.x) / (maxPos.x - minPos.x);
                diff.y = (vertex.y - minPos.y) / (maxPos.y - minPos.y);
                diff.z = (vertex.z - minPos.z) / (maxPos.z - minPos.z);
                animMap.SetPixel(j, i, new Color(diff.x, diff.y, diff.z));
            }
            sampleTime += perFrameTime;
        }
        animMap.Apply();

        this.bakedDataList.Add(new BakedData(animMap.name, curAnim.clip.length, animMap, minPos, maxPos));
    }
    #endregion
}

AnimMapShader

/*
Created by jiadong chen
http://www.chenjd.me
*/

Shader "chenjd/AnimMapShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AnimMap ("AnimMap", 2D) ="white" {}
        _AnimLen("Anim Length", Float) = 0
        _MinPos("Min Pos", Vector) = (0.0, 0, 0, 0)
        _MaxPos("Max Pos", Vector) = (0.0, 0, 0, 0)
    }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
            LOD 100
            Cull off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //开启gpu instancing
            #pragma multi_compile_instancing


            #include "UnityCG.cginc"
            #pragma target 3.0

            struct appdata
            {
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _AnimMap;
            float4 _AnimMap_TexelSize;//x == 1/width

            float4 _MinPos;
            float4 _MaxPos;

            float _AnimLen;


            v2f vert (appdata v, uint vid : SV_VertexID)
            {
                UNITY_SETUP_INSTANCE_ID(v);

                float f = _Time.y / _AnimLen;

                fmod(f, 1.0);

                float animMap_x = (vid + 0.5) * _AnimMap_TexelSize.x;
                float animMap_y = f;

                //这里贴图里包含的坐标而不是普通的贴图,所以要用lod0
                float4 pos = tex2Dlod(_AnimMap, float4(animMap_x, animMap_y, 0, 0));
                float3 diff = float3(0, 0, 0);
                diff.x = (_MaxPos.x - _MinPos.x) * pos.x;
                diff.y = (_MaxPos.y - _MinPos.y) * pos.y;
                diff.z = (_MaxPos.z - _MinPos.z) * pos.z;
                pos.xyz = _MinPos.xyz + diff;

                v2f o;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.vertex = UnityObjectToClipPos(pos);
                /*v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);*/
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

场景可参考:
https://github.com/shaojunping/skinMeshBaker

猜你喜欢

转载自blog.csdn.net/qq_18229381/article/details/80546468