木木的Unity学习笔记(四)—— Unity中的柏林噪声(Perlin Noise)

木木的Unity学习笔记(四)—— Unity中的柏林噪声

柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多媒体领域广泛应用。算法发明者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
    {
        /// <summary>
        /// 图片的宽度
        /// </summary>
        [SerializeField] private int pictureWidth = 100;
        /// <summary>
        /// 图片的高度
        /// </summary>
        [SerializeField] private int pictrueHeight = 100;
        /// <summary>
        /// 用于柏林噪声的X采样偏移量(仿伪随机)
        /// </summary>
        [SerializeField] private float xOrg = .0f;
        /// <summary>
        /// 用于柏林噪声的Y采样偏移量(仿伪随机)
        /// </summary>
        [SerializeField] private float yOrg = .0f;
        /// <summary>
        /// 柏林噪声的缩放值(值越大,柏林噪声计算越密集)
        /// </summary>
        [SerializeField] private float scale = 20.0f;
        /// <summary>
        /// 最终生成的柏林噪声图
        /// </summary>
        private Texture2D noiseTex;
        /// <summary>
        /// 颜色数组
        /// </summary>
        private Color[] pix;
        /// <summary>
        /// 方块的材质
        /// </summary>
        private MeshRenderer meshRend;

        private void Start()
        {
            meshRend = GetComponent<MeshRenderer>();
            noiseTex = new Texture2D(pictureWidth, pictrueHeight);
            // 根据图片的宽高填充颜色数组
            pix = new Color[noiseTex.width * noiseTex.height];
            // 将生成的柏林噪声图赋值给方块的材质
            meshRend.material.mainTexture = noiseTex;
        }

        private void Update()
        {
            // 计算柏林噪声
            CalcNoise();
        }

        /// <summary>
        /// 计算柏林噪声
        /// </summary>
        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可以看到材质的变化:

模拟我的世界(Minecraft)生成地形

在Unity中新建场景,并创建一个空的GameObject,我命名为WorldRoot,编写脚本并绑定给WorldRoot,脚本如下:

using UnityEngine;

namespace Com.PerlinNoise.Fumiki
{
    public class MapCreator : MonoBehaviour
    {
        /// <summary>
        /// 用以柏林噪声采样的X和Z值(柏林噪声返回的是Y值)
        /// </summary>
        private float seedX, seedZ;

        /// <summary>
        /// 地图的宽度(X轴方向)
        /// </summary>
        [SerializeField] private int width = 50;

        /// <summary>
        /// 地图的深度(Z轴方向)
        /// </summary>
        [SerializeField] private int depth = 50;

        /// <summary>
        /// 地图的最大高度
        /// </summary>
        [SerializeField] private int maxHeight = 10;

        /// <summary>
        /// 决定了采样间隔 值越大 采样间隔越小
        /// </summary>
        [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);
        }

        /// <summary>
        /// 利用柏林噪声设定Y值
        /// </summary>
        /// <param name="cube"> 需要被设定Y值的物体 </param>
        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<MeshRenderer>().material.color = color;
        }
    }
}

运行后效果如下:

同前面的石头纹理,运行后选中WorldRoot,在Inspector面板分别拖动MaxHeight或Relief会对地形产生一定的影响,效果如下:

在正版的我的世界中,制作人也是使用的柏林噪声生成地形,是不是跃跃欲试了呢,快去试试这个新奇的东西吧。

猜你喜欢

转载自blog.csdn.net/FumikiSAMA/article/details/80212432