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

基于Perlin Noise的Unity地形分块Mesh缺失及合并需求

地形分块Mesh对齐缺失修复与合并方案

一、先搞定分块Mesh的对齐问题

你的2D噪声图对齐没问题,但Mesh出现缺失/错位,问题根源在于每个Chunk的Mesh坐标没按它在大地图里的位置做偏移。原MeshGenerator.GenerateTerrainMesh是按当前Chunk尺寸计算局部坐标的,所有Chunk的Mesh原点都在自己中心,自然会在场景里重叠或错位。

改法如下:

  1. 给MeshGenerator.GenerateTerrainMesh加两个参数:float worldOffsetX和float worldOffsetZ,用来传递当前Chunk在世界空间的偏移量。
  2. 调整顶点坐标计算,把局部坐标加上世界偏移:

修改后的MeshGenerator代码:

public static class MeshGenerator
{
    // 新增世界偏移参数
    public static MeshData GenerateTerrainMesh(float[,] heightMap, float heightMultiplier, AnimationCurve _heightCurve, int levelOfDetail, float worldOffsetX, float worldOffsetZ)
    {
        AnimationCurve heightCurve = new AnimationCurve(_heightCurve.keys);

        int width = heightMap.GetLength(0);
        int height = heightMap.GetLength(1);
        float topLeftX = (width - 1) / -2f;
        float topLeftZ = (height - 1) / 2f;

        int meshSimplificationIncrement = (levelOfDetail == 0) ? 1 : levelOfDetail * 2;
        int verticesPerLine = (width - 1) / meshSimplificationIncrement + 1;

        MeshData meshData = new MeshData(verticesPerLine, verticesPerLine);
        int vertexIndex = 0;

        for (int y = 0; y < height; y += meshSimplificationIncrement)
        {
            for (int x = 0; x < width; x += meshSimplificationIncrement)
            {
                // 局部坐标 + 世界偏移 = 最终世界坐标
                float worldX = topLeftX + x + worldOffsetX;
                float worldZ = topLeftZ - y + worldOffsetZ;
                meshData.vertices[vertexIndex] = new Vector3(worldX, heightCurve.Evaluate(heightMap[x, y]) * heightMultiplier, worldZ);
                meshData.uvs[vertexIndex] = new Vector2(x / (float)width, y / (float)height);

                if (x < width - 1 && y < height - 1)
                {
                    meshData.AddTriangle(vertexIndex, vertexIndex + verticesPerLine + 1, vertexIndex + verticesPerLine);
                    meshData.AddTriangle(vertexIndex + verticesPerLine + 1, vertexIndex, vertexIndex + 1);
                }

                vertexIndex++;
            }
        }

        return meshData;
    }
}
  1. 在GenerateMap里给每个Chunk计算对应偏移,调用生成方法时传入:
// 循环生成Chunk时,根据索引计算偏移
for (var chunkIndex = 0; chunkIndex < noiseMapChunks.Count; chunkIndex++)
{
    float offsetX = 0;
    float offsetZ = 0;
    // 按Chunk在大地图的位置计算偏移(大地图为2*2个Chunk)
    switch(chunkIndex)
    {
        case 0: // 左上
            offsetX = -mapChunkSize;
            offsetZ = mapChunkSize;
            break;
        case 1: // 右上
            offsetX = 0;
            offsetZ = mapChunkSize;
            break;
        case 2: // 左下
            offsetX = -mapChunkSize;
            offsetZ = 0;
            break;
        case 3: // 右下
            offsetX = 0;
            offsetZ = 0;
            break;
    }
    // 微调偏移让Chunk边缘完美对齐
    float worldOffsetX = offsetX + (mapChunkSize - 1)/2f;
    float worldOffsetZ = offsetZ - (mapChunkSize - 1)/2f;

    Color[] colourMap = new Color[mapChunkSize * mapChunkSize];
    // ... 原颜色图生成代码保持不变 ...

    // 调用修改后的生成方法,传入偏移量
    var meshData = MeshGenerator.GenerateTerrainMesh(noiseMapChunks[chunkIndex], meshHeightMultiplier, meshHeightCurve, levelOfDetail, worldOffsetX, worldOffsetZ);
    display.DrawMesh(meshData.CreateMesh(), TextureGenerator.TextureFromColourMap(colourMap, mapChunkSize, mapChunkSize), chunkIndex);
}

改完之后,四个Chunk的Mesh就能在场景里正确对齐,不会出现缺块问题。

二、合并多个Chunk Mesh的方案

如果确实需要合并Mesh,得注意Unity单个Mesh的顶点数限制——旧版Unity限制为65535个顶点,新版支持32位索引可扩展至数百万,但如果合并后超出限制,Mesh会创建失败。以下是合并方法:

方法:将多个MeshData合并为一个大MeshData

  1. 给MeshGenerator添加合并静态方法:
public static MeshData MergeMeshDatas(List<MeshData> meshDatas, List<Vector3> chunkOffsets)
{
    int totalVertices = 0;
    int totalTriangles = 0;

    // 先统计总顶点数和三角形数
    foreach(var data in meshDatas)
    {
        totalVertices += data.vertices.Length;
        totalTriangles += data.triangles.Length;
    }

    // 创建合并后的MeshData
    MeshData mergedData = new MeshData(totalVertices, totalVertices);
    mergedData.vertices = new Vector3[totalVertices];
    mergedData.uvs = new Vector2[totalVertices];
    mergedData.triangles = new int[totalTriangles];

    int vertexOffset = 0;
    int triangleOffset = 0;

    for(int i=0; i<meshDatas.Count; i++)
    {
        var data = meshDatas[i];
        var offset = chunkOffsets[i];

        // 复制顶点并添加Chunk偏移
        for(int v=0; v<data.vertices.Length; v++)
        {
            mergedData.vertices[vertexOffset + v] = data.vertices[v] + offset;
            mergedData.uvs[vertexOffset + v] = data.uvs[v];
        }

        // 复制三角形并调整索引偏移(因顶点总数变化)
        for(int t=0; t<data.triangles.Length; t++)
        {
            mergedData.triangles[triangleOffset + t] = data.triangles[t] + vertexOffset;
        }

        vertexOffset += data.vertices.Length;
        triangleOffset += data.triangles.Length;
    }

    return mergedData;
}
  1. 在GenerateMap里先收集所有Chunk的MeshData和偏移量,再执行合并:
public void GenerateMap()
{
    // ... 原噪声图拆分代码保持不变 ...

    display.ClearMeshes();
    List<MeshData> allMeshDatas = new List<MeshData>();
    List<Vector3> allChunkOffsets = new List<Vector3>();

    for (var chunkIndex = 0; chunkIndex < noiseMapChunks.Count; chunkIndex++)
    {
        // ... 原颜色图生成代码保持不变 ...

        float offsetX = 0;
        float offsetZ = 0;
        switch(chunkIndex)
        {
            case 0: offsetX = -mapChunkSize; offsetZ = mapChunkSize; break;
            case 1: offsetX = 0; offsetZ = mapChunkSize; break;
            case 2: offsetX = -mapChunkSize; offsetZ = 0; break;
            case 3: offsetX = 0; offsetZ = 0; break;
        }
        float worldOffsetX = offsetX + (mapChunkSize -1)/2f;
        float worldOffsetZ = offsetZ - (mapChunkSize -1)/2f;
        // 先不加偏移,合并时统一处理
        var meshData = MeshGenerator.GenerateTerrainMesh(noiseMapChunks[chunkIndex], meshHeightMultiplier, meshHeightCurve, levelOfDetail, 0, 0);
        allMeshDatas.Add(meshData);
        allChunkOffsets.Add(new Vector3(worldOffsetX, 0, worldOffsetZ));
    }

    // 合并MeshData
    MeshData mergedMeshData = MeshGenerator.MergeMeshDatas(allMeshDatas, allChunkOffsets);
    Mesh mergedMesh = mergedMeshData.CreateMesh();

    // 纹理合并可选:可将四个Chunk的纹理拼接为一张大纹理,或用UV偏移复用单张纹理
    display.DrawMesh(mergedMesh, null, 0); // 按你的Display方法调整
}

注意事项:

  • 如果合并后顶点数超出Unity限制,Mesh会创建失败,这种情况下仍需保持分块。
  • 纹理合并需额外处理,比如使用Texture2D.SetPixels拼接四张纹理,或直接复用原Chunk纹理(UV坐标已正确对应)。

三、额外排查点

  • 检查display.DrawMesh方法是否正确设置了每个Chunk的Transform位置,若直接创建MeshFilter未设置位置,也会导致对齐错误。
  • 原代码中upperRight的try-catch块可删除,排查是否存在数组越界问题,这也可能是Mesh缺块的原因之一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:23:10