Unity Lightmap之Terrain和Lightmap分块切割

本文介绍如何在Unity中实现大地形(Terrain)的动态切割,并为每个切割后的地形保存独立的Lightmap信息。通过代码示例,详细解释了如何利用TerrainData结构进行地形分割,以及如何为切割后的地形生成并保存Lightmap。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇 :Lightmap之动态加载单个物件和其对应的Lightmap

本系列前两篇文章,实际上说明了两点:

1.Lightmap是可以根据脚本动态切换。适用于同一场景白天,黑夜的变换。

2.Lightmap是可以分割,以物件为单位, 每个物件单独保存各自的Lightmap。当然这种分割只是逻辑上的分割,实际并没有分割,而是存储了各自的偏移量和索引。适用于大场景的动态加载物件,能确保物件的Lightmap信息不丢失。

了解了上面两点,那另外一种实际应用就迎刃而解了。这种应用就是,大地型(terrain)的切割。场景比较大,大地形的需要切割,按九宫格之类的形式加载。那要解决这个问题,需要实现两点:

1.Terrain的切割

2.切割后的各个Terrain的Lightmap信息随Terrain单独保存。

完整代码下载

先跳过第1点,第2点其实与上一篇 Lightmap之动态加载单个物件和其对应的Lightmap 很类似,只是之前处理的是Mesh,这里要处理的是Terrain。实现需要的关键信息原来MeshRenderer上带的索引和偏移量,在Terrain中也是带的有的。因此只要代码做小小的调整,把MeshRenderer换成Terrain。

       回过来说第1点,如何切割Terrain? Terrain组件中包含一个可访问的TerrainData的数据结构,有了它,我们可以就是对Terrain进行切割,构建新的分块Terrain。下面的代码是把一块地形切成4*4供16个分块地形。

   //开始分割地形
    [MenuItem("Terrain/Slicing")]
    private static void Slicing()
    {
        Terrain terrain = GameObject.FindObjectOfType<Terrain>();
        if (terrain == null)
        {
            Debug.LogError("找不到地形!");
            return;
        }

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

        TerrainData terrainData = terrain.terrainData;

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

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

        //循环宽和长,生成小块地形
        for (int x = 0; x < SLICING_SIZE; ++x)
        {
            for (int y = 0; y < SLICING_SIZE; ++y)
            {
                //创建资源
                TerrainData newData = new TerrainData();
                string terrainName = TerrainSavePath + TerrainLoad.TERRAIN_NAME + y + "_" + x + ".asset";
                AssetDatabase.CreateAsset(newData, TerrainSavePath + TerrainLoad.TERRAIN_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);

                //设置地形原型
                SplatPrototype[] newSplats = new SplatPrototype[splatProtos.Length];
                for (int i = 0; i < splatProtos.Length; ++i)
                {
                    newSplats[i] = new SplatPrototype();
                    newSplats[i].texture = splatProtos[i].texture;
                    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.splatPrototypes = 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();
    }

 

切片过程 1)开始切片过程,在Unity编辑器顶部选择地形选项,然后单击“切片地形”选项。一个窗口会出现一些配置信息。 2)拖动您希望分割为“地形切片”字段的地形。或者,如果您在步骤1中单击“切片地形”选项时选择了地形,则该字段中已经出现了地形。 3)输入每个补丁的详细分辨率。优选地,该值应与每个补丁值的基本地形细节分辨率匹配。这些信息不能通过脚本访问,这就是为什么你必须在这里输入它。您可以输入与基础地形设置不同的值,但这将导致细节网格(植物草地)复制的准确性降低。 4)选择您希望结束的切片维度。2×2仅仅意味着基本地形将沿着X轴2次Z轴2次分割,以创建4个地形片。64×64未经测试,不建议使用,所以请自行承担风险。 5)设置希望存储地形数据的文件路径。默认情况下,这是资产/ terrainslicing /地形数据。如果您希望暂时在另一个文件夹中创建地形数据,只需在这里输入新路径。如果希望永久更改默认文件夹,请输入新的文件路径,并选择“保存当前文件路径作为默认文件路径”按钮。请确保没有“/”后的文件路径上的文件夹名称(例如,用于文件的默认路径的地形数据后),否则将会出现错误。 6)当单击“创建地形”按钮时,选择是否覆盖现有的地形数据。这是一个安全功能,以确保你不小心覆盖的地形数据,你已经创造了。如果试图在未选中此值时重写数据,则会出现警告消息,告诉您要检查此值,而切片操作将不会开始。 7)单击“创建地形”按钮,等待进度条填充。如果进度条未显示,则在编辑器窗口显示通知错误的通知消息。有时您可能需要检查控制台以获得更详细的信息。最后,确保只在编辑模式下执行切片地形脚本。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值