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

Unity低多边形程序化浮空岛网格生成问题求助

Unity低多边形程序化浮空岛网格生成问题解决指南

你现在做低多边形浮空岛的思路是对的——基于球体修改,但代码里几个关键逻辑出了问题,导致形态不对,我帮你拆解下问题,再给你修正后的代码和其他可选方案。

一、你的代码核心问题分析

  • 动态值滥用:你在计算顶点坐标时混用了Time.timeRandom.Range,这会导致每一帧顶点都在乱跳,网格根本没法稳定下来,更别说得到固定的曲面形态了
  • 顶部扁平化逻辑错误:直接把上半部分顶点的y设为0,会让所有顶部顶点都挤在同一平面,自然会出现渐变收缩的问题,应该是把顶部区域的顶点向一个平坦的面偏移,而不是直接归零
  • 噪声应用逻辑混乱:你没有基于球体的原始顶点做偏移,而是直接修改坐标分量,导致球体的基础形态被破坏,噪声也起不到自然扰动的作用
  • 底部顶点衔接差:最后一个顶点的设置太随意,没有和底部的环形顶点衔接,会导致底部网格出现破面
  • 性能与同步问题Update里每次都新建Mesh对象,而且协程的顶点计算和Update的网格赋值不同步,容易出现异常

二、修正后的完整代码

下面是调整后的代码,我保留了你的球体基础框架,修复了上述问题,实现了顶部平坦、底部带自然噪声的低多边形浮空岛:

using System.Collections;
using UnityEngine;

public class FloatingIslandGenerator : MonoBehaviour
{
    public float baseRadius = 3f;
    public float topFlatness = 1.2f; // 控制顶部平坦程度,值越大顶部越平
    public float bottomNoiseStrength = 0.8f; // 底部噪声强度
    public int longitudeSegments = 24;
    public int latitudeSegments = 16;

    private MeshFilter _meshFilter;
    private Mesh _mesh;

    void Start()
    {
        _meshFilter = GetComponent<MeshFilter>();
        _mesh = new Mesh();
        _meshFilter.mesh = _mesh;
        StartCoroutine(GenerateIslandMesh());
    }

    IEnumerator GenerateIslandMesh()
    {
        #region 顶点计算
        Vector3[] vertices = new Vector3[(longitudeSegments + 1) * latitudeSegments + 2];
        float pi = Mathf.PI;
        float twoPi = pi * 2f;

        // 顶部顶点(保留,但后续会把上半部分顶点拉平)
        vertices[0] = Vector3.up * baseRadius;

        for (int lat = 0; lat < latitudeSegments; lat++)
        {
            // 计算当前纬度的角度
            float latAngle = pi * (float)(lat + 1) / (latitudeSegments + 1);
            float sinLat = Mathf.Sin(latAngle);
            float cosLat = Mathf.Cos(latAngle);

            // 区分顶部平坦区域和底部噪声区域
            bool isTopHalf = lat <= latitudeSegments / 2;

            for (int lon = 0; lon <= longitudeSegments; lon++)
            {
                float lonAngle = twoPi * (float)(lon == longitudeSegments ? 0 : lon) / longitudeSegments;
                float sinLon = Mathf.Sin(lonAngle);
                float cosLon = Mathf.Cos(lonAngle);

                // 基础球体顶点
                Vector3 baseVertex = new Vector3(sinLat * cosLon, cosLat, sinLat * sinLon) * baseRadius;

                if (isTopHalf)
                {
                    // 顶部区域:拉平处理,让y坐标趋近于顶部顶点的y值
                    float flatFactor = 1 - (float)lat / (latitudeSegments / 2);
                    baseVertex.y = Mathf.Lerp(baseVertex.y, vertices[0].y, flatFactor * topFlatness);
                }
                else
                {
                    // 底部区域:添加Perlin噪声扰动,用顶点的x/z作为噪声坐标(避免抖动)
                    float noise = Mathf.PerlinNoise(baseVertex.x * 0.5f, baseVertex.z * 0.5f) * bottomNoiseStrength;
                    // 只向下偏移,避免破坏浮空岛的浮空感
                    baseVertex.y -= noise;
                }

                vertices[lon + lat * (longitudeSegments + 1) + 1] = baseVertex;
                yield return null; // 协程分步生成,避免卡顿
            }
        }

        // 底部顶点:和底部环形顶点衔接,取底部区域的平均y值
        float bottomY = 0;
        int bottomStartIndex = (latitudeSegments - 1) * (longitudeSegments + 1) + 1;
        for (int lon = 0; lon <= longitudeSegments; lon++)
        {
            bottomY += vertices[bottomStartIndex + lon].y;
        }
        bottomY /= (longitudeSegments + 1);
        vertices[vertices.Length - 1] = new Vector3(0, bottomY, 0);
        #endregion

        #region 法线计算
        Vector3[] normals = new Vector3[vertices.Length];
        for (int i = 0; i < vertices.Length; i++)
        {
            normals[i] = vertices[i].normalized;
        }
        #endregion

        #region UV计算(保留原逻辑,可后续调整适配纹理)
        Vector2[] uvs = new Vector2[vertices.Length];
        uvs[0] = Vector2.up;
        uvs[uvs.Length - 1] = Vector2.zero;

        for (int lat = 0; lat < latitudeSegments; lat++)
        {
            for (int lon = 0; lon <= longitudeSegments; lon++)
            {
                uvs[lon + lat * (longitudeSegments + 1) + 1] = new Vector2((float)lon / longitudeSegments, 1f - (float)(lat + 1) / (latitudeSegments + 1));
            }
        }
        #endregion

        #region 三角形索引计算
        int[] triangles = new int[((longitudeSegments * 2) + (longitudeSegments * latitudeSegments * 2)) * 3];
        int index = 0;

        // 顶部帽状三角面
        for (int lon = 0; lon < longitudeSegments; lon++)
        {
            triangles[index++] = 0;
            triangles[index++] = lon + 1;
            triangles[index++] = lon + 2;
        }

        // 中间主体三角面
        for (int lat = 0; lat < latitudeSegments - 1; lat++)
        {
            for (int lon = 0; lon < longitudeSegments; lon++)
            {
                int current = lon + lat * (longitudeSegments + 1) + 1;
                int nextRow = current + longitudeSegments + 1;

                triangles[index++] = current;
                triangles[index++] = nextRow;
                triangles[index++] = nextRow + 1;

                triangles[index++] = current;
                triangles[index++] = nextRow + 1;
                triangles[index++] = current + 1;
            }
        }

        // 底部帽状三角面
        int bottomVertexIndex = vertices.Length - 1;
        for (int lon = 0; lon < longitudeSegments; lon++)
        {
            int current = bottomStartIndex + lon;
            int next = bottomStartIndex + lon + 1;
            triangles[index++] = bottomVertexIndex;
            triangles[index++] = next;
            triangles[index++] = current;
        }
        #endregion

        // 赋值并更新网格
        _mesh.Clear();
        _mesh.vertices = vertices;
        _mesh.normals = normals;
        _mesh.uv = uvs;
        _mesh.triangles = triangles;
        _mesh.RecalculateNormals();
        _mesh.RecalculateBounds();
    }
}

代码关键修改点说明

  • 移除动态干扰值:去掉了Time.time和随机值,改用固定的Perlin噪声基于顶点坐标计算,保证网格形态稳定
  • 顶部平坦化优化:用线性插值Mathf.Lerp让顶部区域的顶点逐渐向顶部顶点的y值过渡,避免突然归零导致的收缩
  • 噪声合理应用:底部区域只对y轴做向下的噪声偏移,基于顶点的x/z坐标计算噪声,保证扰动自然且不破坏整体形态
  • 底部顶点衔接:计算底部环形顶点的平均y值来设置底部中心顶点,避免破面
  • 性能优化:只创建一次Mesh对象,协程分步生成顶点避免卡顿,同步更新网格

三、其他可选算法思路

如果觉得基于球体修改的方式不够灵活,还可以试试这些方法:

  • 平面抬升法:先创建一个圆形平面,然后对平面的顶点用Perlin噪声抬升高度,边缘逐渐降低,再给底部添加一些向下的扰动,最后添加侧面网格形成浮空岛
  • Marching Cubes算法:通过定义3D噪声场,提取等值面来生成更自然的有机形态,适合生成复杂的浮空岛地形,但实现起来稍复杂
  • 低多边形地形工具:直接使用Unity Asset Store里的低多边形地形生成插件,能快速生成各种风格的浮空岛,节省开发时间

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

火山引擎 最新活动