首页 > 其他分享 >Unity中的网格与材质球合并

Unity中的网格与材质球合并

时间:2024-10-31 20:21:15浏览次数:4  
标签:mTmp 合并 网格 Unity mesh Texture2D new 材质

很多时候我们需要把具有相同shader的材质球合并,从而减少drawcall的产生。  

比如九龙战里面,一个人物带有10个部位,10个部位各自来自不同的fbx文件,加上身体,就有11个材质球,占上11个drawcall。如果主城里面跑着10个角色,光人物就占了110个drawcall!所以这种时候材质球合并是必须的。

(下图为九龙战里面的多部位换装效果)


材质球合并,分以下几步走,首先我们讨论普通的MeshRenderer的材质球合并,然后再讨论SkinnedMeshRenderer的材质球合并。

普通的MeshRenderer的材质球合并:

1.合并所有材质球所携带的贴图,新建一个材质球,并把合并好的贴图赋予新的材质球。

2.记录下每个被合并的贴图所处于新贴图的Rect,用一个Rect[]数组存下来。

3.合并网格,并把需要合并的各个网格的uv,根据第2步得到的Rect[]刷一遍。

4.把新的材质球赋予合并好的网格,此时就只占有1个drawcall了。

下面是关键代码:

 

void CombineMesh()
    {
        
        MeshFilter[] mfChildren = GetComponentsInChildren<MeshFilter>();
        CombineInstance[] combine = new CombineInstance[mfChildren.Length];


        MeshRenderer[] mrChildren = GetComponentsInChildren<MeshRenderer>();
        Material[] materials = new Material[mrChildren.Length];


        MeshRenderer mrSelf = gameObject.AddComponent<MeshRenderer>();
        MeshFilter mfSelf = gameObject.AddComponent<MeshFilter>();


        Texture2D[] textures = new Texture2D[mrChildren.Length];
        for (int i = 0; i < mrChildren.Length; i++)
        {
            if (mrChildren[i].transform == transform)
            {
                continue;
            }
            materials[i] = mrChildren[i].sharedMaterial;
            Texture2D tx = materials[i].GetTexture("_MainTex") as Texture2D;


            Texture2D tx2D = new Texture2D(tx.width, tx.height, TextureFormat.ARGB32, false);
            tx2D.SetPixels(tx.GetPixels(0, 0, tx.width, tx.height));
            tx2D.Apply();
            textures[i] = tx2D;
        }


        Material materialNew = new Material(materials[0].shader);
        materialNew.CopyPropertiesFromMaterial(materials[0]);
        mrSelf.sharedMaterial = materialNew;


        Texture2D texture = new Texture2D(1024, 1024);
        materialNew.SetTexture("_MainTex", texture);
        Rect[] rects = texture.PackTextures(textures, 10, 1024);


        for (int i = 0; i < mfChildren.Length; i++)
        {
            if (mfChildren[i].transform == transform)
            {
                continue;
            }
            Rect rect = rects[i];


            Mesh meshCombine = mfChildren[i].mesh;
            Vector2[] uvs = new Vector2[meshCombine.uv.Length];
            //把网格的uv根据贴图的rect刷一遍
            for (int j = 0; j < uvs.Length; j++)
            {
                uvs[j].x = rect.x + meshCombine.uv[j].x * rect.width;
                uvs[j].y = rect.y + meshCombine.uv[j].y * rect.height;
            }
            meshCombine.uv = uvs;
            combine[i].mesh = meshCombine;
            combine[i].transform = mfChildren[i].transform.localToWorldMatrix;
            mfChildren[i].gameObject.SetActive(false);
        }


        Mesh newMesh = new Mesh();
        newMesh.CombineMeshes(combine, true,true);//合并网格
        mfSelf.mesh = newMesh;
    }

 

 

合并前的drawcall:

 

合并后的drawcall:


合并好的网格:

合并好的贴图:

下面我们将讨论SkinnedMeshRenderer的合并。

SkinnedMeshRenderer比MeshRenderer稍微麻烦一点,因为SkinnedMeshRenderer要处理bones。

以下是步骤:

 

1.合并所有材质球所携带的贴图,新建一个材质球,并把合并好的贴图赋予新的材质球。

2.记录下每个被合并的贴图所处于新贴图的Rect,用一个Rect[]数组存下来。

3.记录下需要合并的SkinnedMeshRenderer的bones。

4.合并网格,并把需要合并的各个网格的uv,根据第2步得到的Rect[]刷一遍。

5.把合并好的网格赋予新的SkinnedMeshRenderer,并把第3步记录下的bones赋予新的SkinnedMeshRenderer。

6.把新的材质球赋予合并好的网格,此时就只占有1个drawcall了。


上面红色部分的步骤就是与MeshRenderer不同之处。

下面是关键代码:

 

void CombineMesh()
    {
        SkinnedMeshRenderer[] smrs = GetComponentsInChildren<SkinnedMeshRenderer>();
        CombineInstance[] combine = new CombineInstance[smrs.Length];
        Material[] materials = new Material[smrs.Length];
        Texture2D[] textures = new Texture2D[smrs.Length];

        SkinnedMeshRenderer smrCombine = combineMesh.gameObject.AddComponent<SkinnedMeshRenderer>();
        smrCombine.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
        smrCombine.receiveShadows = false;

        for (int i = 0; i < smrs.Length; i++)
        {
            materials[i] = smrs[i].sharedMaterial;
            Texture2D tx = materials[i].GetTexture("_MainTex") as Texture2D;

            Texture2D tx2D = new Texture2D(tx.width, tx.height, TextureFormat.ARGB32, false);
            tx2D.SetPixels(tx.GetPixels(0, 0, tx.width, tx.height));
            tx2D.Apply();
            textures[i] = tx2D;
        }

        Material materialNew = new Material(materials[0].shader);
        materialNew.CopyPropertiesFromMaterial(materials[0]);
        
        Texture2D texture = new Texture2D(1024, 1024);
        Rect[] rects = texture.PackTextures(textures, 10, 1024);
        materialNew.SetTexture("_MainTex", texture);

        List<Transform> boneTmp = new List<Transform>();

        for (int i = 0; i < smrs.Length; i++)
        {
            if (smrs[i].transform == transform)
            {
                continue;
            }
            Rect rect = rects[i];

            Mesh meshCombine = CreatMeshWithMesh(smrs[i].sharedMesh);
            Vector2[] uvs = new Vector2[meshCombine.uv.Length];

            for (int j = 0; j < uvs.Length; j++)
            {
                uvs[j].x = rect.x + meshCombine.uv[j].x * rect.width;
                uvs[j].y = rect.y + meshCombine.uv[j].y * rect.height;
            }

            boneTmp.AddRange(smrs[i].bones);

            meshCombine.uv = uvs;
            combine[i].mesh = meshCombine;
            combine[i].transform = smrs[i].transform.localToWorldMatrix;
            GameObject.Destroy(smrs[i].gameObject);
        }

        Mesh newMesh = new Mesh();
        newMesh.CombineMeshes(combine, true, true);

        smrCombine.bones = boneTmp.ToArray();
        smrCombine.rootBone = rootBone;
        smrCombine.sharedMesh = newMesh;
        smrCombine.sharedMaterial = materialNew;
    }
    Mesh CreatMeshWithMesh(Mesh mesh)
    {
        Mesh mTmp = new Mesh();
        mTmp.vertices = mesh.vertices;
        mTmp.name = mesh.name;
        mTmp.uv = mesh.uv;
        mTmp.uv2 = mesh.uv2;
        mTmp.uv2 = mesh.uv2;
        mTmp.bindposes = mesh.bindposes;
        mTmp.boneWeights = mesh.boneWeights;
        mTmp.bounds = mesh.bounds;
        mTmp.colors = mesh.colors;
        mTmp.colors32 = mesh.colors32;
        mTmp.normals = mesh.normals;
        mTmp.subMeshCount = mesh.subMeshCount;
        mTmp.tangents = mesh.tangents;
        mTmp.triangles = mesh.triangles;

        return mTmp;
    }
未合并前,有两个网格,占用2drawcall,分别为人物和刀的网格和材质球。

 



合并后,只占用 1 drawcall了,而且AnimationController以及其动画能够正常工作:

 

合并好的网格:

合并好的贴图:

 

以上便是合并Mesh和Material的内容,目前只关注实现,还可以进一步优化,有以下几点要注意的:

1.合并的材质球需要使用同一个shader,如果多个材质球使用了不同shader,就要做进一步的出分类处理了。

2.材质球所用的Texture文件的Read/Write Enable选项要打上勾。

项目的github地址:  https://github.com/arBao/MeshMaterialCombine

原创文章,转载请注明出处:   http://blog.csdn.net/dardgen2015/article/details/51517860

标签:mTmp,合并,网格,Unity,mesh,Texture2D,new,材质
From: https://www.cnblogs.com/gangtie/p/18518810

相关文章

  • Unity性能优化-降低功耗,发热量,耗电量之OnDemandRendering篇
    公司游戏项目,手机运行严重发烫,耗电量飞快。在暂时无法做其他美术性和技术性优化的情况下,我写了这个公司内部文档,并做了个实验,今天干脆公布出来,希望对大家有用。 --官方文档:Unity-ScriptingAPI:OnDemandRendering--Youtube讲解:https://www.youtube.com/watch?v=RYgWn6jbt......
  • Unity控制物体透明度的改变
    目录标题效果图代码调用注意事项效果图代码注意:在控制全部的模型进行透视时,已经隐藏的子物体仍然要处理。usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingDG.Tweening;publicclassFadeModel{privateGameObj......
  • HyperWorks二维网格划分与单元连续性
    自动网格划分HyperWorks中为零件定义几何曲面是创建零件壳单元网格的最佳方式。HyperMesh创建二维网格最有效的方法是使用Automesh面板直接在零件的表面创建网格。Automesh面板是HyperMesh重要的网格划分工具,通过automesh可实现单元尺寸、单元密度、单元类型以及节点分......
  • ​Leetcode 166.珠宝的最高价值​ 网格图dp C++实现
    问题:Leetcode166.珠宝的最高价值现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为:只能从架子的左上角开始拿珠宝每次可以移动到右侧或下侧的相邻位置到达珠宝架子的右下角时,停止拿取注意:珠宝的价值都是大于0的。除非这个......
  • 【Unity】UGUI模拟NGUI的UISprite-->LImage
    UGUI本没有像NGUI方便使用图集的组件,之前也写过继承Image,加入SpriteAtlas作图集,切换图片显示的组件,现在弄一个3.0版本的这个组件的诞生源于上一篇:【Unity】Addressables下的图集(SpriteAtlas)内存优化==========================================================================......
  • Unity项目Native Crash问题修复原理
    背景相信大家公司有crash的收集途径的工具,肯定会看到大量的一种错误类型SIGSEVG(SEGV_MAPERR),这个crash其实并不属于恶性crash,对游戏体验不会造成严重的影响;另外,这个crash也只会在某些特定机型和系统上才会出现,非所有设备都会出现,其它细节也暂不明确,所以我们大部分情况是不......
  • Unity的SkinnedMeshRenderer性能优化
    Unity支持两种主要的Skinning技术在Unity中,Skinning主要指的是角色的蒙皮过程,这是3D动画中的一个关键步骤,用于将3D模型的网格(皮肤)附着到骨骼上,使得模型可以根据骨骼的动作进行逼真的变形。Unity支持两种主要的Skinning技术:CPUSkinning和GPUSkinning。1.CPUSkinning......
  • 【Unity】Addressables下的图集(SpriteAtlas)内存优化
    前言:资源管理系统:AddressablesUI:模拟NGUI图集Sprite,在UGUI下继承Image增加UIImage组件,实现将SpriteAtlas组件拖拽到属性面板上,切换选择里面的小图问题:在检查项目内存占用过高问题时,发现直接拖拽上去的资源不受Addressables系统的自动引用管理,导致部分资源虽然没有引用,但是未被释放......
  • CommunityToolkit.Mvvm中的Ioc
    什么是Ioc在软件工程中,控制反转(IoC)是一种设计原则,其中计算机程序的自定义编写部分从外部源(例如框架)接收控制流。术语“反转”是历史性的:与过程式编程相比,具有这种设计的软件架构“反转”了控制。在过程式编程中,程序的自定义代码调用可重用库来处理通用任务,但在控制反转的情况下,是......
  • 【Unity休闲风格UI资源】GUI - Casual Fantasy
    GUI-CasualFantasy是Unity的一款用户界面(GUI)插件,专为休闲幻想类游戏设计,提供了一套完整的UI资源和工具。该插件能帮助开发者快速搭建符合幻想风格的用户界面,适合各种类型的游戏,特别是带有轻松、卡通风格的RPG、冒险、策略等游戏项目。以下是它的主要功能和特点:1......