【Unity】使用柏林噪声(Perlin Noise)生成地形Mesh

【Unity】使用柏林噪声(Perlin Noise)生成地形Mesh

写来备忘,注释里有些关键点说明,暂时没有其他文字说明。
复制到Unity中就能运行。

用例

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class Test : MonoBehaviour
{
    // 柏林噪声参数
    [Range(1, 10)]
    public int octaves = 4;             // 倍频
    [Range(0.0001f, 0.9999f)]
    public float persistence = 0.4f;    // 持续度
    [Range(0.0001f, 0.9999f)]
    public float scale = 0.01f;         // 采样缩放
    public int xLength = 100;           // Mesh在X轴方向上的长度
    public int zLength = 100;           // Mesh在Z轴方向上的长度
    public float mutiplier = 50.0f;     // 高度增幅
    public byte xVertexCount = 255;     // Mesh在X轴方向上的顶点数
    public byte zVertexCount = 255;     // Mesh在Z轴方向上的顶点数

    private MeshFilter _meshFilter;
    private RectMeshGenerator _meshGenerator;

    private void OnValidate()
    {
        GenerateMesh();
    }

    // 生成Mesh
    private void GenerateMesh()
    {
        if (!_meshFilter)
        {
            _meshFilter = GetComponent<MeshFilter>();
        }
        if (_meshGenerator == null)
        {
            _meshGenerator = new RectMeshGenerator(_meshFilter.sharedMesh);
        }

        _meshGenerator.CalculateMeshData(Vector3.zero, xLength, zLength, xVertexCount, zVertexCount, GetHeight);
        _meshGenerator.UpdateMeshData();
    }

    // 利用柏林噪声计算高度
    private float GetHeight(float xSample, float zSample)
    {
        // 如果不适用柏林噪声,就直接返回0
        //return 0;

        // 使用柏林噪声算法计算高度
        float total = 0;
        float frequency = 1;
        float amplitude = 1;
        float maxValue = 0;
        for (int i = 0; i < octaves; i++)
        {
            total += Mathf.PerlinNoise(xSample * scale * frequency, zSample * scale * frequency) * amplitude * mutiplier;
            maxValue += amplitude;
            amplitude *= persistence;
            frequency *= 2;
        }

        float value = total / maxValue;
        return value;
    }
}

Mesh生成代码

using System;
using UnityEngine;

/// <summary>
/// 生成矩形Mesh。
/// </summary>
public class RectMeshGenerator
{
    private Mesh _mesh;
    private Vector3[] _vertices;
    private int[] _triangles;

    public RectMeshGenerator(Mesh mesh)
    {
        _mesh = mesh;
    }

    /// <summary>
    /// 计算矩形Mesh顶点数据。
    /// </summary>
    /// <param name="origin">Mesh起点位置(左下角)</param>
    /// <param name="xLength">X轴方向上的长度(Unity单位)</param>
    /// <param name="zLength">Z轴方向上的长度(Unity单位)</param>
    /// <param name="xVertexCount">X轴方向上的顶点数</param>
    /// <param name="zVertexCount">Z轴方向上的顶点数</param>
    /// <param name="getHeight">计算每个顶点Y轴高度的方法</param>
    public void CalculateMeshData(Vector3 origin, float xLength, float zLength, byte xVertexCount, byte zVertexCount, Func<float, float, float> getHeight = null)
    {
        if (xVertexCount < 2 || zVertexCount < 2)
        {
            throw new ArgumentOutOfRangeException("Mesh每边至少要有2个顶点");
        }

        if (getHeight == null)
        {
            getHeight = (x, z) => 0;
        }

        // 计算顶点总数和顶点索引总数
        int vertexCount = xVertexCount * zVertexCount;
        int indexCount = (xVertexCount - 1) * 6 * (zVertexCount - 1);
        _vertices = new Vector3[vertexCount];
        _triangles = new int[indexCount];

        // 计算顶点横纵间距和横纵步长
        float xSpace = xLength / (xVertexCount - 1);
        float zSpace = zLength / (zVertexCount - 1);
        Vector3 xStep = Vector3.right * xSpace;
        Vector3 zStep = Vector3.forward * zSpace;

        // 计算顶点和顶点索引
        int vertexIndex = 0;
        int triangleIndex = 0;
        for (int z = 0; z < zVertexCount; z++)
        {
            for (int x = 0; x < xVertexCount; x++)
            {
                // 计算顶点位置
                Vector3 yStep = Vector3.up * getHeight(x * xSpace, z * zSpace);
                Vector3 point = origin + xStep * x + zStep * z + yStep;
                _vertices[vertexIndex++] = point;
                //_vertices[x + z * xVertexCount] = point;

                // 计算顶点索引,跳过右、上边界的顶点
                if ((x < xVertexCount - 1) && (z < zVertexCount - 1))
                {
                    // 顺时针连接
                    _triangles[triangleIndex + 0] = x + z * xVertexCount;
                    _triangles[triangleIndex + 1] = x + z * xVertexCount + xVertexCount;
                    _triangles[triangleIndex + 2] = x + z * xVertexCount + xVertexCount + 1;
                    _triangles[triangleIndex + 3] = x + z * xVertexCount;
                    _triangles[triangleIndex + 4] = x + z * xVertexCount + xVertexCount + 1;
                    _triangles[triangleIndex + 5] = x + z * xVertexCount + 1;

                    // 每个四边形含有2个三角面,6个顶点索引
                    triangleIndex += 6;
                }
            }
        }
    }

    /// <summary>
    /// 更新Mesh数据。
    /// </summary>
    public void UpdateMeshData()
    {
        _mesh.Clear();
        _mesh.vertices = _vertices;
        _mesh.triangles = _triangles;
        _mesh.RecalculateNormals(); // 重新计算法线
        _mesh.RecalculateTangents(); // Bumpmap Shader需要重新计算切线
        //_mesh.RecalculateBounds(); // 设置`triangles`属性后会自动计算Bounds
    }
}
发布了76 篇原创文章 · 获赞 131 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/qq_21397217/article/details/100005766