用柏林噪声生成地图
(大部分内容是我自己的理解,如果出现了错误地方望不吝赐教)
首先了解一个函数缩放
对于一个正弦函数
y=\sin x
保持纵坐标不变,横坐标平移
y=\sin (x+\varphi)
保持纵坐标不变,横坐标变为原来的$\frac{1}{\omega}$
y=\sin (\omega x+\varphi)
保持横坐标不变,纵坐标变为原来的$A$
y=A \sin (\omega x+\varphi)
保持横坐标不变,纵坐标向竖直移动
y=A \sin (\omega x+\varphi)+k
也就是说我们都可以通过控制自变量的系数和偏移来达到可以进行缩放的效果
平面的生成
首先我们要考虑到生成一个大平面上所有的顶点,考虑生成一个边长为N的正方形
顶点的生成很简单(width用来控制相邻点间距)
for (int z=0; z<N; z++)
{
for (int x=0; x<N; x++)
{
Vector3 p = new Vector3(x, y, z) * width;
verts.Add(p);
}
}
接下来我们需要考虑顶点的编号
由于法线方向是按照左手法则来确定的,我们可以根据这样一个规则来确定大平面中的一个四边形的两个三角
顶点生成的办法如下
for (int z=0; z<N-1; z++)
{
for (int x=0; x<N-1; x++)
{
int index = z * N + x; // 左下
int index1 = (z + 1) * N + x; // 左上
int index2 = (z + 1) * N + x+1; // 右上
int index3 = z * N + x+1; // 右下
indices.Add(index); indices.Add(index1); indices.Add(index2);
indices.Add(index); indices.Add(index2); indices.Add(index3);
}
}
噪声生成
我们使用Unity内置的柏林噪声
Mathf.PerlinNoise
函数原型:public static float PerlinNoise(float x, float y);
参数的值被限制在[0,1]
为了能够控制噪声生成的高度和偏移,我们用到开始提到的函数控方法
//高度控制
public float height = 6.0f;
//缩放系数
public float noiseParam = 0.1f;
//偏移量
public float offsetX;
public float offsetZ;
for (int z = 0; z < N; z++)
for (int x = 0; x < N; x++)
{
float y = Mathf.PerlinNoise(x * noiseParam + offsetX, z * noiseParam + offsetZ) * height;
print(y);
vertices.Add(new Vector3(x, y, z) * width);
}
最后构建一下mesh的顶点网格就可以生成随机地图,完整代码如下
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Noise : MonoBehaviour
{
private MeshFilter meshFilter;
private MeshRenderer meshRenderer;
//两点之间的宽度控制
public float width = 0.1f;
//高度控制
public float height = 6.0f;
//缩放系数
public float noiseParam = 0.1f;
//偏移量
public float offsetX;
public float offsetZ;
//每行/列元素数目
public int N = 10;
//顶点数组
public List<Vector3> vertices;
//三角形数组
public List<int> triangles;
// Start is called before the first frame update
void Start()
{
vertices = new List<Vector3>();
triangles = new List<int>();
meshFilter = GetComponent<MeshFilter>();
meshRenderer = new MeshRenderer();
GenerateNoise();
}
public void GenerateNoise()
{
ClearMeshData();
//生成噪音
//float y = Mathf.PerlinNoise(1 * noisePram + offset, 1 * noisePram + offset);
//生成顶点
for (int z = 0; z < N; z++)
for (int x = 0; x < N; x++)
{
float y = Mathf.PerlinNoise(x * noiseParam + offsetX, z * noiseParam + offsetZ) * height;
print(y);
vertices.Add(new Vector3(x, y, z) * width);
}
//构造三角形数组
for(int z = 0; z < N-1; z++)
for (int x = 0; x < N-1; x++)
{
int index1 = z * N + x;
int index2 = z * N + (x + 1);
int index3 = (z + 1) * N + x;
int index4 = (z + 1) * N + (x + 1);
triangles.Add(index1);
triangles.Add(index3);
triangles.Add(index4);
triangles.Add(index1);
triangles.Add(index4);
triangles.Add(index2);
}
//构造网格
Mesh mesh = new Mesh();
//构造顶点及其三角面
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
//重新计算所有的边和法线
mesh.RecalculateNormals();
mesh.RecalculateBounds();
meshFilter.mesh = mesh;
}
public void ClearMeshData()
{
vertices.Clear();
triangles.Clear();
}
// Update is called once per frame
void Update()
{
}
}
效果图
地图拼接
我们之前给地图生成算法增加了一个偏移量,现在是发挥作用的时候
那么我们可以同时生成两块地图,其中一块的偏移量调整为当前的实际长度 * 噪声系数
这个偏移可以让当前生成的块变成噪声图上当前块的真实相邻的部分
但是由于连接点的法线信息不同,光照就会出现问题,在连接处会出现一条缝隙,考虑一下如何处理这条缝隙
由于造成问题的原因是边缘的法线信息不一致,我们可以针对邻接位置的所有点,把法线重新计算
我在当前地形的N-1位置旁的地形名字为Gene1
Vector3[] normal = mesh.normals;
Vector3[] adgNormal = GameObject.Find("Gene1").GetComponent<MeshFilter>().mesh.normals;//获得相邻位置的normal
if (transform.name == "Gene")//避免重复计算
{
for (int z = 0; z < N; z++)
{
normal[z * N + N - 1] = (adgNormal[z * N] + normal[z * N + N - 1]).normalized;
adgNormal[z * N] = normal[z * N + N - 1];
}
}
mesh.normals = normal;
GameObject.Find("Gene1").GetComponent<MeshFilter>().mesh.normals = adgNormal;
我们首先获得他们的法线,然后遍历每一行的交会点,然后把(normal1+adjnormal).normalized 相加后单位化
效果如下
可以看到裂缝已经被完美平滑,如果想要更精准的办法,我猜测可能需要对normal进行加权求均值