柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多媒体领域广泛应用。算法发明者Ken Perlin也因此算法获得奥斯卡科技成果奖。在游戏开发领域,柏林噪声可以用于生成波形,起伏不平的材质或者纹理。例如,它能用于程序生成地形(例如使用柏林噪声来生成我的世界(Minecraft)里的地形),火焰燃烧特效,水和云等等。柏林噪声绝大部分应用在2维,3维层面上,但某种意义上也能拓展到4维。柏林噪声在1维层面上可用于卷轴地形、模拟手绘线条等。
在Unity中,Unity为我们提供了柏林噪声的方法,但只支持2维层面,方法如下:
// 在Mathf类中,调用方法为Mathf.PerlinNoise(x, y);
public static float PerlinNoise(float x, float y);
参数x和y为柏林噪声采样的2维坐标点的x和y,返回值为0.0 ~ 1.0之间的小数。但是有一点需要注意,在Unity中,柏林噪声可能会生成略大于1.0的小数,如果返回值0.0 ~ 1.0对开发者很重要,那么需要限定一下返回值。并且在每次传入的参数固定相同时,柏林噪声会返回同样的返回值,例如:
private void Update()
{
float result = Mathf.PerlinNoise(5.0f, 5.0f);
Debug.Log(result); // 你会发现在Console中输出的值都是一样的
}
接下来,在Unity中实际操作一下。
在Unity中新建场景,在场景中添加一个Cube,编写脚本并绑定给Cube,脚本如下:
using System;
using UnityEngine;
namespace Com.PerlinNoise.Fumiki
{
public class RockMaterialMonitor : MonoBehaviour
{
///
/// 图片的宽度
///
[SerializeField] private int pictureWidth = 100;
///
/// 图片的高度
///
[SerializeField] private int pictrueHeight = 100;
///
/// 用于柏林噪声的X采样偏移量(仿伪随机)
///
[SerializeField] private float xOrg = .0f;
///
/// 用于柏林噪声的Y采样偏移量(仿伪随机)
///
[SerializeField] private float yOrg = .0f;
///
/// 柏林噪声的缩放值(值越大,柏林噪声计算越密集)
///
[SerializeField] private float scale = 20.0f;
///
/// 最终生成的柏林噪声图
///
private Texture2D noiseTex;
///
/// 颜色数组
///
private Color[] pix;
///
/// 方块的材质
///
private MeshRenderer meshRend;
private void Start()
{
meshRend = GetComponent();
noiseTex = new Texture2D(pictureWidth, pictrueHeight);
// 根据图片的宽高填充颜色数组
pix = new Color[noiseTex.width * noiseTex.height];
// 将生成的柏林噪声图赋值给方块的材质
meshRend.material.mainTexture = noiseTex;
}
private void Update()
{
// 计算柏林噪声
CalcNoise();
}
///
/// 计算柏林噪声
///
private void CalcNoise()
{
float y = .0f;
while (y < noiseTex.height)
{
float x = .0f;
while (x < noiseTex.width)
{
// 计算出X的采样值
float xCoord = xOrg + x / noiseTex.width * scale;
// 计算出Y的采样值
float yCoord = yOrg + y / noiseTex.height * scale;
// 用计算出的采样值计算柏林噪声
float sample = Mathf.PerlinNoise(xCoord, yCoord);
// 填充颜色数组
pix[Convert.ToInt32(y * noiseTex.width + x)] = new Color(sample, sample, sample);
x++;
}
y++;
}
noiseTex.SetPixels(pix);
noiseTex.Apply();
}
}
}
效果图:
运行游戏后,在Cube的Inspector面板上分别拖动脚本中的X Org,Y Org和Scale可以看到材质的变化:
在Unity中新建场景,并创建一个空的GameObject,我命名为WorldRoot,编写脚本并绑定给WorldRoot,脚本如下:
using UnityEngine;
namespace Com.PerlinNoise.Fumiki
{
public class MapCreator : MonoBehaviour
{
///
/// 用以柏林噪声采样的X和Z值(柏林噪声返回的是Y值)
///
private float seedX, seedZ;
///
/// 地图的宽度(X轴方向)
///
[SerializeField] private int width = 50;
///
/// 地图的深度(Z轴方向)
///
[SerializeField] private int depth = 50;
///
/// 地图的最大高度
///
[SerializeField] private int maxHeight = 10;
///
/// 决定了采样间隔 值越大 采样间隔越小
///
[SerializeField] private float relief = 15.0f;
private void Awake()
{
seedX = Random.value * 100f;
seedZ = Random.value * 100f;
for (int x = 0; x < width; x++)
{
for (int z = 0; z < depth; z++)
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.localPosition = new Vector3(x, 0, z);
cube.transform.SetParent(transform);
SetY(cube);
}
}
}
private void OnValidate()
{
if (!Application.isPlaying)
return;
foreach (Transform child in transform)
SetY(child.gameObject);
}
///
/// 利用柏林噪声设定Y值
///
/// 需要被设定Y值的物体
private void SetY(GameObject cube)
{
float y = 0;
float xSample = (cube.transform.localPosition.x + seedX) / relief;
float zSample = (cube.transform.localPosition.z + seedZ) / relief;
float noise = Mathf.PerlinNoise(xSample, zSample);
y = maxHeight * noise;
// 为了模仿我的世界的格子风 将每一次计算出来的浮点数值转换到整数值
y = Mathf.Round(y);
cube.transform.localPosition = new Vector3(cube.transform.localPosition.x,
y,
cube.transform.localPosition.z);
Color color = Color.black;
if (y > maxHeight * 0.3f)
{
ColorUtility.TryParseHtmlString("#019540FF", out color);
}
else if (y > maxHeight * 0.2f)
{
ColorUtility.TryParseHtmlString("#2432ADFF", out color);
}
else if (y > maxHeight * 0.1f)
{
ColorUtility.TryParseHtmlString("#D4500EFF", out color);
}
cube.GetComponent().material.color = color;
}
}
}
运行后效果如下:
同前面的石头纹理,运行后选中WorldRoot,在Inspector面板分别拖动MaxHeight或Relief会对地形产生一定的影响,效果如下:
在正版的我的世界中,制作人也是使用的柏林噪声生成地形,是不是跃跃欲试了呢,快去试试这个新奇的东西吧。