Unity中修改3D网格贴合地形时如何保留厚度?
Unity中修改3D网格贴合地形时如何保留厚度?
你遇到的问题我太懂了——好不容易让网格贴了地形,结果模型直接变“薄饼”,所有细节全丢了!从你描述的情况和示意图来看,核心问题是你当前的代码把所有顶点都拉到了地形表面附近,没有保留每个顶点原本相对于模型底部的垂直距离。
先理清楚问题根源:你之前的做法是找到地形点后,给所有顶点统一加了一个height(模型的总高度),但这个height是整个模型的高度,不是每个顶点到模型底部的相对高度。比如原模型上一个凸起的顶点,原本比底部高5个单位,而一个凹陷的顶点只高1个单位,现在你统一加3个单位,自然就把所有顶点拉到了同一高度附近,细节全没了。
下面给你一套修正方案,核心思路是记录每个顶点的相对垂直偏移量,让它们在地形上保持原有的垂直距离:
关键修改思路
- 初始化时,给每个顶点记录它相对于原模型底部的垂直高度(比如原模型底部Y是1,某顶点Y是4,那它的相对高度就是3)
- 变形时,找到地形的命中点后,把顶点放在这个命中点上方,保持它原本的相对高度,这样每个顶点的位置都会对应地形,同时保留原模型的厚度和细节
修改后的完整代码
using UnityEngine; public class TerrainFitter : MonoBehaviour { public LayerMask groundLayer; public float maxRaycastDistance = 100f; public float deformationStrength = 1.0f; private MeshFilter meshFilter; private Mesh originalMesh; private Mesh workingMesh; private Vector3[] originalVertices; // 存储每个顶点相对于原模型底部的垂直偏移量 private float[] vertexRelativeHeights; private Bounds originalMeshBounds; private void Start() { InitializeMesh(); } // 可以在Update里调用,或者你需要的时机调用DeformMesh private void Update() { DeformMesh(); } private void InitializeMesh() { meshFilter = GetComponent<MeshFilter>(); if (meshFilter == null) { Debug.LogError("Missing MeshFilter component!"); return; } originalMesh = meshFilter.sharedMesh; workingMesh = Instantiate(originalMesh); meshFilter.mesh = workingMesh; originalVertices = originalMesh.vertices; originalMeshBounds = originalMesh.bounds; // 计算每个顶点相对于模型底部的垂直高度 vertexRelativeHeights = new float[originalVertices.Length]; float modelBottomY = originalMeshBounds.min.y; for (int i = 0; i < originalVertices.Length; i++) { vertexRelativeHeights[i] = originalVertices[i].y - modelBottomY; } } public void DeformMesh() { if (workingMesh == null || originalVertices == null || vertexRelativeHeights == null) { return; } Vector3[] deformedVerts = new Vector3[originalVertices.Length]; Transform objTransform = transform; for (int j = 0; j < originalVertices.Length; j++) { // 把原顶点转换成世界坐标 Vector3 worldVertexPos = objTransform.TransformPoint(originalVertices[j]); // 射线检测找地形 Vector3? groundHitPoint = GetGroundHitPoint(worldVertexPos); if (groundHitPoint == null) { // 没检测到地形,保持原位置 deformedVerts[j] = originalVertices[j]; continue; } // 计算目标位置:地形点上方,保持原顶点的相对垂直高度 Vector3 targetWorldPos = groundHitPoint.Value; targetWorldPos.y += vertexRelativeHeights[j]; // 用Lerp控制变形的平滑过渡(可选,根据你的需求调整) targetWorldPos = Vector3.Lerp(worldVertexPos, targetWorldPos, deformationStrength); // 转换回局部坐标 deformedVerts[j] = objTransform.InverseTransformPoint(targetWorldPos); } // 更新网格 workingMesh.vertices = deformedVerts; workingMesh.RecalculateNormals(); workingMesh.RecalculateBounds(); } private Vector3? GetGroundHitPoint(Vector3 worldPos) { // 优先向下射线检测(更符合大多数地形场景) if (Physics.Raycast(worldPos, Vector3.down, out RaycastHit hitDown, maxRaycastDistance, groundLayer)) { return hitDown.point; } // 向下没检测到的话,尝试向上(比如模型在地形下方的情况) if (Physics.Raycast(worldPos, Vector3.up, out RaycastHit hitUp, maxRaycastDistance, groundLayer)) { return hitUp.point; } return null; } }
代码关键部分解释
vertexRelativeHeights数组:这是解决问题的核心,它记录了每个顶点离原模型底部的垂直距离,相当于把原模型的“厚度信息”单独存了下来- 初始化时的相对高度计算:
originalMeshBounds.min.y是原模型的底部Y坐标,每个顶点的Y减去这个值,就是它相对于底部的高度,这样每个顶点的“高度差”都被精准保存 - 变形时的位置计算:不再统一加固定高度,而是给每个顶点加上它自己的相对高度,这样原模型上的凸起、凹陷都会在地形上得到保留,完美贴合的同时不会丢失厚度
对你之前尝试的法线方法的补充
你之前尝试用法线来保持距离的思路是对的,但方向偏了——法线偏移适合保持模型的“表面偏移”,但我们要的是垂直厚度,也就是模型在Y轴上的相对距离。所以用记录相对垂直高度的方法,更直接也更适合你的需求,因为我们要保留的是原模型的垂直结构,而不是基于法线的表面偏移。
按照这个方法修改后,你的模型应该就能像你示意图里展示的那样,完美贴合地形的同时,完完整整保留原有的厚度和细节了!
备注:内容来源于stack exchange,提问作者Takase




