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