Mesh地形生成,切割,保存为文件

效果展示:
在这里插入图片描述
在这里插入图片描述
可直接使用的完整代码在本文最后

工具介绍:

工具

  1. Auto Cutting属性,勾选该属性后,Each Terrain Size属性失效,脚本会根据Terrain Size属性自动划分网格,单个网格大小不会大于256*256.
  2. Height Map属性用来存放灰度图
  3. Terrain Height、Terrain Size 用来指定mesh的高度和大小
  4. 在Auto Cutting属性未勾选的情况下, Each Terrain Size属性可以用来指定每块地图的大小,属性要求必须能被TerrainSize整除
  5. Terrain Mat规定了地形的材质
  6. Origin属性为Mesh的父对象,可为空,默认为脚本所在对象
  7. 勾选Create Prefab属性,会将动态生成的Mesh文件保存为Prefab,在Assets文件夹下
Mesh基础知识:

https://catlikecoding.com/unity/tutorials/procedural-grid/

根据灰度图生成Mesh

在读取图像之前,务必勾选Read/Write Enable属性
在这里插入图片描述
单张Mesh的话,我们需要做的只是对它顶点坐标的Y值做一些手脚

 vertices.Add(new Vector3(i, HeightMap.GetPixel(X,Y).grayscale * TerrainHeight,j));

HeightMap.GetPixel((int)a,(int)b).grayscale;可以对图像灰度值进行采样

对Mesh进行切割

由于Mesh的限制,单张Mesh大小不能大于256*256,而且,我们通常不会把整个地图全部加载出来,地图分块是必须的
所谓切割,其实就是分批对灰度图采样

vertices.Add(new Vector3(TX*EachTerrainSize+i, HeightMap.GetPixel(X,Y).grayscale * TerrainHeight, TY*EachTerrainSize+j));

需要注意,无论TerrainSize值为多少,整张灰度图都会被利用到,就是说,在采样之前有一个缩放的过程

int X =(int)((float)TX * (float)EachTerrainSize *((float)MapWidth/(float)TerrainSize) + (float)i * ((float)MapWidth / (float)TerrainSize));
int Y = (int)((float)TY * (float)EachTerrainSize *((float)MapHeight / (float)TerrainSize) + (float)j * ((float)MapHeight / (float)TerrainSize));
Mesh切割之后有缝隙问题

这是因为,两个相邻的Mesh中间的连线并没有连接起来
在这里插入图片描述
我的做法是,规定255*255为最大单片Mesh大小,剩下的预留,用来补间,排除不需要补间的情况

			List<Vector3> vertices = new List<Vector3>();
            List<int> triangles = new List<int>();
            //只有在单片Mesh(不切割)的情况下,EachTerrainSize才有可能==256
            int EachTerrainSize_1 = EachTerrainSize == 256 ? 256 : EachTerrainSize + 1;
            for (int i = 0; i < EachTerrainSize_1; i++)
            {
                for (int j = 0; j < EachTerrainSize_1; j++)
                {
                    if (j == EachTerrainSize_1 && TX >= MAXXY-1)
                    {
                        break;
                    }
                    if (i == EachTerrainSize_1 && TY >= MAXXY - 1)
                    {
                        break;
                    }
                    int X =(int)((float)TX * (float)EachTerrainSize *((float)MapWidth/(float)TerrainSize) + (float)i * ((float)MapWidth / (float)TerrainSize));
                    int Y = (int)((float)TY * (float)EachTerrainSize *((float)MapHeight / (float)TerrainSize) + (float)j * ((float)MapHeight / (float)TerrainSize));
                    vertices.Add(new Vector3(TX*EachTerrainSize+i, HeightMap.GetPixel(X,Y).grayscale * TerrainHeight, TY*EachTerrainSize+j));
                    if (i == 0 || j == 0) continue;
                    triangles.Add(EachTerrainSize_1 * i + j);
                    triangles.Add(EachTerrainSize_1 * i + j - 1);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j);
                    triangles.Add(EachTerrainSize_1 * i + j - 1);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j - 1);
                }
            }

在这里插入图片描述

将Mesh保存

按照文件格式,将文件保存为预设体

if (createPrefab)
    {
       MeshToFile(terraints[ii].GetComponent<MeshFilter>(), terraints[ii].name+string.Format("_{0}",ii));
    }
public static string MeshToString(MeshFilter mf)
    {
        Mesh m = mf.mesh;
        Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;
        StringBuilder sb = new StringBuilder();
        //写入文件名
        sb.Append("g ").Append(mf.name).Append("\n");
        //写入顶点,法线,uv,material,三角面
        foreach (Vector3 v in m.vertices)
        {
            sb.Append(string.Format("v {0} {1} {2}\n", v.x, v.y, v.z));
        }
        sb.Append("\n");
        foreach (Vector3 v in m.normals)
        {
            sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
        }
        sb.Append("\n");
        foreach (Vector3 v in m.uv)
        {
            sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
        }
        for (int material = 0; material < m.subMeshCount; material++)
        {
            sb.Append("\n");
            sb.Append("usemtl ").Append(mats[material].name).Append("\n");
            sb.Append("usemap ").Append(mats[material].name).Append("\n");

            int[] triangles = m.GetTriangles(material);
            for (int i = 0; i < triangles.Length; i += 3)
            {
                sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
                    triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1));
            }
        }
        return sb.ToString();
    }
    public static void MeshToFile(MeshFilter mf, string filename)
    {
        using (StreamWriter sw = new StreamWriter("Assets\\" + filename + ".obj"))
        {
            sw.Write(MeshToString(mf));
        }
    }
}
全部代码

该脚本文件可以直接使用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Text;

public class MeshTerrain : MonoBehaviour {
    //选择自动切割之后,EachTerrainSize失效
    public bool AutoCutting;
    public Texture2D HeightMap;
    [Range(-300f, 300f)]
    public float TerrainHeight;
    [Range(1,1024)]
    public long TerrainSize;
    [Range(16, 256)]
    public int EachTerrainSize;
    public Material TerrainMat;
    public GameObject Origin;
    private int MapHeight;
    private int MapWidth;
    public bool createPrefab;
	void Start () {
        if (TerrainMat == null || HeightMap == null)
        {
            Debug.Log("Missing necessary parameters!");
            return;
        }
        try
        {
            float test = HeightMap.GetPixel(1, 1).grayscale;
        }catch
        {
            Debug.Log("Check HeightMap's readable properties");
            return;
        }
        if (EachTerrainSize>TerrainSize)
        {
            Debug.Log("EachTerrainSize can not bigger than TerrainSize");
            return;
        }
        if(!AutoCutting&&TerrainSize%EachTerrainSize!=0)
        {
            Debug.Log("TerrainSize%EachTerrainSize!=0");
            return;
        }
        MapHeight = HeightMap.height;
        MapWidth = HeightMap.width;
        if (Origin == null)
        {
            Origin = this.gameObject;
        }
        CreateTerrain();
	}
    private void CreateTerrain()
    {
        GameObject[] terraints;
        int MAXXY;
        int TX=0;
        int TY = 0;
        if (AutoCutting)
        {
            if (TerrainSize >= 256)
            {
                int i = 256;
                long TT = TerrainSize * TerrainSize;
                while (true)
                {
                    if (TT % (i * i) == 0) break;
                    i--;
                }
                EachTerrainSize = i-1;
                terraints = new GameObject[(int)Mathf.Pow(TerrainSize / EachTerrainSize, 2)];
                MAXXY = (int)TerrainSize / i;
            }
            else
            {
                terraints = new GameObject[1];
                EachTerrainSize = (int)TerrainSize;
                MAXXY = 0;
            }
        }
        else
        {
            MAXXY = (int)TerrainSize / EachTerrainSize;
            if (MAXXY > 1) EachTerrainSize--;
            terraints = new GameObject[MAXXY * MAXXY];

        }
        for(int ii=0;ii<terraints.Length;ii++)
        {
            terraints[ii] = new GameObject();
            terraints[ii].name = string.Format("TerrainPieces_{0}", ii);
            List<Vector3> vertices = new List<Vector3>();
            List<int> triangles = new List<int>();
            int EachTerrainSize_1 = EachTerrainSize == 256 ? 256 : EachTerrainSize + 1;
            for (int i = 0; i < EachTerrainSize_1; i++)
            {
                for (int j = 0; j < EachTerrainSize_1; j++)
                {
                    if (j == EachTerrainSize_1 && TX >= MAXXY-1)
                    {
                        break;
                    }
                    if (i == EachTerrainSize_1 && TY >= MAXXY - 1)
                    {
                        break;
                    }
                    int X =(int)((float)TX * (float)EachTerrainSize *((float)MapWidth/(float)TerrainSize) + (float)i * ((float)MapWidth / (float)TerrainSize));
                    int Y = (int)((float)TY * (float)EachTerrainSize *((float)MapHeight / (float)TerrainSize) + (float)j * ((float)MapHeight / (float)TerrainSize));
                    vertices.Add(new Vector3(TX*EachTerrainSize+i, HeightMap.GetPixel(X,Y).grayscale * TerrainHeight, TY*EachTerrainSize+j));
                    if (i == 0 || j == 0) continue;
                    triangles.Add(EachTerrainSize_1 * i + j);
                    triangles.Add(EachTerrainSize_1 * i + j - 1);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j);
                    triangles.Add(EachTerrainSize_1 * i + j - 1);
                    triangles.Add(EachTerrainSize_1 * (i - 1) + j - 1);
                }
            }
            Vector2[] uvs = new Vector2[vertices.Count];
            for (var i = 0; i < uvs.Length; i++)
            {
                uvs[i] = new Vector2(vertices[i].x, vertices[i].z);
            }
            terraints[ii].transform.parent = Origin.transform;
            terraints[ii].AddComponent<MeshFilter>();
            MeshRenderer renderer = terraints[ii].AddComponent<MeshRenderer>();
            renderer.sharedMaterial = TerrainMat;
            Mesh groundMesh = new Mesh();
            groundMesh.vertices = vertices.ToArray();
            groundMesh.uv = uvs;
            groundMesh.triangles = triangles.ToArray();
            groundMesh.RecalculateNormals();//生成法线
            terraints[ii].GetComponent<MeshFilter>().mesh = groundMesh;
            terraints[ii].AddComponent<MeshCollider>();
            if (createPrefab)
            {
                MeshToFile(terraints[ii].GetComponent<MeshFilter>(), terraints[ii].name+string.Format("_{0}",ii));
            }
            vertices.Clear();
            triangles.Clear();
            uvs = null;
            TX++;
            if (TX >= MAXXY)
            {
                TX = 0;
                TY++;
            }
        }
    }
    public static string MeshToString(MeshFilter mf)
    {
        Mesh m = mf.mesh;
        Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;
        StringBuilder sb = new StringBuilder();
        //写入文件名
        sb.Append("g ").Append(mf.name).Append("\n");
        //写入顶点,法线,uv,material,三角面
        foreach (Vector3 v in m.vertices)
        {
            sb.Append(string.Format("v {0} {1} {2}\n", v.x, v.y, v.z));
        }
        sb.Append("\n");
        foreach (Vector3 v in m.normals)
        {
            sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
        }
        sb.Append("\n");
        foreach (Vector3 v in m.uv)
        {
            sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
        }
        for (int material = 0; material < m.subMeshCount; material++)
        {
            sb.Append("\n");
            sb.Append("usemtl ").Append(mats[material].name).Append("\n");
            sb.Append("usemap ").Append(mats[material].name).Append("\n");

            int[] triangles = m.GetTriangles(material);
            for (int i = 0; i < triangles.Length; i += 3)
            {
                sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
                    triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1));
            }
        }
        return sb.ToString();
    }
    public static void MeshToFile(MeshFilter mf, string filename)
    {
        using (StreamWriter sw = new StreamWriter("Assets\\" + filename + ".obj"))
        {
            sw.Write(MeshToString(mf));
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_33967521/article/details/87358662