关于柏林噪声的基础


柏林噪声(Perlin Noise)

柏林噪声(Perlin Noise) 是一种常用的渐变噪声算法,广泛应用于游戏开发中,尤其是在程序生成内容(如随机地图、地形生成、纹理生成等)时。它由计算机图形学家 Ken Perlin 在 1983 年发明,目的是为了解决传统随机噪声(如白噪声)看起来不自然的问题。

1. 介绍

1.1 柏林噪声的特点

  1. 平滑渐变:生成的噪声没有突兀的跳跃,适合模拟自然现象,如山脉、云层。
  2. 伪随机性:噪声看似随机,但可以通过种子值生成相同的噪声序列,确保可重复性。
  3. 多维噪声:支持二维(地形)、三维(体积数据)、四维(动态效果)等不同维度的噪声。

1.2 在游戏开发中的应用

  1. 地形生成:生成自然的山脉、平原、海洋等地貌。
  2. 纹理生成:创建无缝纹理,如云彩、岩石、草地等。
  3. 程序生成内容:用于随机生成森林、迷宫、关卡等。
  4. 动态效果:如动态天气、云层运动等。

1.3 柏林噪声的优势

  • 自然感:比传统随机噪声更自然、平滑。
  • 控制性:可以调整噪声的频率、振幅来控制细节和变化幅度。

2. 基本原理

柏林噪声的实现基于 格点插值

2.1 输入坐标的划分

给定一个二维坐标(x, y),你首先需要确定它所在的网格单元。通常情况下,柏林噪声会将空间划分为许多格点(也称为 格点阵列)。

2.2 格点的随机值生成

每个格点都有一个固定的随机值(在一些实现中是随机方向的向量)。这意味着你并不是直接生成每个点的噪声,而是通过格点周围的随机值来进行插值计算。

2.3 平滑插值

对于输入坐标点(x, y),找到它所属的格点,并通过 插值(通常是 线性插值余弦插值)计算该点的噪声值。插值确保了在格点之间的变化是平滑的。

2.4 梯度(Gradient)和向量插值

每个格点都有一个随机梯度,即一个随机的方向向量。通过计算输入点与格点之间的相对向量与该格点的梯度之间的点积来得到噪声值。这些值再通过插值来平滑过渡。

2.5 多频率噪声叠加

为了增强噪声的细节和复杂性,通常会叠加多个不同频率(缩放比例)和振幅的噪声层。这种方法被称为 Octaves(八度)。每个“八度”会产生一种不同尺度的噪声。


3.噪声地图

Unity提供了方便的工具类可以直接使用用于生成噪声地图

3.1 Noise 类:生成柏林噪声地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static class Noise 
{
public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, float scale)
{
// 创建一个二维数组,用于存储噪声地图
float[,] noiseMap = new float[mapWidth, mapHeight];

// 如果比例值scale小于等于0,设置为一个非常小的值,以防止除零或无效的噪声生成
if (scale <= 0)
{
scale = 0.0001f;
}

// 遍历地图的每个像素(从y到x)
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
// 计算每个像素的X和Y采样坐标
float sampleX = x / scale;
float sampleY = y / scale;

// 使用Perlin噪声函数来生成噪声值
float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);

// 将生成的噪声值赋值给对应的噪声地图位置
noiseMap[x, y] = perlinValue;
}
}
return noiseMap;
}
}

解释:

  • GenerateNoiseMap 方法是生成噪声地图的核心。它会根据输入的 mapWidthmapHeightscale 生成一个二维的噪声图。
  • Mathf.PerlinNoise(sampleX, sampleY) 是 Unity 内置的函数,用于生成一个在 [0, 1] 范围内的柏林噪声值。scale 控制噪声的细节:较大的 scale 会生成平滑的噪声,而较小的 scale 会生成细节更多的噪声。

3.2 MapGenerator类:调用噪声生成器并显示地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MapGenerator : MonoBehaviour
{
public int mapWidth;
public int mapHeight;
public float mapScale;
public bool autoUpdate;

public void GenerateMap()
{
// 生成噪声地图
float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight, mapScale);

// 找到 MapDisplay 组件并显示噪声地图
MapDisplay display = FindAnyObjectByType<MapDisplay>();
display.DrawNoiseMap(noiseMap);
}
}

解释:

  • GenerateMap 方法调用 Noise.GenerateNoiseMap 来生成一个噪声地图,并将其传递给 MapDisplay 类的 DrawNoiseMap 方法,渲染到屏幕上。
  • autoUpdate 是一个标志,如果为 true,在每次修改参数后会自动更新地图。

3.3 MapDisplay 类:渲染噪声地图到纹理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MapDisplay : MonoBehaviour
{
public Renderer textureRender;

public void DrawNoiseMap(float[,] noiseMap)
{
int width = noiseMap.GetLength(0);
int height = noiseMap.GetLength(1);

Texture2D texture = new Texture2D(width, height);

// 储存一个大小为 width * height 的颜色数据,每个索引对应一个像素
Color[] colourMap = new Color[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// 将噪声值从 [0, 1] 映射到黑白颜色
colourMap[y * width + x] = Color.Lerp(Color.black, Color.white, noiseMap[x, y]);
}
}

// 将颜色数据应用到纹理
texture.SetPixels(colourMap);
texture.Apply();

// 将生成的纹理设置到材质
textureRender.sharedMaterial.mainTexture = texture;
textureRender.transform.localScale = new Vector3(width, 1, height);
}
}

解释:

  • DrawNoiseMap 方法负责将生成的噪声地图渲染为纹理并应用到指定的材质上。
  • Color.Lerp(Color.black, Color.white, noiseMap[x, y]):使用线性插值函数 Lerp 将噪声值映射为黑白色调。噪声值越接近 1,颜色越白;越接近 0,颜色越黑。
  • texture.SetPixels(colourMap)texture.Apply() 将颜色数据应用到纹理上,然后显示出来。

3.4 自定义编辑器(NewBehaviourScript 类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[CustomEditor(typeof(MapGenerator))]
public class NewBehaviourScript : Editor
{
public override void OnInspectorGUI()
{
MapGenerator mapGen = (MapGenerator)target;

if (DrawDefaultInspector())
{
// 自动更新地图
if (mapGen.autoUpdate)
{
mapGen.GenerateMap();
}
}

// 手动生成按钮
if (GUILayout.Button("Generate"))
{
mapGen.GenerateMap();
}
}
}

解释:

  • 这是一个自定义编辑器,用来控制 Unity Inspector 中的行为。
  • GUILayout.Button("Generate") 创建一个按钮,点击后调用 mapGen.GenerateMap() 生成地图。
  • 如果 autoUpdatetrue,则在参数更改时自动更新地图。

文章作者: 懒惰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 懒惰 !
  目录