You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity3D地形异常排查:TerrainData.SetHeights边缘悬垂问题

Unity地形边缘悬垂问题:地形与海拔数组尺寸不匹配的解决方案

嘿,我仔细看了你的代码和问题描述,这个边缘悬垂的问题主要是因为地形尺寸、高度图顶点数和你的海拔数组之间的对应关系没理清楚,加上代码里几个逻辑小问题导致的。下面给你拆解问题和修正方案:

核心问题分析

1. 地形尺寸计算逻辑有漏洞

你在MapScale里用firstRun只取第一行的长度作为terrainWidth,如果数组里某行长度和第一行不一样(哪怕你预期是200x157),就会算错宽度。而且嵌套循环计算maxHeight也有点冗余。

2. 高度图分辨率设置错误

Unity的heightmapResolution是用来设置方形高度图的分辨率的,如果你需要200x157的非方形地形,直接设置这个属性会强制高度图变成方形(比如取宽高中的最大值+1),导致其中一个方向的顶点数远超需要,自然会出现边缘悬垂。

3. 高度图数组与地形尺寸不匹配

Unity地形的物理尺寸和高度图顶点数的关系是:物理尺寸 = (顶点数 - 1) × 顶点间隔(默认间隔是1单位)。你的海拔数组是200x157,但你创建的高度图数组是float[terrainHeight, terrainWidth],同时把地形size设为(terrainWidth, terrainDepth, terrainHeight),这就导致顶点数和物理尺寸不匹配,Unity会自动填充缺失的顶点高度(默认0),形成边缘的悬垂区域。

修正后的代码

1. 修正MapScale方法

把尺寸计算逻辑简化,确保准确获取数组的行列数,用LINQ快速计算最大高度:

private void MapScale(int[][] mapData)
{
    // 先做参数校验,避免空数组报错
    if (mapData == null || mapData.Length == 0 || mapData[0].Length == 0)
    {
        Debug.LogError("传入的地图数据无效!");
        return;
    }
    terrainHeight = mapData.Length; // 对应地形z方向的顶点数
    terrainWidth = mapData[0].Length; // 对应地形x方向的顶点数
    terrainDepth = 10;
    // 用LINQ快速获取所有元素中的最大值
    maxHeight = mapData.SelectMany(row => row).Max();
    Debug.Log($"最大高度: {maxHeight}, 地形顶点尺寸: {terrainWidth}x{terrainHeight}");
}

2. 修正GenerateTerrain方法

放弃使用heightmapResolution,直接设置高度图的宽高,同时让地形物理尺寸和顶点数对应起来:

private TerrainData GenerateTerrain(TerrainData terrainData, int[][] mapData)
{
    // 直接设置高度图的顶点数,和你的数组尺寸完全匹配
    terrainData.heightmapWidth = terrainWidth;
    terrainData.heightmapHeight = terrainHeight;
    // 地形物理尺寸 = (顶点数 - 1) × 1单位(每个顶点间隔1米)
    // 如果你的数组是"块高度"(200x157块),那顶点数应该是201x158,这里根据你的需求调整
    terrainData.size = new Vector3(terrainWidth - 1, terrainDepth, terrainHeight - 1);
    Debug.Log($"地形物理尺寸: {terrainData.size.x}x{terrainData.size.z}");
    Debug.Log($"高度图维度: {terrainData.heightmapWidth}x{terrainData.heightmapHeight}");
    // 传入完全匹配的高度数组
    terrainData.SetHeights(0, 0, GenerateHeights(mapData));
    return terrainData;
}

3. 优化GenerateHeights方法

增加越界保护,避免数组行长度不一致时报错:

private float[,] GenerateHeights(int[][] mapData)
{
    Debug.Log($"生成{terrainHeight}x{terrainWidth}的高度数据");
    float[,] heights = new float[terrainHeight, terrainWidth];
    for (int z = 0; z < terrainHeight; z++)
    {
        for (int x = 0; x < terrainWidth; x++)
        {
            // 防止某行长度不足导致索引越界
            if (x >= mapData[z].Length)
            {
                heights[z, x] = 0;
                continue;
            }
            float value = mapData[z][x];
            // 避免maxHeight为0时除以0的错误
            heights[z, x] = maxHeight > 0 ? (value / maxHeight) : 0;
        }
    }
    return heights;
}

4. 可选:创建全新的TerrainData

如果你的预制体自带默认高度图设置,可能会干扰你的修改,建议直接创建新的TerrainData:

public void CreateTerrain(int[][] mapData)
{
    MapScale(mapData);
    // 创建全新的TerrainData,避免预制体默认设置的干扰
    TerrainData terrainData = new TerrainData();
    terrainData = GenerateTerrain(terrainData, mapData);
    // 生成Terrain游戏对象
    Terrain.CreateTerrainGameObject(terrainData).transform.position = Vector3.zero;
}

额外说明

如果你的需求是地形物理尺寸为200x157(而不是顶点数),那你的海拔数组应该是201x158的顶点高度数据(因为200单位宽需要201个顶点)。你需要调整MapScale里的尺寸计算,或者修改数组的生成逻辑,确保顶点数和物理尺寸对应。

内容的提问来源于stack exchange,提问作者Patrick Neumüller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:32:35