如何拼接裙边与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; } }
关键逻辑说明
- 顶点近似匹配:新增
TryFindMatchingBorderVertex函数,处理浮点精度导致的顶点位置偏差问题,确保低LOD边缘顶点能找到对应的LOD0边框顶点。 - 缝合三角面生成:遍历低LOD的边缘顶点,找到相邻顶点后生成两个三角面组成四边形,填补低LOD与LOD0边框之间的间隙。
- 边缘顶点筛选:分别在LOD0和低LOD网格中筛选需要参与缝合的边缘顶点,减少无效计算,确保只处理关键的边界部分。
内容的提问来源于stack exchange,提问作者UnknownUser
相关产品推荐
相关产品推荐

