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

如何拼接裙边与LOD地形网格块以消除接缝间隙?

问题

将Unity地形导出为Mesh后,因地形过大分割为多个Chunk并生成Chunk LOD,根据玩家距离切换LOD等级时,不同LOD等级的块连接出现间隙。需要给低于LOD0的块网格添加LOD0密度的边框,通过在边框顶点与低LOD块边缘顶点之间构建四边形来连接两者(实现类似边框缝合的效果)。

现有代码已能提取LOD0块的边框数据,但AppendBorderDataToMesh函数仅简单合并网格,未完成连接逻辑,需要补充缝合三角面的生成。

修改后的完整代码

using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Unity.Collections;
using UnityEngine;

public class MeshStitchGeneration : MonoBehaviour
{
    private struct BorderData : IDisposable
    {
        public NativeList<Vector3> borderVertices;
        public NativeList<int>     borderTriangles;

        public static BorderData Null => default;

        public BorderData(Allocator allocator)
        {
            borderVertices = new(allocator);
            borderTriangles = new(allocator);
        }

        public void Dispose()
        {
            borderVertices.Dispose();
            borderTriangles.Dispose();
        }
    }

    [SerializeField] private Mesh lod0Mesh     = null;
    [SerializeField] private Mesh lowerLODMesh = null;
    [SerializeField] private Material material = null;

    [Button("Generate")]
    private void Generate()
    {
        var newObj = new GameObject("Generated");
        
        newObj.transform.SetParent(gameObject.transform);
        newObj.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);

        var filter = newObj.AddComponent<MeshFilter>();
        var renderer = newObj.AddComponent<MeshRenderer>();
        renderer.sharedMaterial = material;

        using var borderMeshData = GetBorderMeshData();
        var newStitchedMesh = AppendBorderDataToMesh(in borderMeshData);
        filter.sharedMesh = newStitchedMesh;
    }

    private BorderData GetBorderMeshData()
    {
        if (lod0Mesh == null)
        {
            Debug.LogError("No lod0Mesh assigned!");
            return BorderData.Null;
        }

        var bound = lod0Mesh.bounds;
        if (bound.size.x != bound.size.z)
        {
            Debug.LogError("Requires a square mesh!");
            return BorderData.Null;
        }

        var vertices = lod0Mesh.vertices.AsSpan();
        var triangles = lod0Mesh.triangles.AsSpan();

        var gridSize = Mathf.RoundToInt(Mathf.Sqrt(vertices.Length));
        var vertexIncrement = bound.size.x / (gridSize - 1);

        var data = new BorderData(Allocator.Persistent);
        var vertexIndexMapping = new Dictionary<int, int>();

        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 vertex = vertices[i];
            var ox = bound.min.x;
            var oz = bound.min.z;

            // 筛选LOD0的边框顶点(保留边缘两层)
            if (vertex.x < ox + bound.size.x - vertexIncrement
                && vertex.z < oz + bound.size.z - vertexIncrement
                && vertex.x > ox + vertexIncrement * 2
                && vertex.z > oz + vertexIncrement * 2)
            {
                continue;
            }

            vertexIndexMapping[i] = data.borderVertices.Length;
            data.borderVertices.Add(vertex);
        }

        // 提取边框三角面
        for (int i = 0; i < triangles.Length; i += 3)
        {
            int v1 = triangles[i];
            int v2 = triangles[i + 1];
            int v3 = triangles[i + 2];

            bool isV1OnBorder = vertexIndexMapping.ContainsKey(v1);
            bool isV2OnBorder = vertexIndexMapping.ContainsKey(v2);
            bool isV3OnBorder = vertexIndexMapping.ContainsKey(v3);

            if (isV1OnBorder || isV2OnBorder || isV3OnBorder)
            {
                if (isV1OnBorder) data.borderTriangles.Add(vertexIndexMapping[v1]);
                if (isV2OnBorder) data.borderTriangles.Add(vertexIndexMapping[v2]);
                if (isV3OnBorder) data.borderTriangles.Add(vertexIndexMapping[v3]);
            }
        }
        return data;
    }

    private Mesh AppendBorderDataToMesh(in BorderData borderData)
    {
        if (lowerLODMesh == null)
        {
            Debug.LogError("No lowerLODMesh assigned!");
            return null;
        }

        var newMesh = new Mesh();
        var lowerVertices = lowerLODMesh.vertices;
        var lowerTriangles = lowerLODMesh.triangles;

        var combinedVertices = new List<Vector3>(lowerVertices.Length + borderData.borderVertices.Length);
        var combinedTriangles = new List<int>(lowerTriangles.Length + borderData.borderTriangles.Length);

        // 添加低LOD顶点
        combinedVertices.AddRange(lowerVertices);
        int lowerVertexCount = lowerVertices.Length;

        // 添加边框顶点并建立位置到合并后索引的映射
        var borderPosToIndex = new Dictionary<Vector3, int>();
        foreach (var borderVertex in borderData.borderVertices)
        {
            int index = combinedVertices.Count;
            combinedVertices.Add(borderVertex);
            borderPosToIndex[borderVertex] = index;
        }

        // 添加低LOD原有的三角面
        combinedTriangles.AddRange(lowerTriangles);

        // 添加边框原有的三角面(偏移索引)
        foreach (var triangleIndex in borderData.borderTriangles)
        {
            combinedTriangles.Add(lowerVertexCount + triangleIndex);
        }

        // 生成缝合用的三角面:连接低LOD边缘顶点和边框顶点
        // 第一步:建立低LOD边缘顶点的位置到索引的映射
        var lowerEdgePosToIndex = new Dictionary<Vector3, int>();
        var lowerBound = lowerLODMesh.bounds;
        float lowerVertexIncrement = lowerBound.size.x / (Mathf.RoundToInt(Mathf.Sqrt(lowerVertices.Length)) - 1);

        for (int i = 0; i < lowerVertices.Length; i++)
        {
            Vector3 vertex = lowerVertices[i];
            var ox = lowerBound.min.x;
            var oz = lowerBound.min.z;

            // 筛选低LOD的边缘顶点(最外层)
            if (vertex.x <= ox + lowerVertexIncrement * 0.1f
                || vertex.x >= ox + lowerBound.size.x - lowerVertexIncrement * 0.1f
                || vertex.z <= oz + lowerVertexIncrement * 0.1f
                || vertex.z >= oz + lowerBound.size.z - lowerVertexIncrement * 0.1f)
            {
                lowerEdgePosToIndex[vertex] = i;
            }
        }

        // 第二步:遍历低LOD边缘顶点,找到对应的边框顶点,生成四边形三角面
        foreach (var lowerEdgePair in lowerEdgePosToIndex)
        {
            Vector3 lowerPos = lowerEdgePair.Key;
            int lowerIndex = lowerEdgePair.Value;

            // 查找同位置的边框顶点(允许微小误差,用近似匹配)
            if (TryFindMatchingBorderVertex(lowerPos, borderPosToIndex, out int borderIndex))
            {
                // 找到X轴方向相邻的低LOD边缘顶点和边框顶点,生成四边形
                var neighborLowerPos = new Vector3(lowerPos.x + lowerVertexIncrement, lowerPos.y, lowerPos.z);
                if (lowerEdgePosToIndex.TryGetValue(neighborLowerPos, out int neighborLowerIndex)
                    && TryFindMatchingBorderVertex(neighborLowerPos, borderPosToIndex, out int neighborBorderIndex))
                {
                    // 生成两个三角面组成四边形:lowerIndex -> neighborLowerIndex -> neighborBorderIndex -> borderIndex
                    combinedTriangles.Add(lowerIndex);
                    combinedTriangles.Add(neighborLowerIndex);
                    combinedTriangles.Add(neighborBorderIndex);

                    combinedTriangles.Add(lowerIndex);
                    combinedTriangles.Add(neighborBorderIndex);
                    combinedTriangles.Add(borderIndex);
                }

                // 处理Z轴方向的相邻顶点
                neighborLowerPos = new Vector3(lowerPos.x, lowerPos.y, lowerPos.z + lowerVertexIncrement);
                if (lowerEdgePosToIndex.TryGetValue(neighborLowerPos, out neighborLowerIndex)
                    && TryFindMatchingBorderVertex(neighborLowerPos, borderPosToIndex, out neighborBorderIndex))
                {
                    combinedTriangles.Add(lowerIndex);
                    combinedTriangles.Add(neighborLowerIndex);
                    combinedTriangles.Add(neighborBorderIndex);

                    combinedTriangles.Add(lowerIndex);
                    combinedTriangles.Add(neighborBorderIndex);
                    combinedTriangles.Add(borderIndex);
                }
            }
        }

        // 赋值网格数据并计算法线和边界
        newMesh.vertices = combinedVertices.ToArray();
        newMesh.triangles = combinedTriangles.ToArray();
        newMesh.RecalculateNormals();
        newMesh.RecalculateBounds();

        return newMesh;
    }

    // 近似匹配顶点位置(解决浮点精度问题)
    private bool TryFindMatchingBorderVertex(Vector3 targetPos, Dictionary<Vector3, int> borderPosMap, out int index)
    {
        const float tolerance = 0.001f;
        foreach (var pair in borderPosMap)
        {
            if (Vector3.Distance(pair.Key, targetPos) < tolerance)
            {
                index = pair.Value;
                return true;
            }
        }
        index = -1;
        return false;
    }
}

关键逻辑说明

  1. 顶点近似匹配:新增TryFindMatchingBorderVertex函数,处理浮点精度导致的顶点位置偏差问题,确保低LOD边缘顶点能找到对应的LOD0边框顶点。
  2. 缝合三角面生成:遍历低LOD的边缘顶点,找到相邻顶点后生成两个三角面组成四边形,填补低LOD与LOD0边框之间的间隙。
  3. 边缘顶点筛选:分别在LOD0和低LOD网格中筛选需要参与缝合的边缘顶点,减少无效计算,确保只处理关键的边界部分。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 08:33:08