Unity场景优化工具:Mesh Baker 基础教程(贴图篇)

Posted 萤火虫实验室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity场景优化工具:Mesh Baker 基础教程(贴图篇)相关的知识,希望对你有一定的参考价值。

目录

前言

一、Mash Baker是什么?

二、使用步骤

1.打开场景

2.将Texture Baker添加到场景中

3.使用Texture Baker生成贴图集

4.烘焙新的模型并使每个模型独立

总结


前言

模型贴图整合是3D游戏中美术资源优化的重要环节,我们通常把多个模型的贴图集成到一张2048大小的贴图集中,以达到减少贴图和材质球的数量来节省资源。但是面对成百上千的模型,纹理贴图,法线贴图,高光贴图等等,每种贴图集合成大图,再分别对一次UV,结果是累死,各种贴图还未必对得上位置……,那使用 Mesh Baker 我们可以批量自动拼合贴图并映射UV,大多数工作只需在 Unity 中来完成,免去在三维软件中手动调整UV的烦恼。话不多说,上教程。

使用后:多个模型共用一个材质球,使用一张大贴图(图片来源网络)
使用前:一个模型对应一个材质球,使用一张小贴图(图片来源网络)

一、Mash Baker是什么?

Mash Baker 用于 Unity 有优化模型场景的一种工具,该工具是为了解决场景模型数量过多导致运行时产生较高的DrawCall而创建的。

以下是官方的对于这款插件的介绍。

提高性能!结合网格和材料以减少批量! Mesh Baker 是一个强大的工具包,具有灵活的非破坏性工作流程,用于优化道具和场景。 Unity 最著名的优化工具。自 2012 年以来的五星级支持和更新。

* 为道具组创建图集或纹理数组
* 在大型复杂场景中查找、分组和组合网格
* 修改道具预制件以使用图集材料
* 支持 URP、HDRP、标准管道
* 适用于内置和自定义着色器
* 为使用色调的材料创建地图集
* 支持多种材质的道具
* 处理平铺纹理
* 结合自定义并向蒙皮网格添加道具
* 创建可定制的蒙皮网格角色
* 将蒙皮网格与混合形状相结合
* UV、法线、切线自动调整
*光照贴图支持
*混合搭配灵活的工作流程工具
* 运行时 API

二、使用步骤

1.打开场景

如果导入的模型都集中在世界原点,这将不利于我们后面的操作,我们需要把模型分开

分开放置,方便我们后续的操作

2.将Texture Baker添加到场景中

如果你已经在项目中导入了Mesh Baker插件则在Unity菜单栏中找到以下路径来添加场景组件

GameObject > Create Other > Mesh Baker > TextureBaker and Mesh Baker 

这将在Hierarchy窗口中生成一个名为TextureBaker对象

可以看到这款插件的功能非常强大,此篇主要是讲贴图的优化,所以选择TextureBaker and Mesh Baker 是我们适用的功能。

3.使用Texture Baker生成贴图集

让我们选中Hierarchy窗口中的TextureBaker组件,我们可以看到右侧inspector窗口中有着许多复杂的设置,请不用担心,我将在实践中介绍他们的作用。

 我们先进行操作的是MB3_Texture Baker (Script)里的参数,所以让我们把MB3_ Mesh Baker Grouper (Script)折叠起来。下图是对插件功能的解释说明。

 选中我们要导入的场景对象,拖放到图中的添加窗口栏。

为了方便我们一次导入所有,我们先给它打个组,之后只拖它的父对象即可,这里我将父对象命名为Building

 先在Hierarchy中高亮选择TextureBaker组件,然后直接按住父对象(Building)拖放到inspector中的对象添加窗口。

 对象导入后,我们需要继续进行MB3_Texture Baker (Script)的设置,由于对象过多,我们直接折叠列表。

这些选项的理解是我看了官方教程后的总结,解释不正确还请反馈

我们首先点击Create Empty Assets For Combined Material 这将给我们的贴图集和共享材质提供一个存放位置并且会自动生成一个贴图和材质。

让我们在Project中找到我们所保存的路径,看看发生了什么。 

 这里我是用标准着色Standard

 之后让我们回到TextureBakery的Inspector中,我们可以看到,在Texture Baker Result(存放结果的地方)自动填上了刚才生成的文件,此外Combined Mesh Material 中也自动填上了新生成的材质球。

 点击过后,根据对象的数量,及其复杂程度,我们需要等待个十几、几十秒,甚至是分钟!让插件来合并贴图。我建议一次不要搞太多以防万一(虽然到目前我还没遇到过崩溃的情况)。

恭喜你,你已经成功一半了

4.烘焙新的模型并使每个模型独立

MB3_Texture Baker (Script)的使用到这里就结束了,我们在Inspector面板中将它折叠。并展开
MB3_ Mesh Baker Grouper (Script),你可以看到又是一大堆参数设置,请不要烦恼,在这里我们并不需要做很多改动。接下来我将对部分设置进行解释。

 首先我们要使每个子对象都可以独立选择并移动变换,我们先看上图黄色框内找到Cluster Type(集群类型),切换为Agglomerative这个选择使按照物体本身来划分,最适合我们。然后我们点击Click To Build Clusters按钮来构建集群。

 这个时候你在Scene窗口仔细观察,会发现模型被一圈圈的曲线包裹,这类似于碰撞网格一样的东西,每一个被包裹的将会分到一个组内,成为一个独立的模型。

 现在的包裹曲线数量并不多,我需要每个模型都有自己的包裹曲线,这样每个模型就是一个组,以达到独立模型子对象的目的。这个时候我们可以看到有个滑块叫Max Distance(最大距离)我们将他调至最左,在滑动的过程中,观察场景中包裹曲线的变化。

 这个时候我们需要生成一个记录分组信息的组件,在烘焙时插件会读取该组件的分组信息,来进行烘焙分组。我们将inspectior面板滚动到最底部,点击Generate Mesh Bakers,如果跳出提示我们点击OK即可。此时在Hierarchy面板中会多出一个名字超长的组件,这就是用来记录分组信息的组件,我们不用管它。此时我们接着点击Bake All Child MeshBakers(烘焙所有子对象)

 在Hierarchy窗口中,你会多出来很多对象,没错,这就是我们烘焙出来的新对象。目前这些对象与原对象处于重叠状态,我们将它拖出来。选择名称为0结尾的Mesh按住Shift再点击最后一个Mesh将其全部选中。在Scene窗口中将他移动与原对象分开。

 插件生成的对象几乎和我们原对象一样!烘焙出来的对象有些反光,这是因为我们没有调整材质球的Smoothness,我们将它归零。此时我们随便点击一个对象并在Inspectior中查看它所使用的材质球,不出所料,使用的正是我们新生成的材质球,我们再选择其他对象查看,使用的也是相同的材质球

恭喜,整个新生成的对象所使用的材质都是同一个,这达到了我们的目的!

总结

以上就是使用Mesh Baker批量合并贴图的基础教程,本文仅仅简单介绍了Mesh Baker中Texture Baker功能的使用,而Mesh Baker的强大之处不仅仅只有这些。有关更多Mesh Baker的介绍可以前往官方论坛进行查看。

论坛

油管官方教程

资料参考:3D美术资源优化—材质贴图篇

Unity场景扩展,生成Mesh

场景扩展:

案例1: OnSceneGUI 实现如下效果

 

 代码:

[CustomEditor(typeof(Test))]  --函数作用是Test对象能在编辑器激活

public class MyEditor : Editor

    private void OnSceneGUI()

   

        Test test = (Test)target;  --选中的对象 需要CustomEditor

        Handles.Label(test.transform.position + Vector3.up * 2,

            test.transform.name + ":" + test.transform.position.ToString());

        //开始绘制GUI

        Handles.BeginGUI();

        GUILayout.BeginArea(new Rect(100, 200, 100, 100));

        if(GUILayout.Button("这是一个按钮!"))

       

            Debug.Log("test");

       

        GUILayout.Label("我在编辑Scene视图");

        GUILayout.EndArea();

        Handles.EndGUI();

   

 

案例二:继承OnInspectorGUI

 

实现如下效果:

 

public class MyEditor : Editor

    private void OnSceneGUI()

   

       

   

    public override void OnInspectorGUI()

   

        GUILayout.Label("This is a Label");

        if(GUILayout.Button("Button"))

       

            Debug.Log("cccc");

       

   

 

案例三:打开文件面板

 

string[] filters = "region file", "region";

            string defaultPath = Application.streamingAssetsPath + "/regions";

            string strPath = EditorUtility.OpenFilePanelWithFilters("加载区块文件", defaultPath, filters);

            if (!string.IsNullOrEmpty(strPath))

                regionEditor.Load(strPath);

 

2、Mesh生成

实现如下图形

 

!!!注意:绘制网格三角形顺序 需要严格按照顺时针或者逆时针绘制

  颜色最大值是255  m_colors[vertexIdx + 0] = new Color32(1, 1, 1, 255);

 

C#代码

protected Mesh _mesh;

    public Color color;

    public float xWidth = 100;

    public float yWidth = 100;

    private void Awake()

   

        var meshFilter = GetComponent<MeshFilter>();

        if(meshFilter == null)

       

            meshFilter = gameObject.AddComponent<MeshFilter>();

       

        if(_mesh == null)

       

            _mesh = new Mesh();

            meshFilter.mesh = _mesh;

       

   

    // Use this for initialization

    void Start ()

        Vector3[] vertices = new Vector3[8];

        vertices[0] = new Vector3(0, 0, 0);

        vertices[1] = new Vector3(500, 0, 0);

        vertices[2] = new Vector3(500, 500, 0);

        vertices[3] = new Vector3(0, 500, 0);

        vertices[4] = new Vector3(0 - xWidth, 0 - yWidth, 0);

        vertices[5] = new Vector3(500 + xWidth, 0 - yWidth, 0);

        vertices[6] = new Vector3(500 + xWidth, 500 + yWidth, 0);

        vertices[7] = new Vector3(0 - xWidth, 500 + yWidth, 0);

        int[] triangles = new int[24] 0, 4, 7, 7,3,0,3,7,2,2,7,6,6,1,2,1,6,5,5,0,1,0,5,4 ;

        _mesh.vertices = vertices;

        _mesh.triangles = triangles;

        UpdateColor();

        UpdateUVs(vertices);

       

    protected void UpdateColor()

   

        Color32[] colors32 = new Color32[_mesh.vertices.Length];

        for (int i = 0; i < colors32.Length; i++)

       

            colors32[i] = color;

       

        _mesh.colors32 = colors32;

   

    protected void UpdateUVs(Vector3[] vertices)

   

        Vector2[] uvs = new Vector2[vertices.Length];

        for (int i = 0; i < uvs.Length; i++)

       

            float u = (vertices[i].x+xWidth) / (500+2*xWidth) ;

            float v = (vertices[i].y+yWidth) / (500+2*yWidth) ;

            u = Mathf.Clamp(u,0, 1);

            v = Mathf.Clamp(v,0, 1);

            uvs[i] = new Vector2(u, v);

       

        _mesh.uv = uvs;

   

 

材质Shader:

Shader "LOS/Basic"

       SubShader

              Tags

              "IgnoreProjector" = "True"

              "Queue" = "Transparent+2"

              "RenderType" = "Transparent"

       

              Pass

              Blend SrcAlpha OneMinusSrcAlpha

              ZWrite Off

              Lighting Off

              Fog Mode Off

              CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#pragma target 3.0

              uniform sampler2D _MainTex;

       uniform float _intensity;

       struct vIn

              float4 vertex : POSITION;

              float4 color : COLOR;

       ;

       struct v2f

              float4 pos : SV_POSITION;

              float4 color : COLOR;

       ;

       v2f vert(vIn v)

              v2f o;

              o.color = v.color;

              o.pos = UnityObjectToClipPos(v.vertex);

              return o;

       

       fixed4 frag(v2f i) : COLOR

              _intensity = 1;

       return fixed4(i.color.rgb * _intensity, i.color.a);

       

              ENDCG

       

       

 

3、九宫格动态地图加载

玩家在移动的过程中,会加载周围共九个方向的动态地图,如图1:

              

                                 图1 九宫格 

                

  

                                 图2  向上移动

绿色框代表屏幕大小,因此每块地图的大小基于屏幕大小设定,宽度为屏幕宽度的一半,高度为屏幕高度的一半。

每块地图都有自己的逻辑坐标,如图1括号所示,在加载地图时根据玩家逻辑坐标的改变更新地图显示,如图2,向上移动后

Y轴坐标为-1地图删除,Y轴坐标为2加载出来。 

最终效果如图3所示

 

                                     图3  最终效果

实现

定义一个字典存放地图逻辑坐标和GameObject:

//存储玩家格子坐标及对应地图物体

private Dictionary<Vector2Int, GameObject> m_dynamicTileMapDict = new Dictionary<Vector2Int, GameObject>();

 

设定一个更新时间,根据玩家的位置动态更新玩家周围的地图

 

    /// <summary>

    /// 删除和加载动态地图

    /// </summary>

    /// <param name="pos"></param>

    private void UpdatePlayerTilePoint(Vector3 pos)

   

        if(m_playerCurrentTilePoint!=m_playerPreviousTilePoint)

       

            //位置发生改变  加载和删除动态地图

            m_playerPreviousTilePoint = m_playerCurrentTilePoint;

            m_tempPointList.Clear();

            for (int i = m_playerCurrentTilePoint.x - 1; i <= m_playerCurrentTilePoint.x + 1; i++)

           

                for (int j = m_playerCurrentTilePoint.y - 1; j <= m_playerCurrentTilePoint.y + 1; j++)

               

                    Vector2Int vector2 = new Vector2Int(i, j);

                    if (vector2 != m_playerCurrentTilePoint)

                   

                        m_tempPointList.Add(vector2);

                        //字典没有改地图信息 加载

                        if (!m_dynamicTileMapDict.ContainsKey(vector2))

                       

                            AddDynamicMap(vector2);

                       

                   

               

           

            // m_tempPointList 不包含字典key  删除

            foreach (var key in new List<Vector2Int>(m_dynamicTileMapDict.Keys))

           

                if(!m_tempPointList.Contains(key) && key != m_playerCurrentTilePoint)

               

                    RemoveDynamicMap(key);

               

           

       

   

 

 

目前加载地图方式,通过加载Prefab的名字实现

 

   /// <summary>

    /// 根据格子坐标动态加载物体

    /// </summary>

    /// <param name="point"></param>

    /// <returns></returns>

    public GameObject LoadMapPrefabByPointName(Vector2Int point)

   

        //TODO 根据AssetBundle加载

        string name = string.Format("[0,1]Grid", point.x, point.y);

        GameObject prefab =null;

        try

       

            prefab = Instantiate(Resources.Load("MapPrefab/" + name)) as GameObject;

            prefab.transform.SetParent(this.transform);

       

        catch(Exception e)

       

            Debug.LogError("没有地图资源加载");

       

        return prefab;

   

 

 /// <summary>

    /// 根据格子坐标动态添加地图

    /// </summary>

    /// <param name="pos"></param>

    public void AddDynamicMap(Vector2Int point)

   

        if(!m_dynamicTileMapDict.ContainsKey(point))

       

            m_dynamicTileMapDict.Add(point, LoadMapPrefabByPointName(point));

       

   

    /// <summary>

    /// 根据格子坐标动态显示已加载地图

    /// </summary>

    /// <param name="pos"></param>

    public void ShowDynamicMap(Vector2Int point)

   

        if (m_dynamicTileMapDict.ContainsKey(point))

       

            m_dynamicTileMapDict[point].SetActive(true);

       

   

    /// <summary>

    /// 根据格子坐标动态删除地图

    /// </summary>

    /// <param name="pos"></param>

    public void RemoveDynamicMap(Vector2Int point)

   

        if (m_dynamicTileMapDict.ContainsKey(point))

       

            Destroy(m_dynamicTileMapDict[point]);

            m_dynamicTileMapDict.Remove(point);

       

   

    /// <summary>

    /// 根据格子坐标动态隐藏地图

    /// </summary>

    /// <param name="pos"></param>

    public void DisappearDynamicMap(Vector2Int point)

   

        if (m_dynamicTileMapDict.ContainsKey(point))

       

            m_dynamicTileMapDict[point].SetActive(false);

       

   

 

以上是关于Unity场景优化工具:Mesh Baker 基础教程(贴图篇)的主要内容,如果未能解决你的问题,请参考以下文章

Runtime Mesh Baker v1.1.3 中文手册(自己参考)

Unity3d之将terrain转化成mesh

Unity场景扩展,生成Mesh

Unity场景扩展,生成Mesh

unity3d中网格mesh可以制作的吗?怎么做的?

Unity性能优化