多人同屏渲染例子——3、生成VAT资源

Unity引擎制作万人同屏效果


  大家好,我是阿赵。
  之前介绍了怎样去用Shader播放顶点动画VAT。但这里有一个前提,就是这张顶点动画的贴图,是怎样生成的呢?
  由于是先说明了Shader的写法,所以可能会有一种先入为主的想法,是不是VAT贴图一定就要按照这种方式去播放呢?其实并不是。应该是反过来,由生成的格式决定播放的方法。不过由于要先说明VAT的原理,才好说明怎样去生成这类型的资源,所以我的说明是反过来了。
在这里插入图片描述

  现在再回头来看看这张VAT的贴图。之前漏了说一个比较重要的地方。由于我们需要获取的是绝对像素颜色来转换成顶点的坐标,所以这个颜色值的含义其实只是一个Vector3,也就是Shader里面的Float3,所以,sRGB不要勾选,这样不会受色彩空间的影响。然后FilterMode要选择成Point,也就是说没有任何插值。
  接下来开始分析我们需要的数据了。
  从比较简单的例子开始,假如现在有一个模型,它只包含一个网格,然后有6个动作,分别是idle、attack、damage、dead、run、walk。
1、生成UV2的网格
  首先,我们要通过UV2来读取VAT,那么我们需要有一个地方去存储UV2。而且由于是需要能在Shader直接读取的,所以存储在Mesh的UV2信息就很合适。但我们不一定能修改原始模型的Mesh,所以我的做法是复制一份原始模型的Mesh出来,把UV2设置进去。
设置UV2的方式也很简单,直接按照顶点的顺序设置就行。

        private void CreateTempMesh()
        {
            Mesh shareMesh = render.sharedMesh;
            tempMesh = new Mesh();
            Vector3[] tempVerts = new Vector3[shareMesh.vertices.Length];
            for(int i = 0;i<tempVerts.Length;i++)
            {
                tempVerts[i] = render.gameObject.transform.TransformPoint(shareMesh.vertices[i]);
            }
            tempMesh.vertices = tempVerts;
            tempMesh.uv = shareMesh.uv;
            tempMesh.triangles = shareMesh.triangles;
            tempMesh.normals = shareMesh.normals;
            tempMesh.tangents = shareMesh.tangents;
            
            Vector2[] uv2 = new Vector2[vertexCount];
            float maxSize = VertexAnimCommonTool.GetNearestTexSize(vertexCount);
            for(int i = 0;i<vertexCount;i++)
            {
                float u = (float)i / maxSize;
                uv2[i] = new Vector2(u, 0);
            }
            tempMesh.uv2 = uv2;
       //这里可以把网格保存下来,具体怎样保存就看个人需要
 }

2、 逐个动作保存信息
  有了UV2之后,根据上一篇的Shader里面需要的数据,我们还需要保存的信息有:

  1. 顶点坐标的范围最大最小值
  2. 动作每一帧的顶点坐标相对于最大最小值的比例
  3. 动作的最大帧率
  4. 动作的播放速度
  5. 贴图的最大高度
      由于这些信息,在不同动作时是不一样的,所以到这里是需要分开逐个动作去生成。
      主要的思路很简单,就是通过固定的帧率时间间隔,让带有Animator的动画跳转到固定某一帧,然后bake一个mesh出来,把上面的顶点信息都记录下来而已。旧版的Animation也一样。
      跳转动画主要是靠animator.playbackTime来控制的,不过我发现一个问题,这个api在Unity的非播放阶段控制起来比较困难。所以我的选择是,在导出工具运行的时候,先自动进入Unity的运行状态,再去控制动画播放。
    这是切换动作时的方法:
        private void ChangeAnim(string str)
        {
            float animLength = GetAnimLength(str);
            int frameCount = (int)(animLength * frameRate);
            animator.Rebind();
            animator.StopPlayback();
            animator.recorderStartTime = 0;
            animator.Play(str);
            // 开始记录指定的帧数
            animator.StartRecording(frameCount);
            for (var i = 0; i < frameCount - 1; i++)
            {
                // 记录每一帧
                animator.Update(1.0f / frameRate);
            }
            // 完成记录
            animator.StopRecording();
            animator.StartPlayback();
            animator.playbackTime = 0;
            animator.Update(0);
        }
然后通过获得动作的长度,然后根据帧率来遍历每一帧,记录每一帧的动作:
            for (int i = 0;i<frameCount;i++)
            {
                float curTime = i * frameTime;
                animator.playbackTime = curTime;
                animator.Update(0);
                foreach(KeyValuePair<string,SkinnedMeshRenderer>item in skinMeshDict)
                {
                    ParsePartFrameAnim(item.Key,animName);
                }
}

bake每一帧的mesh待用

        public void ParsePartFrameAnim(string animName)
        {
            if(render == null)
            {
                return;
            }
            if(anims == null||anims.ContainsKey(animName)==false)
            {
                return;
            }
            Mesh mesh = new Mesh();
            render.BakeMesh(mesh);
            anims[animName].AddToFrameMeshList(mesh);
     }
创建空贴图:
        private void CreateTempTexture()
        {
            texWidth = VertexAnimCommonTool.GetNearestTexSize(shareMesh.vertexCount);
            texHeight = VertexAnimCommonTool.GetNearestTexSize(frameCount);
            speed = 1 / time;
            tempTex = new Texture2D(texWidth, texHeight);
            Color blackColor = new Color(0, 0, 0, 1);
            for (int i = 0;i< texWidth; i++)
            {
                for(int j = 0;j< texHeight; j++)
                {
                    tempTex.SetPixel(i, j, blackColor);
                }
            }
            tempTex.Apply();
        }
最后记录信息:
        private void ParseFrameTex()
        {
            float minX = 10000;
            float minY = 10000;
            float minZ = 10000;
            float maxX = -10000;
            float maxY = -10000;
            float maxZ = -10000;
            int vertCount = shareMesh.vertexCount;
            if(frameMeshList == null)
            {
                return;
            }
            int frameCount = frameMeshList.Count;
            for (int i = 0; i < frameMeshList.Count; i++)
            {
                Vector3[] verts = frameMeshList[i].vertices;
                for (int j = 0; j < verts.Length; j++)
                {
                    Vector3 vert = verts[j];
                    if (vert.x < minX)
                    {
                        minX = vert.x;
                    }
                    if (vert.y < minY)
                    {
                        minY = vert.y;
                    }
                    if (vert.z < minZ)
                    {
                        minZ = vert.z;
                    }
                    if (vert.x > maxX)
                    {
                        maxX = vert.x;
                    }
                    if (vert.y > maxY)
                    {
                        maxY = vert.y;
                    }
                    if (vert.z > maxZ)
                    {
                        maxZ = vert.z;
                    }
                }
            }
            float offsetX = maxX - minX;
            float offsetY = maxY - minY;
            float offsetZ = maxZ - minZ;

            for (int i = 0; i < frameMeshList.Count; i++)
            {
                Vector3[] verts = frameMeshList[i].vertices;
                for (int j = 0; j < vertCount; j++)
                {
                    Vector3 vert = verts[j];
                    float r = (vert.x - minX) / offsetX;
                    float g = (vert.y - minY) / offsetY;
                    float b = (vert.z - minZ) / offsetZ;

                    Color col = new Color(r, g, b);
                    tempTex.SetPixel(j, i, col);
                }
            }

            tempTex.Apply();
            boundMin = new Vector3(minX, minY, minZ);
            boundMax = new Vector3(maxX, maxY, maxZ);
 }

3、 保存除了贴图以外的信息。
  每一帧的顶点数据,可以保存在贴图里面,但由于最大帧数、贴图高度、bound的最大最小值这些,是不适合保存在贴图里面的,所以,可以单独保存在一个txt文本里面,然后运行的时候再读取出来,赋予给对应的材质球。
  还有,如果原来的材质比较规范,我们可以通过复制原来的模型的材质球上面的_MainTex,这样就可以方便很多。
  最后,把导出的资源都放在一起,一个角色最终导出的资源会是这样:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值