欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-01 13:32:19
...

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