Calmer的文章

  • 首页
  • 文章归档
  • 关于页面

  • 搜索
体验游戏 笔记 推荐 工具链 工具使用 小游戏 插件 UI 软件 教程

Unity3d大场景切分与加载研究

发表于 2020-07-18 | 0 | 阅读次数 2124

前言

最近面临要制作大地图,于是开始捣腾大地图,会将学习总结记录于此。


SomeRecord

(大地图)可参考吃鸡
场景:分为地表(地形) 静态物件 动态物件(包括人物等) 光照烘焙等等。


地形处理:

  1. 地形切割
    模型文件格式组织的研究 OBJ FBX MESH Terrain(也是可以转成相应模型文件)
    对地形进行切割,再分块加载
    地形无非就是 模型文件 然后也就是一些Mesh数据 或者Unity3d自带的Terrain
    切分就是在进行这些数据的操作

在手机游戏中,一般很少用到Terrain,因为性能限制,一般会用Terrain制作好场景文件,然后转成Obj文件 美术进行减面操作,重新展UV等 再导入,所以一般切分就是一个模型文件。

  1. 地形加载 流式加载 Or 块加载(九宫加载,视野加载)(SceneManager)
    可否考虑使用JobSystem(Unity自带的线程系统) 异步加载

参考资料

切分

切分模型Mesh数据:
https://zhuanlan.zhihu.com/p/34487275
https://github.com/DavidArayan/ezy-slice

Unity3D教程:储存模型网格资料:
https://gameinstitute.qq.com/community/detail/112830

Unity3D分割地形Terrain:
https://blog.csdn.net/qq_14903317/article/details/69835578?locationNum=13&fps=1

Unity3d Mesh2Obj: https://blog.csdn.net/tom_221x/article/details/61920213

基于Unity3D的大地形研究(1):Cluster Async Load:
https://www.zhihu.com/people/maxwellgeng
https://zhuanlan.zhihu.com/p/51291312

Unity官方EditorSceneManager文档

加载

切分代码

using UnityEngine;
using UnityEditor;
using EzySlice;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
using System.Text;
using System.IO;

public class SplitTool : EditorWindow
{

    //1.切GameObject(Obj or Fbx) 保存Mesh 保存Prefab 保存Scene
    //2.... 保存成Object 保存Prefab 保存场景
    //3.切Terrain 其他。
    static Vector3 itemSize = new Vector3(3, 0 ,3);

    [MenuItem("Assets/GenSubMesh", true)]
    static bool IsCan()
    {
        if (PrefabUtility.GetPrefabAssetType(Selection.activeObject) == PrefabAssetType.Regular)
        {
            return true;
        }
        return false;
    }

    [MenuItem("Assets/GenSubMesh", false)]
    static void GenSubMesh()
    {
        GameObject curGO = Selection.activeObject as GameObject;
        //curGO.SliceInstantiate(Vector3.zero, -Vector3.forward);
        curGO.transform.position = Vector3.zero;
        curGO.transform.eulerAngles = Vector3.zero;

        Vector3 curScale = curGO.transform.lossyScale;
        Mesh curObjMesh = curGO.GetComponent<MeshFilter>().sharedMesh;

        //Debug.Log($"X:{curObjMesh.bounds.center.x},Y:{curObjMesh.bounds.center.y},Z:{curObjMesh.bounds.center.z}");
        Debug.Log($"X:{curObjMesh.bounds.min.x},Y:{curObjMesh.bounds.min.y},Z:{curObjMesh.bounds.min.z}");
        //Debug.Log($"X:{curObjMesh.bounds.max.x},Y:{curObjMesh.bounds.max.y},Z:{curObjMesh.bounds.max.z}");
        
        int column = (int)Mathf.Ceil(curObjMesh.bounds.size.x*curScale.x/itemSize.x);
        int row = (int)Mathf.Ceil(curObjMesh.bounds.size.z * curScale.z / itemSize.z);

        float columnInterval = itemSize.x / curScale.x;
        float rowInterval = itemSize.z/curScale.z;
        

        if (row == 1 && column == 1)
        {
            Debug.Log("不需要切分");
            return;
        }

        Debug.Log("Row:" + row + ",Column:" + column);
        List<Mesh> rowList = new List<Mesh>();

        List<GameObject> rowGoList = new List<GameObject>();
        Mesh[,] meshes = new Mesh[row,column];

        SlicedHull h=null;
        GameObject nextObj = curGO;
        for(int i=1;i<=column-1;i++)
        {
            h = nextObj.Slice(curObjMesh.bounds.min*curScale.x + new Vector3(i*itemSize.x, 0, 0), Vector3.right);
            nextObj = h.CreateUpperHull(nextObj);
            rowGoList.Add(h.CreateLowerHull(nextObj));
            rowList.Add(h.lowerHull);
        }
        rowGoList.Add(h.CreateUpperHull(nextObj));
        rowList.Add(h.upperHull);

        for (int i = 0; i < rowList.Count; i++)
        {
            SlicedHull h2 = null;
            GameObject curMeshObj = rowGoList[i];
            for (int j = 1; j <= row - 1; j++)
            {
                h2 = curMeshObj.Slice(curObjMesh.bounds.max* curScale.z - new Vector3(0, 0, j * itemSize.z), -Vector3.forward);
                curMeshObj = h2.CreateUpperHull(curMeshObj);
                meshes[j - 1,i] = h2.lowerHull;
            }
            meshes[row - 1, i] = h2.upperHull;
        }

        for (int i = 0; i < meshes.GetLength(0); i++)
        {
            for(int j=0;j<meshes.GetLength(1);j++)
            {
                AssetDatabase.CreateAsset(meshes[i,j], $"Assets/SubMesh/RowList{i}_{j}.asset");
            }
        }

        for (int i = 0; i < row; i++)
        {
            for(int j=0;j<column;j++)
            {
                GameObject gameObj = new GameObject("Terrain" + i+"_"+j);
                gameObj.transform.position = curGO.transform.position;
                gameObj.transform.localScale = curGO.transform.localScale;
                gameObj.transform.eulerAngles = curGO.transform.eulerAngles;
                gameObj.AddComponent<MeshFilter>().mesh = AssetDatabase.LoadAssetAtPath<Mesh>($"Assets/SubMesh/RowList{i}_{j}.asset");
                MeshRenderer renderer = gameObj.AddComponent<MeshRenderer>();
                renderer.sharedMaterial = curGO.GetComponent<MeshRenderer>().sharedMaterial;
                PrefabUtility.SaveAsPrefabAssetAndConnect(gameObj, $"Assets/SubMesh/Terrain{i}_{j}.prefab", InteractionMode.AutomatedAction);
                //Scene s = SceneManager.CreateScene("Terrain" + i + "_" + j);
                //Scene s= EditorSceneManager.NewScene("Terrain" + i + "_" + j);
                Scene s= EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
                s.name = $"Terrain{i}_{j}";
                GameObject terrain = new GameObject("Terrain");
                Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>($"Assets/SubMesh/Terrain{i}_{j}.prefab")).transform.SetParent(terrain.transform);
                EditorSceneManager.MarkSceneDirty(s);
                EditorSceneManager.SaveScene(s, $"Assets/Scenes/Terrain{i}_{j}.unity");
                //AssetDatabase.CreateAsset(s,); 
            }
        }
    }


    static Vector3 GetGameObjectSize(GameObject gameObject)
    {
        Mesh mesh = gameObject.GetComponent<MeshFilter>().mesh;
        Vector3 scale = gameObject.transform.lossyScale;
        return new Vector3(mesh.bounds.size.x * scale.x, mesh.bounds.size.y * scale.y, mesh.bounds.size.z * scale.z);
    }

    [MenuItem("Assets/GenObj")]
    static void Test()
    {
        GameObject curObject = Selection.activeObject as GameObject;
        GenObj(curObject);
    }

    //暂时还没完善
    static void GenObj(GameObject meshGo)
    {
        using (StreamWriter streamWriter = new StreamWriter(string.Format("{0}{1}.obj", Application.dataPath+"/Obj/", meshGo.name)))
        {
            streamWriter.Write(MeshToString(meshGo.GetComponent<MeshFilter>(), new Vector3(-1f, 1f, 1f)));
            streamWriter.Close();
        }
        AssetDatabase.Refresh();
    }

    static string MeshToString(MeshFilter mf,Vector3 scale)
    {
        Mesh mesh = mf.sharedMesh;
        Material[] sharedMaterials= mf.GetComponent<Renderer>().sharedMaterials;
        Vector2 textureOffset = mf.GetComponent<Renderer>().sharedMaterial.GetTextureOffset("_MainTex");
        Vector2 textureScale = mf.GetComponent<Renderer>().sharedMaterial.GetTextureScale("_MainTex");

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append($"mtllib design.mtl\ng {mf.name}\n");
        Vector3[] vertices = mesh.vertices;
        for(int i=0;i<vertices.Length;i++)
        {
            Vector3 vector = vertices[i];
            stringBuilder.Append($"v {vector.x * scale.x} {vector.y * scale.y} {vector.z * scale.z}\n");
        }
        stringBuilder.Append("\n");
        Dictionary<int, int> dic = new Dictionary<int, int>();
        if(mesh.subMeshCount>1)
        {
            int[] triangles = mesh.GetTriangles(1);
            for(int j=0;j<triangles.Length;j+=3)
            {
                if(!dic.ContainsKey(triangles[j]))
                {
                    dic.Add(triangles[j], 1);
                }
                if(!dic.ContainsKey(triangles[j+1]))
                {
                    dic.Add(triangles[j + 1], 1);
                }
                if(!dic.ContainsKey(triangles[j+2]))
                {
                    dic.Add(triangles[j + 2], 1);
                }
            }
        }

        for(int num=0;num!=mesh.uv.Length;num++)
        {
            Vector2 vector2 = Vector2.Scale(mesh.uv[num], textureScale) + textureOffset;
            if(dic.ContainsKey(num))
            {
                stringBuilder.Append($"vt {mesh.uv[num].x} {mesh.uv[num].y}\n");
            }
            else
            {
                stringBuilder.Append($"vt {vector2.x} {vector2.y}\n");
            }
        }

        for (int k = 0; k < mesh.subMeshCount; k++)
        {
            stringBuilder.Append("\n");

            if (k == 0)
            {
                stringBuilder.Append("usemtl ").Append("Material_design").Append("\n");
            }

            if (k == 1)
            {
                stringBuilder.Append("usemtl ").Append("Material_logo").Append("\n");
            }

            int[] triangles2 = mesh.GetTriangles(k);

            for (int l = 0; l < triangles2.Length; l += 3)
            {
                stringBuilder.Append(string.Format("f {0}/{0} {1}/{1} {2}/{2}\n", triangles2[l] + 1, triangles2[l + 2] + 1, triangles2[l + 1] + 1));
            }
        }

        return stringBuilder.ToString();

    }




    //切分Terrain

    public static string TerrainSavePath = "Assets/SubTerrain/";
    //分割大小
    public static int SLICING_SIZE = 4;

    public static int subTerrainSize = 250;

    [MenuItem("Assets/SliceTerrain",true)]
    private static bool IsTerrain()
    {
        Object o = Selection.activeObject;
        TerrainData tD = Selection.activeObject as TerrainData;
        if (tD == null)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    //开始分割地形
    //[MenuItem("Terrain/Slicing")]
    [MenuItem("Assets/SliceTerrain", false)]
    private static void Slicing()
    {

        TerrainData tD = Selection.activeObject as TerrainData;

        //Terrain terrain = null;

        //Terrain terrain = GameObject.FindObjectOfType<Terrain>();
        if (tD == null)
        {
            Debug.LogError("不是地形数据!");
            return;
        }

        if (Directory.Exists(TerrainSavePath)) Directory.Delete(TerrainSavePath, true);
        Directory.CreateDirectory(TerrainSavePath);

        //TerrainData terrainData = terrain.terrainData;
        TerrainData terrainData = tD;

        //这里我分割的宽和长度是一样的.这里求出循环次数,TerrainLoad.SIZE要生成的地形宽度,长度相同
        //高度地图的分辨率只能是2的N次幂加1,所以SLICING_SIZE必须为2的N次幂
        SLICING_SIZE = (int)terrainData.size.x / subTerrainSize;

        Vector3 oldSize = terrainData.size;

        //得到新地图分辨率
        int newHeightmapResolution = (terrainData.heightmapResolution - 1) / SLICING_SIZE;
        int newAlphamapResolution = terrainData.alphamapResolution / SLICING_SIZE;
        int newbaseMapResolution = terrainData.baseMapResolution / SLICING_SIZE;
        TerrainLayer[] splatProtos = terrainData.terrainLayers;

        //循环宽和长,生成小块地形
        for (int x = 0; x < SLICING_SIZE; ++x)
        {
            for (int y = 0; y < SLICING_SIZE; ++y)
            {
                //创建资源
                TerrainData newData = new TerrainData();
                string terrainName = TerrainSavePath + terrainData.name + y + "_" + x + ".asset";
                AssetDatabase.CreateAsset(newData, TerrainSavePath + terrainData.name + y + "_" + x + ".asset");
                EditorUtility.DisplayProgressBar("正在分割地形", terrainName, (float)(x * SLICING_SIZE + y) / (float)(SLICING_SIZE * SLICING_SIZE));

                //设置分辨率参数
                newData.heightmapResolution = (terrainData.heightmapResolution - 1) / SLICING_SIZE;
                newData.alphamapResolution = terrainData.alphamapResolution / SLICING_SIZE;
                newData.baseMapResolution = terrainData.baseMapResolution / SLICING_SIZE;

                //设置大小
                newData.size = new Vector3(oldSize.x / SLICING_SIZE, oldSize.y, oldSize.z / SLICING_SIZE);

                //设置地形原型
                TerrainLayer[] newSplats = new TerrainLayer[splatProtos.Length];
                for (int i = 0; i < splatProtos.Length; ++i)
                {
                    newSplats[i] = new TerrainLayer();
                    
                    newSplats[i].diffuseTexture = splatProtos[i].diffuseTexture;
                    newSplats[i].tileSize = splatProtos[i].tileSize;

                    float offsetX = (newData.size.x * x) % splatProtos[i].tileSize.x + splatProtos[i].tileOffset.x;
                    float offsetY = (newData.size.z * y) % splatProtos[i].tileSize.y + splatProtos[i].tileOffset.y;
                    newSplats[i].tileOffset = new Vector2(offsetX, offsetY);
                }
                newData.terrainLayers = newSplats;


                //设置混合贴图
                float[,,] alphamap = new float[newAlphamapResolution, newAlphamapResolution, splatProtos.Length];
                alphamap = terrainData.GetAlphamaps(x * newData.alphamapWidth, y * newData.alphamapHeight, newData.alphamapWidth, newData.alphamapHeight);
                newData.SetAlphamaps(0, 0, alphamap);

                //设置高度
                int xBase = terrainData.heightmapWidth / SLICING_SIZE;
                int yBase = terrainData.heightmapHeight / SLICING_SIZE;
                float[,] height = terrainData.GetHeights(xBase * x, yBase * y, xBase + 1, yBase + 1);
                newData.SetHeights(0, 0, height);
            }
        }

        EditorUtility.ClearProgressBar();
    }
}
  • 本文作者: Calmer
  • 本文链接: https://mytechplayer.com/archives/unity3d大场景切分与加载研究
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
工具库
Unity3D Shader入门
  • 文章目录
  • 站点概览
Calmer

Calmer

88 日志
7 分类
10 标签
RSS
Creative Commons
0%
© 2020 — 2025 Calmer
由 Halo 强力驱动
蜀ICP备20010026号-1川公网安备51019002006543
Copyright © 2020-2025 Calmer的文章