【Unity3D】 合并mesh那些事 CombineMeshes(二)

上期(https://blog.csdn.net/tw_345/article/details/79771454)简单绘制了几个mesh,然而合并他们感觉没什么意义。于是我稍微深入一下,再引入几个概念,Material(材质)、Map(贴图)、Texture(纹理)。他们应该是使单调的mesh有个皮肤。一般情况下,这三者的层级关系是:Material包含Map,Map包含Texture。
Texture在游戏领域基本用的是位图,是最基本的数据输入单位。Map把Texture通过UV映射到3D物体表面(除了UV坐标。Map还包含Texture以外的其他很多信息)。Material是一个数据集,Map就是其中的一部分,另外,还有一个重要部分是Shader,用以在着色流水线改变渲染效果。(了解不是很深就先这样理解吧)
有了这些概念,我就可以渲染很多角色和场景了。然后再引入几个概念,动画,骨骼。unity中对以上的概念解释可以参考:

https://www.jianshu.com/p/f3f6d4a7940d (为Unity3D创建素材(1):图片、着色器、材质球)
https://www.jianshu.com/p/e2aa4d898fb7 (为Unity3D创建素材(2):模型、绑定、动画)
他写的很详细。

所以,合并mesh不只是处理mesh,还涉及到很多元素。我在github找到一个换装的实现。继续研究吧。
https://github.com/GITHUB243884919/ChangeEQForUnity
看了下这个实现,效果大致是:
【Unity3D】 合并mesh那些事 CombineMeshes(二)_第1张图片
其核心代码是:

 /// 
    /// Combine SkinnedMeshRenderers together and share one skeleton.
    /// Merge materials will reduce the drawcalls, but it will increase the size of memory. 
    /// 
    /// skeleton是一个只带transfrom的prefab,很形象,就是一个骨架。
    /// meshes 是各个身体部件上的SkinnedMeshRenderer组件。
    /// 因为最终要合并,所以并不用把各部件的骨头设置父节点成骨架。
    /// 然而武器就不一样了,武器并不参与合并,所以还是要设置父节点。
    /// 
    /// combine meshes to this skeleton(a gameobject)
    /// meshes need to be merged
    /// merge materials or not
    public void CombineObject (GameObject skeleton, 
        SkinnedMeshRenderer[] meshes, bool combine = false)
    {

        // Fetch all bones of the skeleton
        // transforms是骨架上所有的transform组件的List
        List transforms = new List();
        transforms.AddRange(skeleton.GetComponentsInChildren(true));

        // the list of materials 
        // materials是所有身体部件上的所有Material组成的List
        List materials = new List();

        // the list of meshes
        // combineInstances是所有身体部件上所有的mesh组成的List
        List combineInstances = new List();

        //the list of bones
        List bones = new List();

        // Below informations only are used for merge materilas(bool combine = true)
        List oldUV = null;
        Material newMaterial = null;
        Texture2D newDiffuseTex = null;

        // Collect information from meshes
        // 这里分别把所有Material,Mesh,Transform(骨头)保存到对应的List
        for (int i = 0; i < meshes.Length; i ++)
        {
            SkinnedMeshRenderer smr = meshes[i];
            // Collect materials
            materials.AddRange(smr.materials); 

            // Collect meshes
            for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = smr.sharedMesh;
                ci.subMeshIndex = sub;
                combineInstances.Add(ci);
            }

            // Collect bones
            // 收集骨头有点区别:只收集骨架中有的。这应该会涉及到具体的美术标准。
            for (int j = 0 ; j < smr.bones.Length; j ++)
            {
                int tBase = 0;
                for (tBase = 0; tBase < transforms.Count; tBase++)
                {
                    if (smr.bones[j].name.Equals(transforms[tBase].name))
                    {
                        //Debug.Log("Equals bones " + smr.bones[j].name);
                        bones.Add(transforms[tBase]);
                        break;
                    }
                }
            }
        }

        // merge materials
        // Material合并,主要是处理贴图和其UV
        if (combine)
        {
            newMaterial = new Material (Shader.Find ("Mobile/Diffuse"));
            oldUV = new List();
            // merge the texture
            // 合并贴图(从收集的各Material中取)
            // Textures是所有贴图组成的列表
            List Textures = new List();
            for (int i = 0; i < materials.Count; i++)
            {
                Textures.Add(materials[i].GetTexture(COMBINE_DIFFUSE_TEXTURE) as Texture2D);
            }

            //所有贴图合并到newDiffuseTex这张大贴图上
            newDiffuseTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
            Rect[] uvs = newDiffuseTex.PackTextures(Textures.ToArray(), 0);
            newMaterial.mainTexture = newDiffuseTex;

            // reset uv
            // 根据原来单个上的uv算出合并后的uv,uva是单个的,uvb是合并后的。
            // uva取自combineInstances[j].mesh.uv
            // 用oldUV保存uva。为什么要保存uva?它不是单个吗?先跳过往下看
            // 计算好uvb赋值到到combineInstances[j].mesh.uv
            Vector2[] uva, uvb;
            for (int j = 0; j < combineInstances.Count; j++)
            {
                //uva = (Vector2[])(combineInstances[j].mesh.uv);
                uva = combineInstances[j].mesh.uv;
                uvb = new Vector2[uva.Length];
                for (int k = 0; k < uva.Length; k++)
                {
                    uvb[k] = new Vector2((uva[k].x * uvs[j].width) + uvs[j].x, (uva[k].y * uvs[j].height) + uvs[j].y);
                }
                //oldUV.Add(combineInstances[j].mesh.uv);
                oldUV.Add(uva);
                combineInstances[j].mesh.uv = uvb;
            }
        }

        // Create a new SkinnedMeshRenderer
        SkinnedMeshRenderer oldSKinned = 
            skeleton.GetComponent ();
        if (oldSKinned != null) 
        {
            GameObject.DestroyImmediate (oldSKinned);
        }
        SkinnedMeshRenderer r = skeleton.AddComponent();
        r.sharedMesh = new Mesh();
        // Combine meshes
        r.sharedMesh.CombineMeshes(combineInstances.ToArray(), combine, false);
        // Use new bones
        r.bones = bones.ToArray();
        if (combine)
        {
            Debug.Log("combine " + combine);
            r.material = newMaterial;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                // 这为什么要用oldUV,这不是保存的uva吗?它是单个的uv呀?
                // 原因在于,这行代码其实并不影响显示,影响显示的是在Mesh合并前的uv。
                // 这行的意义在于合并后,又要换部件时,在新的合并过程中找到正确的单个uv。
                // 也是oldUV存在的意义。
                combineInstances[i].mesh.uv = oldUV[i];
            }
        }
        else
        {
            Debug.Log("combine " + combine);
            r.materials = materials.ToArray();
        }
    }

这里涉及到了骨骼的合并。值得注意的是,参数是SkinnedMeshRenderer,而不是之前的MeshRenderer。有什么区别呢?继续研究一下。
找到一篇不错的文章:https://blog.csdn.net/n5/article/details/3105872 (花了一些功夫终于找到了原创,为什么很多人喜欢摘抄,却又摘抄不全?)
这里以动画入手,解释了骨骼蒙皮动画的来由和原理。看来换装这个功能的核心技术应该是Skinned Mesh,而CombineMeshes 只是其实现的必要一步。
看了几遍大神的文章,大概弄懂了。因为骨骼蒙皮动画不像关节动画的多mesh,它的顶点信息是受骨骼和skin info数据影响的。动画数据控制骨骼,而mesh随之改变。骨骼只有一套所以mesh需要合并。
最后,还有一个问题,材质需要合并成一个么?

你可能感兴趣的:(unity3d)