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

Unity Terrain移除建筑下方草地绘制地面层实现方案

原代码失效原因

你写的逻辑出黑色斑块是几个硬伤导致的:

  • 新建的alphamap数组没有读取地形原有数据,C#里新建的float数组默认值全是0,相当于所有地形层权重都是0,自然渲染出纯黑色
  • 你初始化的element数组只给[0,0]这一个像素点赋了值,其余所有像素权重全是0,调用SetAlphamaps时会把覆盖区域除了单个点之外的地方全清成黑块
  • 坐标计算完全不对:只取了建筑坐标对应的单个像素点,没有覆盖建筑整个占地面积,也没算周边预留的清理范围
  • 没有做单位转换:直接把建筑的世界尺寸(单位米)当成alphamap像素数用,实际不同地形尺寸下单个alphamap像素对应的世界大小完全不一样
  • 没做边界校验,坐标超出地形范围时会直接抛错或者写入异常
  • 没有保证单像素所有层权重和为1,渲染必然出异常
正确实现代码

以下代码默认约定:alphamap第0层为草地,第1层为需要铺设的硬地面层,周边额外清理半径默认1米,可按需传参调整

/// <summary>
/// 清除建筑本体及周边范围的草地,铺设指定地面层
/// </summary>
/// <param name="buildingPos">建筑的世界坐标位置</param>
/// <param name="buildingSize">建筑占地的世界尺寸,x、z对应水平方向长宽</param>
/// <param name="extraClearRadius">建筑周边额外清除草地的半径,单位:米</param>
/// <param name="groundLayerIndex">待铺设地面层在alphamap中的索引,默认1</param>
/// <param name="grassLayerIndex">草地层在alphamap中的索引,默认0</param>
private void SetGroundOnPosition(Vector3 buildingPos, Vector3 buildingSize, float extraClearRadius = 1f, int groundLayerIndex = 1, int grassLayerIndex = 0)
{
    // 缓存地形组件和基础数据
    Terrain targetTerrain = GameObject.Find("Terrain").GetComponent<Terrain>();
    TerrainData terrainData = targetTerrain.terrainData;
    Vector3 terrainOriginPos = targetTerrain.transform.position;

    // 计算单个alphamap像素对应的世界空间尺寸
    float pixelWorldWidth = terrainData.size.x / terrainData.alphamapWidth;
    float pixelWorldDepth = terrainData.size.z / terrainData.alphamapHeight;

    // 计算需要修改的总范围(建筑尺寸+两侧额外清理半径),转成alphamap像素尺寸
    float totalWorldWidth = buildingSize.x + extraClearRadius * 2;
    float totalWorldDepth = buildingSize.z + extraClearRadius * 2;
    int pixelWidth = Mathf.CeilToInt(totalWorldWidth / pixelWorldWidth);
    int pixelDepth = Mathf.CeilToInt(totalWorldDepth / pixelWorldDepth);

    // 计算修改区域的起点像素坐标,以建筑中心为基准对齐
    int startPixelX = Mathf.FloorToInt(
        ((buildingPos.x - totalWorldWidth / 2f) - terrainOriginPos.x)
        / terrainData.size.x * terrainData.alphamapWidth
    );
    int startPixelZ = Mathf.FloorToInt(
        ((buildingPos.z - totalWorldDepth / 2f) - terrainOriginPos.z)
        / terrainData.size.z * terrainData.alphamapHeight
    );

    // 边界钳制,避免坐标超出地形范围引发报错或写入异常
    startPixelX = Mathf.Clamp(startPixelX, 0, terrainData.alphamapWidth - pixelWidth);
    startPixelZ = Mathf.Clamp(startPixelZ, 0, terrainData.alphamapHeight - pixelDepth);
    pixelWidth = Mathf.Min(pixelWidth, terrainData.alphamapWidth - startPixelX);
    pixelDepth = Mathf.Min(pixelDepth, terrainData.alphamapHeight - startPixelZ);

    // 读取目标区域原有alphamap数据,禁止直接新建空数组覆盖
    float[,,] alphaData = terrainData.GetAlphamaps(startPixelX, startPixelZ, pixelWidth, pixelDepth);

    // 遍历区域内所有像素修改权重
    for (int z = 0; z < pixelDepth; z++)
    {
        for (int x = 0; x < pixelWidth; x++)
        {
            // 计算当前像素对应的世界坐标
            float currentWorldX = terrainOriginPos.x + (startPixelX + x) * pixelWorldWidth;
            float currentWorldZ = terrainOriginPos.z + (startPixelZ + z) * pixelWorldDepth;

            // 判断当前像素是否在清理范围内
            float dx = Mathf.Abs(currentWorldX - buildingPos.x);
            float dz = Mathf.Abs(currentWorldZ - buildingPos.z);
            bool needClear = dx <= buildingSize.x / 2f + extraClearRadius 
                            && dz <= buildingSize.z / 2f + extraClearRadius;

            if (needClear)
            {
                // 范围内清空草地权重,全权重切换到目标地面层
                alphaData[z, x, grassLayerIndex] = 0f;
                alphaData[z, x, groundLayerIndex] = 1f;
                // 其余无关层权重清零,保证单像素所有层权重和为1
                for (int layer = 2; layer < terrainData.alphamapLayers; layer++)
                {
                    alphaData[z, x, layer] = 0f;
                }
            }
        }
    }

    // 写回修改后的数据刷新地形
    terrainData.SetAlphamaps(startPixelX, startPixelZ, alphaData);
    targetTerrain.Flush();
}
使用注意事项
  • 调用前确认自身地形的alphamap层顺序,如果草地、硬地面的索引和默认约定不一致,传入对应索引参数即可
  • 地形数据修改有一定性能开销,不要在Update等高频生命周期里调用,建筑放置完成时调用一次即可
  • 如果需要草地和硬地面的过渡更自然,可以在范围边缘加权重渐变逻辑,不要硬切0和1的权重值,能消除明显接缝
  • 如果场景是多地形分片的结构,不要用GameObject.Find硬找地形对象,要根据建筑世界坐标定位所属的Terrain组件再修改,避免写错地形数据

内容的提问来源于stack exchange,提问作者Daniil999

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:54:04