Unity3D Value噪声算法代码实现

噪声算法

上一期我们讲了最经典的perlinNoise算法的实现
其实漏讲了一点就是噪声算法的一些规则。虽然并没有很严格的规定,但噪声算法大致都要符合以下几点,才能说是一个’有用的’,看上去’正确’的噪声算法。

1.无论使用什么随机算法,都要保证在同样的输入时获取同样的输出。(即用同样的种子可以得到同样的结果,这也是为什么很多依赖地形生成的游戏中有’种子’一说)
2.无论输入值是几维,返回值为一维类型
3.噪声需要具有连续性,如有参数连续性和几何连续性,这样噪声才不会看上去乱七八糟毫无逻辑可言。
4.四方连续性,为了保证噪声图平铺时不会出现裂缝,需要保证上下左右四个方向都是连续的,这其实也是通过连续性来实现,如hermite插值可以保证在边界处的一阶导数相等。

我们这期讲讲最简单的噪声算法即Value noise算法

Value Noise噪声算法

value噪声的算法过程如下
首先需要创建一个2D’网格’,‘网格’由’格子’构成,这里的’格子’并不是对应单个像素的,而是一个’格子’里包含有许多个像素。
(如图所示的一个格子,一个格子由9个像素组成,而’网格’由无数个这种’格子’组成,一个格子由多少像素组成对最后的效果有很大影响)
在这里插入图片描述
对每一个像素,我们需要计算它的灰度值,计算过程如下。
1.对每个’格子’的四个顶点,分别关联一个随机值(该随机值需要对同样的输入有同样的输出,一般采用某种hash算法)
2.判断该像素属于哪一个’格子’
3.对该像素相对于格子的四个顶点进行正方形插值,获得该像素的灰度值

代码实现

我们要写的函数形式是这样的,即给定一个浮点坐标值,返回一个灰度值。
当然像素坐标是没有浮点值的,这里需要用到我们用到的’格子’概念,如果一个格子包含四个像素,在传入像素坐标值的时候就要除以4。

  public static float noise(float x, float y)
    {
    }

接下来声明4个点,分明是该坐标所属的格子的四个顶点。具体的计算通过向下取整来进行。

     //声明四个顶点
     Vector2 rightUp = new Vector2((int) x + 1, (int) y + 1);
     Vector2 rightDown = new Vector2((int) x + 1, (int) y);
     Vector2 leftUp = new Vector2((int) x, (int) y + 1);
     Vector2 leftDown = new Vector2((int) x, (int) y);

然后进行正方形插值即可,正方形插值就是线性插值的二维版本。首先对上面两个顶点进行插值,接着对下面两个顶点进行插值。最后对这两个值再进行插值。
插值函数用的是unity自带的Mathf.SmoothStep,其内部实现是一个hermite插值

 //正方形插值
            float v1 = Mathf.SmoothStep(hash(leftDown), hash(rightDown), x-(int)x);
            float v2 = Mathf.SmoothStep(hash(leftUp), hash(rightUp), x-(int)x);       
            return Mathf.SmoothStep(v1, v2, y-(int)y);

使用的hash函数如下,自己创建一个hash函数都是可以的,只要满足之前说的
1.对同样的输入有同样的输出
2.粗略的满足统计上的随机性

 private static float hash(Vector2 v) => (Mathf.Cos(Mathf.Sin(1234 + v.x * 123 + v.y * 353) * 12334)+1)/2;

这样value noise的代码就写完了。
接着我们再创建一个c#脚本基于这个函数来生成一张纹理。
创建一个Texture2D然后在一个二重循环里用SetPixel函数来设置对应像素点的颜色,因为我们要创建一个灰度图,所以rgb分量全都一样。
需要注意我们传入参数的时候对坐标值除以了16,这就代表一个’格子’里有16个像素。
最后还写了一个函数把创建好的纹理保存成图片。
把这个类挂载在一个物体上就可以生成一张noise噪声图并贴在物体上,同时保存该图在Assets文件夹下。

    public class CreateValueNoise : MonoBehaviour
    {
        [Range(1, 512)] public int cellSize=16;

        private void Start()
        {
            Texture2D texture = new Texture2D(512, 512);
            this.GetComponent<Renderer>().material.mainTexture = texture;
            for (int y = 0; y < texture.height; y++)
            {
                for (int x = 0; x < texture.width; x++)
                {
                    float grayscale = ValueNoise.noise(x / (float) cellSize, y / (float) cellSize,8);
                    texture.SetPixel(x, y, new Color(grayscale, grayscale, grayscale));
                }
            }

            texture.Apply();
            saveTexture2D(texture, "tex");
        }

        void saveTexture2D(Texture2D texture, string fileName)
        {
            var bytes = texture.EncodeToPNG();
            var file = File.Create(Application.dataPath + "/02ValueNoise/" + fileName + ".png");
            var binary = new BinaryWriter(file);
            binary.Write(bytes);
            file.Close();
            AssetDatabase.Refresh();
        }
    }

效果演示

效果如下,和之前的perlin noise比较,可以看出value noise生成的效果更为杂乱无章。
和perlin noise主要用作地形和自然景观生成不同。其实value noise一般都不是单独使用,很多时候都是叠加在原始纹理上,造成一种噪点的效果。

在这里插入图片描述

地形生成

我们同样用value noise来生成一个地形试试

using Algorithm;
using UnityEngine;

public class CreateTerrain : MonoBehaviour
{
    
    void Start()
    {
        for (int z = 0; z < 128; z++)
        {
            for (int x = 0; x < 128; x++)
            {
                float grayscale = ValueNoise.noise(x / (float) 16, z / (float) 16);
                var cube=GameObject.CreatePrimitive(PrimitiveType.Cube);
                int cubeY = (int) (grayscale * 10);
                cube.transform.position = new Vector3(x, cubeY, z);
                cube.GetComponent<Renderer>().material.color=new Color(0,grayscale,0);
            }
        }
    }
}

把这个脚本挂载到任意场景中的物体上,就可以得到如下效果。
在这里插入图片描述

完整代码

using UnityEngine;

namespace Algorithm
{
    public static class ValueNoise
    {
        public static float noise(float x, float y)
        {
            //声明四个顶点
            Vector2 rightUp = new Vector2((int) x + 1, (int) y + 1);
            Vector2 rightDown = new Vector2((int) x + 1, (int) y);
            Vector2 leftUp = new Vector2((int) x, (int) y + 1);
            Vector2 leftDown = new Vector2((int) x, (int) y);

            //正方形插值
            float v1 = Mathf.SmoothStep(hash(leftDown), hash(rightDown), x-(int)x);
            float v2 = Mathf.SmoothStep(hash(leftUp), hash(rightUp), x-(int)x);
            
            return Mathf.SmoothStep(v1, v2, y-(int)y);
        }

        private static float hash(Vector2 v) => (Mathf.Cos(Mathf.Sin(1234 + v.x * 123 + v.y * 353) * 12334)+1)/2;

    }
    
    
}

生成图片的类

using System.IO;
using UnityEditor;
using UnityEngine;

namespace Algorithm
{
    public class CreateValueNoise : MonoBehaviour
    {
        [Range(1, 512)] public int cellSize=16;

        private void Start()
        {
            Texture2D texture = new Texture2D(512, 512);
            this.GetComponent<Renderer>().material.mainTexture = texture;
            for (int y = 0; y < texture.height; y++)
            {
                for (int x = 0; x < texture.width; x++)
                {
                    float grayscale = ValueNoise.noise(x / (float) cellSize, y / (float) cellSize);
                    texture.SetPixel(x, y, new Color(grayscale, grayscale, grayscale));
                }
            }

            texture.Apply();
            saveTexture2D(texture, "tex");
        }

        void saveTexture2D(Texture2D texture, string fileName)
        {
            var bytes = texture.EncodeToPNG();
            var file = File.Create(Application.dataPath + "/02ValueNoise/" + fileName + ".png");
            var binary = new BinaryWriter(file);
            binary.Write(bytes);
            file.Close();
            AssetDatabase.Refresh();
        }
    }
}

另外代码也传到github仓库里了,大家也可以关注一下哦~
我的github

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值