您的位置:首页 > 产品设计 > UI/UE

Value Noise(二)

2017-11-08 21:34 295 查看

一、Value Noise 1D

  根据之前学习的Value 噪声原理,我们分步来实现之。但在这之前,我们还有一些准备工作要做,如之前所说的,Value噪声是将随机值赋于晶格点,然后根据输入点所处晶格,对随机值进行线性插值而得到噪声值,所以,我们首先初始化一个晶格顶点值数组,并将各顶点的随机值保存在顶点上,在实施中,我们初始化了一个长度512的float数组保存我们的晶格顶点随机值,即我们完成了如下图所示的工作:



初始化的代码如下:

p = new float[MapSize];
Random r = new Random();
int maxInt = 100000;
for (int i = 0; i < MapSize; i++)
p[i] = r.Next(maxInt) * 1.0f / maxInt;


初始化时,我们强制结果值在(0,1]之间,方便我们的下步操作。下面我们分步实现Value噪声1D。

第一步,计算输入点所在晶格。

  在一维时,这个网格可以被看作是条直线,在直线的整数点处有我们初始化的值,如下图所示:



  在上图中,x=0时的随机值是0.36,x=1是0.68,x=2是0.11。我们需要知道我们的输入点落在哪个区间,这很简单,我们只需要求出输入值的floor值即可以确定输入点所处的区间,代码如下:

Xi = GetFloor(x);
--------------------------------------------------------------
private int GetFloor(float value)
{
return (value >= 0 ? (int)value : (int)value - 1);
}


  GetFloor()方法很简单,就是返回输入点的floor值,这样,我们就确定了输入点所在的区间。

第二步,计算缓和函数。

  就像我们之前所的,我们希望我们得到的是平滑连续的值,这样,我们需要使用一个缓和函数来作我们插值的系数,以此来保证得到的值是连续和可导的(如果不可导,将会出现视觉突变)。我们使用输入值的小数部分运算得到查值系数。

fracX = x - (float)Xi;
fracX = Fade(fracX);


第三步,对顶点随机值进行插值。

  现在我们需要对输入点的值进行插值计算,原理如下图所示:



  在上图中,假如我们输入点是0.5,我们将利用0.36和0.68进行插值,假如我们的输入点是1.2,我们将利用0.68和0.11进行插值计算。换言之,我们将利用输入点所在区间的两个端点处存储的随机值进行插值。代码如下:

noise1 = p[Xi & (MapSize - 1)];
noise2 = p[(Xi + 1) & (MapSize - 1)];
return (float)Interpolate(noise1, noise2, fracX);


  在上面的代码中,我们先得到两个端点处存储的随机值,然后对这两个随机值进行线性插值,即得到我们需要的噪声值。这就是1D Value噪声,离散点的1D Value噪声如下图所示:



  当然,我们可以进行分形以便得到更多细节。



二、Value Noise 2D

  在2D中,给晶格顶点赋初始值,我们采用两种不同的方法:第一种方法与在1D中采用的方法一样,通过一个初始数组存储晶格顶点随机值;第二种方法我们采用计算的方法获取顶点随机值,但需要保证每次访问同一个顶点需要得到同样的随机值,我们将晶格的顶点坐标做为参数以解决同一个顶点返回同一个随机值的问题。第一种方法与1D一样,下面的代码我们将以第二种方法来演示,还是采用分步的方式:

第一步,计算输入点所在晶格。

  在2D中,晶格是一个平面,如下图所示:



  我们采用与1D中一样的方法,得到输入点(图中红点)所在晶格及其四个顶点(图中蓝点):



  代码如下:

Xi = GetFloor(x);
Yi = GetFloor(y);


第二步,计算缓和函数。

  与1D一样,我们使用输入点的小数来计算缓和系数,不再赘述。

fracX = x - (float)Xi;
fracY = y - (float)Yi;
fracX = Fade(fracX);
fracY = Fade(fracY);


第三步,对顶点随机值进行插值。

  在2D中,我们需要使用双线性插值来得到最后的噪声值,双线性插值原理如下图所示:



  在代码中,我们先得到4个顶点处存储的随机值,再进行双线性插值得到最后的噪声值,代码如下:

noise11 = hash(Xi, Yi);
noise12 = hash(Xi + 1, Yi);
noise21 = hash(Xi, Yi + 1);
noise22 = hash(Xi + 1, Yi + 1);
interpolatedx1 = (float)Interpolate(noise11, noise12, fracX);
interpolatedx2 = (float)Interpolate(noise21, noise22, fracX);
return (float)Interpolate(interpolatedx1, interpolatedx2, fracY);


  至此,我们得到2D Value噪声,得到的噪声如下图所示:



  分形2D Value噪声如下:



三、Value Noise 3D

  3D Value噪声计算原理也是一样,除了插值使用了立方插值外,其余与2D基本一致,这里不再赘述,我们直接给出代码:

            Xi = GetFloor(x);
Yi = GetFloor(y);
Zi = GetFloor(z);

fracX = x - (float)Xi;
fracY = y - (float)Yi;
fracZ = z - (float)Zi;

fracX = Fade(fracX);
fracY = Fade(fracY);
fracZ = Fade(fracZ);

noise11 = hash(Xi, Yi,Zi);
noise12 = hash(Xi + 1, Yi,Zi);
noise21 = hash(Xi, Yi + 1,Zi);
noise22 = hash(Xi + 1, Yi + 1,Zi);
interpolatedx1 = Interpolate(Interpolate(noise11, noise12, fracX), Interpolate(noise21, noise22, fracX), fracY);
noise11 = hash(Xi, Yi, Zi+1);
noise12 = hash(Xi + 1, Yi, Zi+1);
noise21 = hash(Xi, Yi + 1, Zi+1);
noise22 = hash(Xi + 1, Yi + 1, Zi+1);
interpolatedx2 = Interpolate(Interpolate(noise11, noise12, fracX), Interpolate(noise21, noise22, fracX), fracY);
return Interpolate(interpolatedx1, interpolatedx2, fracZ);


  我们将第三维作为时间输入,得到动态的2D噪声图如下:



四、小结

  总的来说,Value噪声还是比较简单的,噪声函数的实现依赖于一个随机值数组,其中每一个值都被认为是位于晶格顶点处存储的值,这对于我们理解Value噪声非常重要。在Value噪声中,当噪声频率太大时,它会再次变成白噪声,当噪声频率过高时,我们可以通过滤波去除高频部分。当然我们也可以通过不同频率得到不同细节,以此来模拟自然世界的分形现象。

五、代码下载

1、基于Unity2017.1.1f1,用Cg写的GPU代码:

Unity2017.1.1f1_Cg_ValueNoise

2、基于VS2015,用C#写的CPU 1D2D3D_ValueNoise代码:

VS2015_C#_1D2D3D_ValueNoise

参考文献

1、Value Noise and Procedural Patterns,Value Noise and Procedural Patterns
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息