• Unity3D 移动平台实现一种大规模(其实跟PC比还是算小规模)动画角色渲染的方案---绝对原创方案。。。


    手机硬件限制,很多PC上的渲染优化技术是没办法直接拿过来用的。目前有些游戏为了实现多部队战斗的效果,各种降低骨骼数目,模型面数的方案,但都逃不过骨骼动画计算这一环节。

    在上个公司的时候,自己瞎想了一张方案,没想到最后还写出来了, 没想到最后还用上了。。。

    先上张图,里面有100个士兵和10个萌宝宝的场景,每个角色的动作是分开控制的,在小米3上可以60fps的帧率流畅运行,之前也尝试过,300多个角色带动画也可以55左右的fps运行

     

    先说明一下这套方案的优缺点:

     优点:可不用计算骨骼动画,并能流畅播放骨骼动画(其实已经不是骨骼动画了);每个角色动作也可以单独控制;对cpu很小消耗;

     缺点:动作切换没有过渡(群体作战的时候,这点基本不影响审美);对内存占用有一点消耗(其实也还好)

    再说明方案思路:

       为了省掉骨骼动画计算这一环节,就不能用现有骨骼动画系统。那么可选的方案也只有顶点变形动画了,而传统的顶点动画不管是内存占用还是不低的,而且unity支持也不算好。

        那么既然只能选顶点动画且为了降低内存消耗,那么可以通过改写shader,通过gpu来插值实现顶点动画播放骨骼动画的效果。

     shader插值的方案确实也不少,但结合具体实现上来,我选了两种实现相对简单的方案:

        1.vertex shader里通过每帧采样的uv偏移采样纹理来控制顶点的位置,当我已经在pc端实现的时候才突然发现一个显而易见的问题:在vertex shader里采样纹理,pc端部分显卡是支持的,但手机gpu就不要想了。

        2.于是只能第二种方案:还是vextex shader里插值顶点来播放动画,那么就有一个问题,vertex data从哪里来? unity的mesh结构有很多通道,顶点,颜色,uv,uv2,法线,切线。。这些通道里面除了color(主要是精度问题)和uv(还是必须给纹理坐标一个位置的)通道以外我都可以用来存顶点数据,然后通过控制时间点来组合顶点进行插值,然而还有个问题需要解决,这种方式一个mesh只能插值4个关键顶点,那么较长的动画怎么办呢,可以通过提前生成多个mesh,来切换。

        动画截取可以通过unity的BakeMesh函数或者美工来帮助实现,下面是组合mesh 的代码:

    byte[] Make(Mesh mesh1, Mesh mesh2, Mesh mesh3, Mesh mesh4, float clipTimeLenghts, float frame2Pos, float frame3Pos)
    {
    Mesh[] meshs = new Mesh[] { mesh1, mesh2, mesh3, mesh4};
    VertexAnimationResManager.ClipMeshData meshData = new VertexAnimationResManager.ClipMeshData();
    meshData.subMeshCount = meshs[0].subMeshCount;
    
    int count = meshs[0].vertices.Length;
    //顶点
    if (meshs[0].vertices != null && meshs[0].vertices.Length > 0)
    {
    meshData.vertexBuffer = new float[count * 3];
    
    for (int i = 0; i < meshs[0].vertices.Length; i++)
    {
    meshData.vertexBuffer[i * 3] = meshs[0].vertices[i].x;
    meshData.vertexBuffer[i * 3 + 1] = meshs[0].vertices[i].y;
    meshData.vertexBuffer[i * 3 + 2] = meshs[0].vertices[i].z;
    } 
    }
    //uv
    if (meshs[0].uv != null && meshs[0].uv.Length > 0)
    {
    meshData.uvBuffer = new float[count * 2];
    
    for (int i = 0; i < meshs[0].vertices.Length; i++)
    {
    meshData.uvBuffer[i * 2] = meshs[0].uv[i].x;
    meshData.uvBuffer[i * 2 + 1] = meshs[0].uv[i].y;
    }
    
    //GCHandle verSrcHand = GCHandle.Alloc(meshs[0].uv, GCHandleType.Pinned);
    //Marshal.Copy(verSrcHand.AddrOfPinnedObject(), meshData.uvBuffer, 0, meshData.uvBuffer.Length);
    //verSrcHand.Free(); 
    }
    
    //法线 这里用来存动画第二帧的顶点信息
    if (meshs[1].vertices != null && meshs[1].vertices.Length > 0)
    {
    meshData.normalBuffer = new float[count * 3];
    
    for (int i = 0; i < meshs[0].vertices.Length; i++)
    {
    meshData.normalBuffer[i * 3] = meshs[1].vertices[i].x;
    meshData.normalBuffer[i * 3 + 1] = meshs[1].vertices[i].y;
    meshData.normalBuffer[i * 3 + 2] = meshs[1].vertices[i].z;
    }
    }
    
    //切线 这里用来存动画第三帧的顶点信息
    if (meshs[2].vertices != null && meshs[2].vertices.Length > 0)
    {
    meshData.tangentBuffer = new float[count * 4];
    
    for (int i = 0; i < meshs[0].vertices.Length; i++)
    {
    meshData.tangentBuffer[i * 4] = meshs[2].vertices[i].x;
    meshData.tangentBuffer[i * 4 + 1] = meshs[2].vertices[i].y;
    meshData.tangentBuffer[i * 4 + 2] = meshs[2].vertices[i].z;
    meshData.tangentBuffer[i * 4 + 3] = meshs[3].vertices[i].x;
    }
    }
    
    //UV2 用来存第四个关键帧率的 顶点YZ 坐标 X坐标由切线的W通道来存
    if (meshs[3].vertices != null && meshs[3].vertices.Length > 0)
    {
    meshData.uv2Buffer = new float[count * 2];
    
    for (int i = 0; i < meshs[0].vertices.Length; i++)
    {
    meshData.uv2Buffer[i * 2] = meshs[3].vertices[i].y;
    meshData.uv2Buffer[i * 2 + 1] = meshs[3].vertices[i].z;
    } 
    }
    
    //颜色 用来存第5个顶点信息
    //if (meshs[Indexs[4]].vertices != null && meshs[Indexs[4]].vertices.Length > 0)
    //{
    // //颜色通道貌似没有负数,且范围为0到1 所有这里需要将模型顶点映射到[0,1]之间,映射范围为[-1,1]之间
    
    // meshData.colorBuffer = new float[count * 4];
    // for (int i = 0; i < meshs[Indexs[4]].vertices.Length; i++)
    // {
    // meshData.colorBuffer[i * 4] = (meshs[Indexs[4]].vertices[i].x * 0.5f) + 0.5f;
    // meshData.colorBuffer[i * 4 + 1] = (meshs[Indexs[4]].vertices[i].y * 0.5f) + 0.5f;
    // meshData.colorBuffer[i * 4 + 2] = (meshs[Indexs[4]].vertices[i].z * 0.5f) + 0.5f;
    // }
    //}
    
    
    count = 0;
    int len = 0;
    meshData.subMeshTriangleLens = new int[meshData.subMeshCount];
    for (int i = 0; i < meshData.subMeshCount; i++)
    {
    len = meshs[0].GetTriangles(i).Length;
    count += len;
    meshData.subMeshTriangleLens[i] = len;
    }
    
    meshData.triangleBuffer = new int[count];
    
    len = 0;
    for (int i = 0; i < meshData.subMeshCount; i++)
    {
    meshs[0].GetTriangles(i).CopyTo(meshData.triangleBuffer, len);
    len += meshData.subMeshTriangleLens[i];
    }
    
    ByteBuffer bbuffer = new ByteBuffer();
    bbuffer.WriteFloat(clipTimeLenghts);
    bbuffer.WriteFloat(frame2Pos);
    bbuffer.WriteFloat(frame3Pos);
    bbuffer.WriteInt(meshs[0].subMeshCount);
    
    for(int i=0;i<meshData.subMeshTriangleLens.Length;i++ )
    {
    bbuffer.WriteInt(meshData.subMeshTriangleLens[i]);
    }
    
    bbuffer.WriteInt(meshData.triangleBuffer.Length);
    for (int i = 0; i < meshData.triangleBuffer.Length; i++)
    { 
    bbuffer.WriteInt(meshData.triangleBuffer[i]);
    }
    
    bbuffer.WriteInt(meshData.vertexBuffer.Length);
    for (int i = 0; i < meshData.vertexBuffer.Length; i++)
    {
    bbuffer.WriteFloat(meshData.vertexBuffer[i]);
    }
    
    bbuffer.WriteInt(meshData.normalBuffer.Length);
    for (int i = 0; i < meshData.normalBuffer.Length; i++)
    {
    bbuffer.WriteFloat(meshData.normalBuffer[i]);
    }
    
    bbuffer.WriteInt(meshData.tangentBuffer.Length);
    for (int i = 0; i < meshData.tangentBuffer.Length; i++)
    {
    bbuffer.WriteFloat(meshData.tangentBuffer[i]);
    }
    
    bbuffer.WriteInt(meshData.uvBuffer.Length);
    for (int i = 0; i < meshData.uvBuffer.Length; i++)
    {
    bbuffer.WriteFloat(meshData.uvBuffer[i]);
    }
    
    bbuffer.WriteInt(meshData.uv2Buffer.Length);
    for (int i = 0; i < meshData.uv2Buffer.Length; i++)
    {
    bbuffer.WriteFloat(meshData.uv2Buffer[i]);
    } 
    return bbuffer.ToBytes();
    }

     截取好后,保存为自己的二进制文件,运行的时候加载并 解析的代码如下:

      

    public void AddAnimationInfo(string aniName, byte[] clipData)
    {
    VertexAnimationClipInfo clipInfo = null;
    
    AnimationClipInfos.TryGetValue(aniName, out clipInfo);
    
    if(clipInfo!=null)
    {
    Debug.LogError("animation clip has exits!");
    return; 
    }
    
    clipInfo = new VertexAnimationClipInfo();
    
    ByteBuffer bbuffer = new ByteBuffer(clipData);
    int Count = bbuffer.ReadInt();
    
    for (int i = 0; i < Count; i++)
    {
    ClipMeshData meshData = GetMeshData(bbuffer);
    clipInfo.clipTotalTimeLen += meshData.timeLenth;
    clipInfo.clipLenghts.Add(meshData.timeLenth);
    clipInfo.everyClipFrameTimePoints.Add(new Vector3(meshData.Frame2TimePoint, meshData.Frame3TimePoint)); //,meshData.Frame4TimePoint
    clipInfo.clipMeshs.Add(meshData.GenMesh());
    }
    
    bbuffer.Close();
    
    AnimationClipInfos.Add(aniName, clipInfo);
    }

    VertexAnimationClipInfo定义如下:

    [Serializable]
    public class VertexAnimationClipInfo
    {
    public float clipTotalTimeLen = 0;
    public List<Mesh> clipMeshs = new List<Mesh>();
    public List<Vector2> everyClipFrameTimePoints = new List<Vector2>();
    public List<float> clipLenghts = new List<float>();
    }

    ClipMeshData定义如下:

    public class ClipMeshData
    {
    public float timeLenth;
    
    ///Frame1TimePoint =0 Frame4TimePoint = 1
    public float Frame2TimePoint = 0.333f;
    public float Frame3TimePoint = 0.666f;
    //public float Frame4TimePoint = 0.75f;
    
    public int subMeshCount;
    public int[] subMeshTriangleLens;
    public int[] triangleBuffer;
    public float[] vertexBuffer;
    public float[] normalBuffer;
    public float[] tangentBuffer;
    public float[] uvBuffer;
    public float[] uv2Buffer;
    //public float[] colorBuffer;
    
    public Mesh GenMesh()
    { 
    Mesh mesh = new Mesh();
    
    int vertexCount = vertexBuffer.Length / 3;
    
    mesh.subMeshCount = subMeshCount;
    //顶点
    Vector3[] vertexs = new Vector3[vertexCount];
    for (int i = 0; i < vertexCount; i++)
    {
    vertexs[i] = new Vector3(vertexBuffer[i * 3], vertexBuffer[i * 3 + 1], vertexBuffer[i * 3 + 2]);
    }
    mesh.vertices = vertexs; 
    //uv
    Vector2[] uv = new Vector2[vertexCount];
    for (int i = 0; i < uv.Length; i++)
    {
    uv[i] = new Vector2(uvBuffer[i * 2], uvBuffer[i * 2 + 1]);
    }
    mesh.uv = uv;
    //uv2
    Vector2[] uv2 = new Vector2[vertexCount];
    for (int i = 0; i < uv.Length; i++)
    {
    uv2[i] = new Vector2(uv2Buffer[i * 2], uv2Buffer[i * 2 + 1]);
    }
    mesh.uv2 = uv2;
    
    //法线 
    Vector3[] normals = new Vector3[vertexCount];
    for (int i = 0; i < normals.Length; i++)
    {
    normals[i] = new Vector3(normalBuffer[i * 3], normalBuffer[i * 3 + 1], normalBuffer[i * 3 + 2]);
    } 
    mesh.normals = normals;
    
    //切线
    var tangents = new Vector4[vertexCount]; 
    for (int i = 0; i < tangents.Length; i++)
    {
    tangents[i] = new Vector4(tangentBuffer[i * 4], tangentBuffer[i * 4 + 1], tangentBuffer[i * 4 + 2], tangentBuffer[i * 4 + 3]);
    } 
    mesh.tangents = tangents;
    ////颜色
    //Color[] colors = new Color[colorBuffer.Length / 4];
    //for (int i = 0; i < colors.Length; i++)
    //{
    // colors[i] = new Vector4(colorBuffer[i * 4], colorBuffer[i * 4 + 1], colorBuffer[i * 4 + 2], 1);
    //}
    //mesh.colors = colors;
    
    //三角形 
    int startIndex = 0;
    int bufferLen = 0;
    
    for (int i = 0; i < subMeshCount; i++)
    {
    bufferLen = subMeshTriangleLens[i];
    if (bufferLen <= 0) continue;
    var triIndexBuffer = new int[bufferLen];
    Array.Copy(triangleBuffer, startIndex, triIndexBuffer, 0, bufferLen);
    mesh.SetTriangles(triIndexBuffer, i);
    startIndex += bufferLen;
    }
    return mesh;
    }
    }

    播放动画切换对应的mesh就可以了,下面是gpu插值所用的shader代码:

    Shader "LXZ_TEST/VertexAnimation-NoColorBuf" {
    Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _CurTime("Time", Float) = 0 
    _Frame2Time("Frame2Time", Float) = 0.333
    _Frame3Time("Frame3Time", Float) = 0.666
    _Color ("MainColor", color) = (1,1,1,1)
    }
    SubShader {
    // Tags { "QUEUE"="Geometry" "RenderType"="Opaque" }
    
    Pass { 
    Blend SrcAlpha OneMinusSrcAlpha    
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // #include "UnityCG.cginc" 
    
    
    #pragma glsl_no_auto_normalization 
    
    sampler2D _MainTex;
    float _CurTime;
    float _Frame2Time;
    float _Frame3Time;
    float4 _Color;
    
    struct appdata {
    float4 vertex : POSITION;
    float3 vertex1 : NORMAL;
    float4 vertex2 : TANGENT;
    float2 texcoord : TEXCOORD0;
    float2 vertex3: TEXCOORD1;
    //float3 vertex4: COLOR;
    }; 
    
    struct v2f {
    float4 pos : POSITION;
    float2 uv : TEXCOORD0;
    };
    
    v2f vert(appdata v) {
    v2f result; 
    
    float a = _CurTime - _Frame2Time;
    float b = _CurTime - _Frame3Time;
    
    float3 vec; 
    
    float3 vertex3 = float3(v.vertex2.w,v.vertex3.xy);
    
    if(a<0)
    vec = v.vertex.xyz + (v.vertex1 - v.vertex.xyz)* _CurTime/_Frame2Time;
    else if(a>=0 && b<0)
    { 
    vec = v.vertex1 + (v.vertex2.xyz - v.vertex1)* a/(_Frame3Time-_Frame2Time); 
    } 
    else
    vec = v.vertex2.xyz + (vertex3 - v.vertex2.xyz)* b/(1-_Frame3Time); 
    
    
    result.pos = mul(UNITY_MATRIX_MVP, float4(vec,1)); 
    //result.pos = mul(UNITY_MATRIX_MVP, float4(v.vertex1.xyz,1)); 
    //result.pos = mul(UNITY_MATRIX_MVP, float4(v.vertex2.xyz,1)); 
    // result.pos = mul(UNITY_MATRIX_MVP, float4(vertex3.xyz,1)); 
    // result.pos = mul(UNITY_MATRIX_MVP, float4(vertex4.xyz,1)); 
    result.uv = v.texcoord; 
    
    return result;
    }
    
    float4 frag(v2f i) : COLOR
    {
    float4 color = tex2D(_MainTex, i.uv);
    return color *_Color;
    }
    
    ENDCG 
    }
    } 
    FallBack "Diffuse"
    }

    如果各位还有更好的方案,欢迎交流。。不擅长文字的东西,所以直接贴代码了,有需要可以与我连续交流。。。

  • 相关阅读:
    mysql索引
    数据库修复
    数据库取值 三级分类后台遍历
    创建数据库!
    mysql按条件 导出sql
    nodejs 简单安装环境
    C++ 性能剖析 (一)
    C++ 性能剖析 (二):值语义 (value semantics)
    JavaScript Nested Function 的时空和身份属性
    C++ Reference 的“三位一体”诠释
  • 原文地址:https://www.cnblogs.com/lxzCode/p/4780360.html
Copyright © 2020-2023  润新知