噪声算法
上一期我们讲了最经典的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