You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity中修改3D网格贴合地形时如何保留厚度?

Unity中修改3D网格贴合地形时如何保留厚度?

你遇到的问题我太懂了——好不容易让网格贴了地形,结果模型直接变“薄饼”,所有细节全丢了!从你描述的情况和示意图来看,核心问题是你当前的代码把所有顶点都拉到了地形表面附近,没有保留每个顶点原本相对于模型底部的垂直距离。

先理清楚问题根源:你之前的做法是找到地形点后,给所有顶点统一加了一个height(模型的总高度),但这个height是整个模型的高度,不是每个顶点到模型底部的相对高度。比如原模型上一个凸起的顶点,原本比底部高5个单位,而一个凹陷的顶点只高1个单位,现在你统一加3个单位,自然就把所有顶点拉到了同一高度附近,细节全没了。

下面给你一套修正方案,核心思路是记录每个顶点的相对垂直偏移量,让它们在地形上保持原有的垂直距离

关键修改思路

  1. 初始化时,给每个顶点记录它相对于原模型底部的垂直高度(比如原模型底部Y是1,某顶点Y是4,那它的相对高度就是3)
  2. 变形时,找到地形的命中点后,把顶点放在这个命中点上方,保持它原本的相对高度,这样每个顶点的位置都会对应地形,同时保留原模型的厚度和细节

修改后的完整代码

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;
    }
}

代码关键部分解释

  1. vertexRelativeHeights数组:这是解决问题的核心,它记录了每个顶点离原模型底部的垂直距离,相当于把原模型的“厚度信息”单独存了下来
  2. 初始化时的相对高度计算originalMeshBounds.min.y是原模型的底部Y坐标,每个顶点的Y减去这个值,就是它相对于底部的高度,这样每个顶点的“高度差”都被精准保存
  3. 变形时的位置计算:不再统一加固定高度,而是给每个顶点加上它自己的相对高度,这样原模型上的凸起、凹陷都会在地形上得到保留,完美贴合的同时不会丢失厚度

对你之前尝试的法线方法的补充

你之前尝试用法线来保持距离的思路是对的,但方向偏了——法线偏移适合保持模型的“表面偏移”,但我们要的是垂直厚度,也就是模型在Y轴上的相对距离。所以用记录相对垂直高度的方法,更直接也更适合你的需求,因为我们要保留的是原模型的垂直结构,而不是基于法线的表面偏移。

按照这个方法修改后,你的模型应该就能像你示意图里展示的那样,完美贴合地形的同时,完完整整保留原有的厚度和细节了!

备注:内容来源于stack exchange,提问作者Takase

火山引擎 最新活动